功能列表
- 支持linux、darwin
- 获取远程go版本列表
gvm remote
- 安装指定版本go
gvm install [version]
- 列出本地已下载go
gvm list
- 切换go版本
gvm use [version]
- 清理本地指定版本go
gvm clear [version]
获取远程go版本列表实现
直接拉取go下载页面html源码,然后通过正则获取版本号
res, err := http.Get("https://golang.org/dl/")
if err != nil {
return nil, err
}
pageContent, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
stableReg, err := regexp.Compile(`<div class="toggleVisible" id="go(.*?)">`)
if err != nil {
return nil, err
}
stable := stableReg.FindAllStringSubmatch(string(pageContent), -1)
unstableReg, err := regexp.Compile(`<div class="toggle" id="go(.*?)">`)
if err != nil {
return nil, err
}
unstable := unstableReg.FindAllStringSubmatch(string(pageContent), -1)
versionList := make([]string, len(stable)+len(unstable))
vListSource := append(stable, unstable...)
for i := len(vListSource) - 1; i >= 0; i-- {
versionList[i] = vListSource[i][1]
}
return versionList, nil
安装指定版本go
获取到版本后,根据版本拼接下载链接,下载到用户home目录下到.gvm文件夹,然后解压到/usr/local/目录
// 获取用户home路径
func GetUserHomePath() (string, error) {
currUser, err := user.Current()
if err != nil {
return "", err
}
return currUser.HomeDir, nil
}
// 下载以及进度条的实现
type Reader struct {
io.Reader
Total int64
Current int64
}
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
r.Current += int64(n)
percent := float64(r.Current*10000/r.Total) / 100
Show(percent)
return
}
// Show 显示进度
func Show(percent float64) {
total := 50
middle := int(percent) * total / 100.0
arr := make([]string, total)
for j := 0; j < total; j++ {
if j < middle-1 {
arr[j] = "-"
} else if j == middle-1 {
arr[j] = ">"
} else {
arr[j] = " "
}
}
bar := fmt.Sprintf("[%s]", strings.Join(arr, ""))
fmt.Printf("\r%s %% %.2f", bar, percent)
}
func DownloadFileProgress(url, filename string) error {
home, err := GetUserHomePath()
if err != nil {
return err
}
dir := home + "/.gvm/"
_, err = os.Stat(dir)
if err != nil && !os.IsExist(err) {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}
}
r, err := http.Get(url)
if err != nil {
return err
}
defer func() { _ = r.Body.Close() }()
f, err := os.OpenFile(dir+filename, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
reader := &Reader{
Reader: r.Body,
Total: r.ContentLength,
}
_, err = io.Copy(f, reader)
if err != nil {
return err
}
return nil
}
解压到/usr/local/需要root权限,所以安装过程中,会要求输入管理员密码,关于go执行shell如何获取root权限如下:
tarCmd := exec.Command("/bin/sh", "-c", "sudo rm -rf /usr/local/go && sudo tar -zxf "+home+"/.gvm/"+fileName+" -C "+"/usr/local")
if _, err := tarCmd.Output(); err != nil {
return err
}
切换版本
切换版本如果没有找到要切换的版本会自动执行install下载相应版本go,如果本地以及下载就解压到/usr/local/目录
path := home + "/.gvm/" + fileName
_, err = os.Stat(path)
// 当前版本不存在
if err != nil && !os.IsExist(err) {
fmt.Println("download from origin")
if err := Install(version, goos, arch, ""); err != nil {
log.Error("install failed", err)
return
}
return
}
tarCmd := exec.Command("/bin/sh", "-c", "sudo rm -rf /usr/local/go && sudo tar -zxf " + path + " -C " + "/usr/local")
if _, err := tarCmd.Output(); err != nil {
log.Error("change go version failed", err)
}
获取本地已下载列表
主要用到获取目录文件列表,以及正则获取版本号
// 获取已下载列表
gvmDir := home + "/.gvm/"
files, err := ioutil.ReadDir(gvmDir)
if err != nil{
return nil, err
}
if len(files) == 0{
return nil, nil
}
reg := regexp.MustCompile("go([0-9.]*)[.].*?.tar.gz")
versionList := []string{}
for i := 0; i < len(files); i++{
version := reg.FindStringSubmatch(files[i].Name())
if len(version) > 1{
versionList = append(versionList, version[1])
}
}
// 获取当前go版本
goCMD := exec.Command("go", "version")
goVersionCMDRes, err := goCMD.Output()
if err != nil{
log.Error("get now use failed", err)
return
}
reg := regexp.MustCompile("go.*?go([0-9.]*)[\\s].*?")
verionRes := reg.FindStringSubmatch(string(goVersionCMDRes))
if len(verionRes) > 1{
fmt.Println("now use: ", verionRes[1])
}
清除已下载版本
直接使用go os.Remove 方法
gvmDir := home + "/.gvm/"
if param == "all"{
if err := os.RemoveAll(gvmDir); err != nil{
log.Error( err)
return
}
}
files, err := ioutil.ReadDir(gvmDir)
if err != nil{
log.Error( err)
}
for _, v := range files{
if strings.Contains(v.Name(), param){
if err := os.Remove(gvmDir + v.Name()); err != nil{
log.Error( err)
return
}
}
}
小结
go版本兼容做的很好,很多时候无脑升最新稳定版就好,多版本共存的需求并不多,所以不是很有必要用到版本管理。造这个轮子的目的更多还是练习写代码,熟悉下go文件相关操作以及执行shell命令等。完整源码