Go Toolchains

Go Toolchains

从 Go 1.21 开始,Go 发行版由一个 go 命令和一个捆绑的 Go 工具链组成,工具链包括标准库以及编译器、汇编器和其他工具。 go 命令可以使用其捆绑的 Go 工具链,也可以使用它在本地 PATH 中找到或根据需要下载的其他版本,这让 go 命令本身具备了选择、下载并切换 Go 编译工具链版本的能力。不再一定需要为了不同项目手工安装、切换多个 Go 版本。

所使用的 Go 工具链取决于 GOTOOLCHAIN 环境变量的设置,以及主模块的 go.mod 文件或当前工作区的 go.work 文件中的 gotoolchain 指令。当在不同的主模块和工作区之间切换时,实际使用的工具链版本也可能随之变化,就像模块依赖的版本会发生变化一样。

术语

文档特别区分了两个易混淆的概念:

  • Language version:语法为 "1.N",如 1.21,它涵盖所有实现该版本 Go 语言和标准库的发布版本。
  • Released versions:语法“1.N.P”,表示 Go 1.N 的第 P 个版本,如 1.21.5

以 Go Release Notes 为例,涉及以下几方面修改:

  • Language:如 Go 1.27 now supports generic methods
  • Tools / Go command:开发工具的变化,比如 go 命令、go testgo docgo fixgo modvet 等。
  • Runtime:GC、goroutine 调度、内存分配、栈、panic、profiling、tracing。
  • Compiler:包括优化、代码生成、内联、escape analysis、symbol 处理等
  • Linker / Assembler / cgo:链接器、汇编器、cgo 以及构建底层机制的变化
  • Standard library:新 Package、新 API、deprecated API 等
  • Ports / Platform Support

与 C++ 不同,C++ 11/17/20/23/26 都是语言规范,如 ISO/IEC 14882:2017,不是某个具体工具链版本(gcc/clang/msvc),类比的话相当于 Go Release Notes 中的 Language 部分。

Go 1.26 的范围比“语言规范”更大,涵盖了上面的多个方面。当然,经常会说 Go 1.xx 发布了 xx 新特性,这里值得其实就是 Language 部分。

配置来源:go.mod 中的两个指令

工具链选择由 go.mod(或 go.work)中的两个指令控制。

go 指令

module example.com/myapp

go 1.21.0

在标准配置下,如果 go 命令自身捆绑的工具链版本不低于主模块或工作区中 gotoolchain 命令所指定的版本,那么 go 命令就会直接使用自身捆绑的工具链。

例如,当你使用 Go 1.21.3 自带的 go 命令,而主模块中写的是 go 1.21.0 时,go 命令会使用 Go 1.21.3。

如果 gotoolchain 命令指定的版本比当前捆绑的工具链更新,那么 go 命令会改为运行更新版本的工具链。

例如,当你使用 Go 1.21.3 自带的 go 命令,而主模块中写的是 go 1.21.9 时,go 命令会查找并运行 Go 1.21.9。它首先会在 PATH 中查找名为 go1.21.9 的程序;如果找不到,则会下载 Go 1.21.9 工具链并将其缓存起来。

这种自动切换工具链的行为可以被禁用。不过,在禁用之后,为了提供更精确的向前兼容性保证,如果主模块或工作区中的 go 命令要求的 Go 版本高于当前工具链版本,go 命令将拒绝运行。

也就是说,go 命令规定了使用某个模块或工作区所必需的最低 Go 版本。

版本选择逻辑

运行 go 命令时,版本选择遵循以下流程:

  1. 确定本地默认工具链版本(即已安装的 go
  2. 读取 go.mod / go.work 中的版本要求
  3. 比较两者:
    • 本地版本 要求版本,直接使用本地工具链
    • 本地版本 < 要求版本,下载并自动切换到目标工具链(如果禁用了自动切换工具链,则 go 命令拒绝运行)

toolchain 指令

作为其他模块依赖项的模块,可能需要将最低 Go 版本要求设置得低于直接在该模块中开发时所希望使用的工具链版本。

在这种情况下,go.modgo.work 文件中的 toolchain 行用于指定首选工具链。当 go 命令决定使用哪个工具链时,toolchain 行的设置优先于 go 行。

module example.com/myapp

go 1.21.0
toolchain go1.21.5

gotoolchain 命令可以理解为指定模块对 Go 工具链本身的版本要求,就像 go.mod 文件中的 require 行指定当前模块对其他模块依赖的版本要求一样。

go get 命令管理 Go 工具链依赖的方式,也与它管理其他模块依赖的方式类似。例如,go get go@latest 会更新当前模块,使其要求使用最新发布的 Go 工具链版本。

可以认为工具链被设计为一种特殊的 Go module,因此复用了模块的分发与校验体系:

  • 通过 module proxy(默认 proxy.golang.org)下载
  • 存储在模块缓存的 golang.org/toolchain 路径下
  • GOPROXYGOSUMDB 控制,通过 checksum database 保证完整性

下列内容参考 聊聊Go语言的向前兼容性和toolchain规则 | Tony Bai

Go 1.21 版本后的向前兼容性策略的调整就是参考了 go module 对依赖的管理方法:即将 go 版本和 go toolchain 版本作为一个 module 的“依赖”来管理

所谓向前兼容性指的是:使用低版本编译器编译 go 命令为高版本的 Go module,在 Go 1.21 之前,可能会遇到编译失败(如 module 代码中使用了新版本的语言特性)、运行失败甚至运行时行为出现breaking change的问题,而这些都是不确定的。也许自己写的代码可以通过约定特性的使用等措施来避免问题,但是如果 module 有外部依赖,却无法保证依赖不存在这种向前兼容性的问题。

Go从1.21版本开始,改变了 go.mod 中 go 命令的语义:指定了 module 最小可用的 Go 版本,如果Go编译器版本低于go.mod中的go版本,将无法编译代码:

module example

go 1.26.1 // 指定最小可用版本为Go 1.26.1

$GOTOOLCHAIN=local go build
go: go.mod requires go >= 1.26.1 (running go 1.26.0; GOTOOLCHAIN=local)

必须设置了 GOTOOLCHAIN=local,否则 auto 模式下会自动按需自动下载高版本 toolchain 并切换

在 go 1.21 版本前,解决方案是手动下载更新版本的 Go Release 进行编译,这会导致开发者需要管理多个 Go Release 版本。

在 Go 1.21 版本及以后,go 还提供了自动 Go 工具链管理,如果 go 发现本地工具链版本低于 go module 要求的最低 go 版本,那么 go 会自动下载高版本的 go 工具链,缓存到 go module cache 中,并用新下载的 go 工具链对 module 进行编译构建。

module demo1

go 1.21.1 // 指定最小可用版本为Go 1.21.1

$go build
go: downloading go1.21.1 (darwin/amd64)

对应module有依赖的情况,比如下图:

understand-go-forward-compatibility-and-toolchain-rule-3.png

这里要正确编译图中的 main module,我们至少需要go 1.21.0版本,这个版本是main所有依赖中version最大的那个。

当然最终选择哪个版本的 go 工具链对 module 进行编译,则有一个选择决策的过程。

Go 最小版本选择(MVS)research!rsc: Minimal Version Selection (Go & Versioning, Part 4)

GOTOOLCHAIN 环境变量

GOTOOLCHAIN 是控制工具链行为的总开关,优先级高于 go.mod 中的指令。

取值行为
auto默认值,依据上面的选择逻辑按需自动下载并切换
local仅使用本地版本,禁止切换(版本不足则报错)
path使用 PATH 中匹配的版本
go1.21.5强制使用指定版本
<版本>+auto设定最低默认版本,同时允许向上切换

常用设置:

go env GOTOOLCHAIN              # 查看当前设置
go env -w GOTOOLCHAIN=local     # 关闭自动下载
go env -w GOTOOLCHAIN=auto      # 恢复自动模式

版本管理命令

go get go@latest            # go 指令升级到最新版本
go get go@1.21.5            # go 指令设置到指定版本
go get toolchain@go1.21.5   # 升级 toolchain 指令
go get toolchain@none       # 删除 toolchain 指令

Reference

Go Toolchains - The Go Programming Language

Forward Compatibility and Toolchain Management in Go 1.21 - The Go Programming Language

research!rsc: Minimal Version Selection (Go & Versioning, Part 4)

聊聊Go语言的向前兼容性和toolchain规则 | Tony Bai

评论