引子

在使用 Go 语言开发过程中,我发现经常会遇到想使用的项目的 Go 版本与当前本机的 Go 版本不一致的情况,通常是本地的版本较低。

所以每次都需要去手动更新版本,而手动更新的过程是比较繁琐的,需要下载新版本并替换旧版本(听起来也没什么是吧)但是 Go 版本的更新还算是比较频繁的,特别是各种小版本。

所以我寻思着一定有什么自动化的更新或者可以管理本地的 Go 版本的工具吧,于是在浏览 Reddit 的过程中找到了 gvmg 这两个 Go 版本工具。

在进入 gvm 之前,我想了想,自己的 Go 是怎么安装的呢?使用命令 go env | grep GOROOT 查看路径发现我是通过 Homebrew 安装的。

所以执行 brew upgrade go应该是可行的,但是怎么说呢,速度感人啊,一共花了 41s

故之后的 gvm 测试我决定拿到开发板上进行测试,之前我们在开发板环境搭建中不就遇到了 Golang 版本的切换问题吗?🧐

gvm

听名字有点像 Java 中的 jvm 不是吗?Anyway,我使用开发板进行这个测试。

安装

# 安装bison
sudo apt-get install bison
# 安装gvm
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
Cloning from https://github.com/moovweb/gvm.git to /home/debian/.gvm
Created profile for existing install of Go at /usr/local/go
Installed GVM v1.0.22

Please restart your terminal session or to get started right away run
 `source /home/debian/.gvm/scripts/gvm`

测试命令

gvm list all
# 安装1.24.5
gvm install 1.24.5

额,发现其下载速度感人,最后下载还显示错误ERROR: Unrecognized Go version

g

安装时就遇到问题,我发现其在 riscv64 架构上并不适配。

curl -sSL https://raw.githubusercontent.com/voidint/g/master/install.sh | bash
[1/3] Downloading https://github.com/voidint/g/releases/download/v1.8.0/g1.8.0.linux-.tar.gz

可以看到 linux- 后面缺了一块,在其 release 中也发现暂未支持 RiscV 架构,但是用 Go 写的,那自己编译试试呗。

手动编译适配

其实对于 Golang 项目,其对于 RiscV 架构的适配是比较简单的,一方面因为 RiscV 架构比较新,没人用啊,哈哈;另一方面 Go 本身是适配 RiscV 架构的,所以这并不是一件难事。

先手动编译一下

git clone https://github.com/voidint/g.git
cd g
GOOS=linux GOARCH=riscv64 go build

这会编译出一个 g 二进制文件,接下来将其移动到 bin 中

mkdir -p ~/.g/bin
mv g ~/.g/bin/g
export PATH="$HOME/.g/bin:$PATH"
source ~/.bashrc

测试使用,注意使用之前先卸载掉之前已经安装好的 Go,或者修改 bashrc 中关于 Go 的路径设置。

g ls-remote
g install 1.24.5

Oh,居然没有遇到架构问题?damn,成功了,看来这两个 Go 系统管理系统在安装逻辑这里有区别哦。

g install 1.24.5
Downloading 100% [===============] (76/76 MB, 9.0 MB/s)
Computing checksum with SHA256
Checksums matched
Now using go1.24.5 linux/riscv64

可以发现 go 文件夹安装到了 ~/.g 路径下,所以我们来修改 bashrc 中的路径。

# g: Go 版本管理器
export PATH="$HOME/.g/bin:$PATH"

# 设置 g 当前版本的 GOROOT 和 PATH
export GOROOT="$HOME/.g/go"
export PATH="$GOROOT/bin:$PATH"

# 设置 GOPATH(你的 go mod 或 go install 会安装到这里)
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"

至此 g 可以在开发板上顺利切换版本。

# 列出当前已安装的版本
g ls
* 1.24.2
  1.24.5
# 使用某个已经安装的具体版本 
g use 1.24.5
Now using go1.24.5 linux/riscv64

不同点

g install 流程

首先来到 g 的源码中关于 install 的部分,大致梳理一遍下载流程。

  • 获取要下载的版本名称
  • 构建 NewCollector
    • 根据 url 选择构建何种的 Collector
    • 将 url 预解析为 pURL,内容更加丰富,带有 Host,Path 等字段
    • 对 url 发送 Get 请求,使用 goquery 读取响应的 HTML
  • 获取所有的版本信息保存在 items 中
  • 根据版本名称,os,arch 信息找到 items 中对应的信息
  • 拼凑出将要安装到 g 的路径,例如 /Users/lutao/.g/versions/1.22.5,并检查其是否已存在
  • 从 items中找到符合 ArchiveKind, os, arch 的包文件(可能不止一个)
  • checkSum
  • 拼接 fileName 作为要从远程下载的文件名称 /Users/lutao/.g/downloads/go1.22.5.darwin-arm64.tar.gz
  • 下载return httppkg.Download(pkg.URL, dst, os.O_CREATE|os.O_WRONLY, 0644, true)
  • 解压下载的文件
  • 切换版本

1

最终在~/.g的目录下会有

  • downloads:不同版本的 go 源文件
  • versions:解压源文件得到的文件
  • go:软链接,指向versions中的某个版本的文件

gvm install 流程

与 g 不同的是,gvm 是以一个完全的 shell 脚本运行的,但是底层逻辑差不多都是从官方仓库拉取源码进行安装,设置路径,文件目录等。

download_binary的部分对架构和 os 进行了判断,显然其中没有 RiscV 架构,所以我们之前安装失败。

向g项目学习

因为自己做的毕设可能也要开发命令行工具,所以 g 项目对我而言吸引力还是蛮大的。

我顺便看了看 ls,ls_remote 等功能是如何实现的,下面简单谈谈吧。

ls

本质是os.ReadDir()方法的运用,读出的文件夹格式如下

  • parent
  • name
  • typ
  • info

接着利用 name 构造 version 结构体:namej,sv,pkgs,并按升序排序。

Version represents a Go language distribution version.

调用 render,根据传来的版本信息打印出结果。值得学习的是,color.New().Fprintf()这样的着色式打印信息。

做出适配贡献

着眼于 Makefile,打开后发现其对于 linux 的构建规则中没有 riscv64,所以我们进行添加

build-linux: build-linux-386 build-linux-amd64 build-linux-arm build-linux-arm64 build-linux-s390x build-linux-riscv64

build-linux-riscv64:
	GOOS=linux GOARCH=riscv64 $(GO) build $(GO_FLAGS) -o  bin/linux-riscv64/g

同时,看到其安装方式是通过安装install.sh脚本实现,所以也要修改这个脚本。

原本脚本中是这样的,没有 riscv 架构,所以会出现最开始那种情况 linux- 缺了一块关于架构的。

function get_arch() {
    a=$(uname -m)
    case ${a} in
    "x86_64" | "amd64")
        echo "amd64"
        ;;
    "i386" | "i486" | "i586")
        echo "386"
        ;;
    "aarch64" | "arm64")
        echo "arm64"
        ;;
    "armv6l" | "armv7l")
        echo "arm"
        ;;
    "s390x")
        echo "s390x"
        ;;
    *)
        echo ${NIL}
        ;;
    esac
}

添加关于 riscv64 的字段即可。

最后我们将其汇总为一个 Pull Request 提交到了仓库中。

总结

Golang 的版本更新需要一个管理工具,反观 Java,在学习的时候好像很少有人提到其版本的更新,听到最多的是 Java 使用的版本要不然是 Java8 要不然就是 17,选一个很稳定的一直用。

Java 的版本工具最常用的是 SDKMAN(AI 回答)。

最终我们选择了 g 这个命令行项目作为我在 RiscV 开发板上的 Go 版本管理工具,并且这个项目有很多关于写命令行操作中值得学习的地方,最后我们提出了相应的 PR 🫡

这里是LTX,感谢您阅读这篇博客,人生海海,和自己对话,像只蝴蝶纵横四海。