原文地址
作为Go编程语言用户,我发现在一个项目中支持运行多个go版本是很有用的。如果你曾经尝试或考虑过这个事情,那就太好了!
在这篇文章中,我将介绍什么时候以及如何启用多个Go版本。最后,我们将讨论为什么这种用法如此强大。
下面开始
我们什么时候需要多个Go版本?
安装了Go意味着您可以运行一个Go命令来构建和测试项目。这只是一个简单的开始,离真正使用可能还存在限制。
一个更灵活的设置是,通过go1.15或go1.16命令,可以在同一个环境中运行多个版本。另一种方法是将终端的PATH环境变量设置为指向特定Go版本的SDK。
在一些情况下,我发现拥有多个版本是有益的:
1、项目之间的不同需求——在多个项目之间切换时,经常需要为每个项目使用不同的Go版本。
2、创建特定的测试环境——在测试向后兼容性或确保bug修复成功时,控制运行时版本是很重要的。
3、保持最新——当测试新特性或包需在最新的Go版本中可用。
条件准备
本指南假设您已经了解如何使用Go构建和运行程序。具体来说,这意味着您已经安装了可用的Go和Git。
- Go – https://golang.org/doc/install
- Git – https://git-scm.com/
- Modules – Using Go Modules
如何使用多个Go版本
我们可以使用go get命令来获取go的各个版本。
运行go get golang.org/dl/go<version>将下载并安装特定版本的Go命令包装器。
通过使用Go包装器,我们可以下载特定版本的Go,并运行该版本的Go工具链。
例如Go v1.16.4
$ go get golang.org/dl/go1.16.4
$ go1.16.4 download
使用go1.16.4包装器,我们可以使用Go v1.16.4进行构建和测试。例如:
$ go1.16.4 mod init hello
go: creating new go.mod: module hello
$ echo 'package main; import "fmt"; func main() { fmt.Println("Hello, World") }' > hello.go
$ go1.16.4 build
$ ./hello
Hello, World
另一个选择是使用我们刚刚下载的版本并设置GOROOT。这会将特定的版本添加到系统路径中:
$ export GOROOT=$(go1.16.4 env GOROOT)
$ export PATH=${GOROOT}/bin;${PATH}
你可以通过上面的GOROOT值删除对应文件夹以卸载特定版本。
还有一个特别版本,叫做gotip,它可以从开发树中获得Go的最新版本:
$ go get golang.org/dl/gotip
$ gotip download
注意,gotip下载并构建当前的开发版本。它还可以接受其他参数——分支名称或更改列表(CL),并使用它来提取特定的Go版本。
在钩子下如何工作
https://go.googlesource.com/dl库保存了这个功能的代码。它包含:
1、每个Go版本有一个小应用程序-例如:go1.16.4/main.go
2、实现Go包装器功能的内部包:internal/version/version. Go
3、生成Go包装器代码的应用程序:internal/genv/main.go
对于每个版本的main.go,go包装类似如下代码:
package main
import "golang.org/dl/internal/version"
func main() {
version.Run("go1.16.4")
}
正如你所看到的,没多少复杂。只是简单地调用需使用版本的公共代码。
一个打包版本的Run函数,基于命令行参数执行两个主要任务:
1、下载-运行go<version>下载
2、包装go工具链-运行go<version> <anything else>
下载
根据提供的版本、操作系统和平台将从 https://dl.google.com/go中下载Go SDK安装包保存在/home/sdk目录下。
接着,使用sha256验证安装包的完整性,方法是从下载服务器获取checksum文件。最后,它将安装包解压,并创建一个名为unpack -success 的空标记文件。
Gotip版本-当使用gotip下载时,我们只有可用的源代码—最新的或特定的分支/变更列表。
gotip: usage: gotip download [CL number | branch name]
gotip下载将使用git来获取源文件,执行清理,并运行相关的make脚本来构建Go。
与解压特定版本不同,没有标记文件来标记解压成功。相反,gotip将总是尝试获取最新版本并运行构建过程。
包装器
包装器首先验证我们有一个有效的SDK,验证我们在相关的Go文件夹下有一个unpacked-success标记文件。
如果缺少go<verison>将会有错误信息提示相应go版本未下载。运行go<version> download在/home/sdk/go<version>目录下安装。以下摘自internal/version/version.go代码,基于我们想要执行的go版本,格式化要执行的命令。 它使用GOROOT环境变量——设置正确的路径(并将其添加到path中)。
func runGo(root string) {
gobin := filepath.Join(root, "bin", "go"+exe())
cmd := exec.Command(gobin, os.Args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
newPath := filepath.Join(root, "bin")
if p := os.Getenv("PATH"); p != "" {
newPath += string(filepath.ListSeparator) + p
}
cmd.Env = dedupEnv(caseInsensitiveEnv, append(os.Environ(), "GOROOT="+root, "PATH="+newPath))
handleSignals()
if err := cmd.Run(); err != nil {
os.Exit(1)
}
os.Exit(0)
}
为每个版本提供一个Go包装器主程序,需要使用一个辅助命令- genv。它接受我们想要构建特定版本的go包装器作为输入。
首先,运行go list -m -json并解析输出:
$ go list -m -json
{
"Path": "golang.org/dl",
"Main": true,
"Dir": "<workspace>/dl",
"GoMod": "<workspace>/dl/go.mod",
"GoVersion": "1.11"
}
然后它将Path匹配为golang.org/dl,并使用Dir作为包装器代码的目标目录(' <version>/main.go ')。
最后,它使用可用的Go模板来呈现上面所述的Go包装器代码。
总结
运行多个Go版本是很简单的——它使用了Go工具。并为我们提供了一种以最少的编码访问不同版本的Go sdk的方法。