功能列表

  • 支持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命令等。完整源码