Go 工具链
简介
从 Go 1.21 开始,Go 发行版由 go 命令和一个捆绑的 Go 工具链组成,该工具链包括标准库以及编译器、汇编器和其他工具。go 命令可以使用其捆绑的 Go 工具链,也可以使用它在本地 PATH 中找到或根据需要下载的其他版本。
所使用的 Go 工具链的选择取决于 GOTOOLCHAIN 环境设置以及主模块的 go.mod 文件或当前工作区的 go.work 文件中的 go 和 toolchain 行。当你在不同的主模块和工作区之间切换时,正在使用的工具链版本可能会发生变化,就像模块依赖版本会变化一样。
在标准配置下,当 go 命令捆绑的工具链至少与主模块或工作区中的 go 或 toolchain 行指定的版本一样新时,它会使用其自身的捆绑工具链。例如,在一个声明了 go 1.21.0 的主模块中使用与 Go 1.21.3 捆绑的 go 命令时,go 命令会使用 Go 1.21.3。当 go 或 toolchain 行比捆绑的工具链更新时,go 命令会转而运行更新的工具链。例如,在一个声明了 go 1.21.9 的主模块中使用与 Go 1.21.3 捆绑的 go 命令时,go 命令会查找并运行 Go 1.21.9。它首先在 PATH 中查找名为 go1.21.9 的程序,如果找不到,则下载并缓存一份 Go 1.21.9 工具链的副本。这种自动工具链切换可以被禁用,但在这种情况下,为了更精确的向前兼容性,如果 go 行要求一个更新版本的 Go,go 命令将拒绝在该主模块或工作区中运行。也就是说,go 行设置了使用一个模块或工作区所需的最低 Go 版本要求。
作为其他模块依赖项的模块可能需要设置一个较低的最低 Go 版本要求,以便在直接操作该模块时可以使用首选的工具链。在这种情况下,go.mod 或 go.work 中的 toolchain 行设置了一个首选工具链,当 go 命令决定使用哪个工具链时,它的优先级高于 go 行。
go 和 toolchain 行可以被视为指定了模块对 Go 工具链本身的依赖版本要求,就像 go.mod 中的 require 行指定了对其他模块的依赖版本要求一样。go get 命令管理 Go 工具链的依赖关系,就像管理对其他模块的依赖一样。例如,go get go@latest 会将模块更新为要求最新的已发布 Go 工具链。
GOTOOLCHAIN 环境设置可以强制指定一个特定的 Go 版本,覆盖 go 和 toolchain 行。例如,要使用 Go 1.21rc3 测试一个包:
GOTOOLCHAIN=go1.21rc3 go test
默认的 GOTOOLCHAIN 设置是 auto,它启用了前面描述的工具链切换功能。另一种形式 <name>+auto 设置了在决定是否进一步切换之前使用的默认工具链。例如,GOTOOLCHAIN=go1.21.3+auto 指示 go 命令以默认使用 Go 1.21.3 开始其决策,但如果 go 和 toolchain 行有指示,仍然会使用更新的工具链。因为默认的 GOTOOLCHAIN 设置可以通过 go env -w 来更改,所以如果你安装了 Go 1.21.0 或更高版本,那么
go env -w GOTOOLCHAIN=go1.21.3+auto
就等同于用 Go 1.21.3 替换了你的 Go 1.21.0 安装。
本文档的其余部分将更详细地解释 Go 工具链的版本控制、选择和管理方式。
Go 版本
已发布的 Go 版本使用版本语法 ‘1.N.P’,表示 Go 1.N 的第 P 个发布版本。初始版本是 1.N.0,例如 ‘1.21.0’。后续版本如 1.N.9 通常被称为补丁发布。
Go 1.N 的发布候选版,在 1.N.0 之前发布,使用版本语法 ‘1.NrcR’。Go 1.N 的第一个发布候选版版本为 1.Nrc1,例如 1.23rc1。
语法 ‘1.N’ 被称为“语言版本”。它表示实现了该版本 Go 语言和标准库的 Go 发布系列整体。
Go 版本的语言版本是截断 N 之后所有内容的结果:1.21、1.21rc2 和 1.21.3 都实现了语言版本 1.21。
已发布的 Go 工具链,如 Go 1.21.0 和 Go 1.21rc1,通过 go version 和 runtime.Version 报告该特定版本(例如,go1.21.0 或 go1.21rc1)。而从未发布(仍在开发中)的、从 Go 开发仓库构建的 Go 工具链则只报告语言版本(例如,go1.21)。
任何两个 Go 版本都可以进行比较,以决定一个小于、大于还是等于另一个。如果语言版本不同,这就决定了比较结果:1.21.9 < 1.22。在同一个语言版本内,从小到大的顺序是:语言版本本身,然后是按 R 排序的发布候选版,最后是按 P 排序的发布版本。
例如,1.21 < 1.21rc1 < 1.21rc2 < 1.21.0 < 1.21.1 < 1.21.2。
在 Go 1.21 之前,Go 工具链的初始发布版本是 1.N,而不是 1.N.0,因此对于 N < 21,顺序会调整为将 1.N 放在发布候选版之后。
例如,1.20rc1 < 1.20rc2 < 1.20rc3 < 1.20 < 1.20.1。
更早版本的 Go 有测试版,版本如 1.18beta2。测试版在版本排序中紧接在发布候选版之前。
例如,1.18beta1 < 1.18beta2 < 1.18rc1 < 1.18 < 1.18.1。
Go 工具链名称 {#name}标准 Go 工具链的命名格式为 goV,其中 V 代表 Go 版本号,可以是测试版、发布候选版或正式发布版。
例如,go1.21rc1 和 go1.21.0 是工具链名称;
而 go1.21 和 go1.22 不是(初始发布版为 go1.21.0 和 go1.22.0),
但 go1.20 和 go1.19 是。
非标准工具链使用 goV-suffix 格式的名称,其中 suffix 可为任意后缀。
工具链通过比较名称中嵌入的版本 V 进行排序(去掉开头的 go 并丢弃以 - 开头的任何后缀)。
例如,go1.21.0 和 go1.21.0-custom 在排序时被视为相等。
模块与工作区配置
Go 模块和工作区在其 go.mod 或 go.work 文件中指定与版本相关的配置。
go 行声明使用该模块或工作区所需的最低 Go 版本。
出于兼容性考虑,若 go.mod 文件中省略 go 行,则模块被视为隐含 go 1.16 行;
若 go.work 文件中省略 go 行,则工作区被视为隐含 go 1.18 行。
toolchain 行声明建议用于该模块或工作区的工具链。
如后文“Go 工具链选择”所述,若默认工具链版本低于建议工具链版本,
go 命令在该模块或工作区中运行时可能会使用此特定工具链。
若省略 toolchain 行,则模块或工作区被视为隐含
toolchain goV 行,其中 V 为 go 行中的 Go 版本。
例如,一个仅包含 go 1.21.0 且无 toolchain 行的 go.mod 文件,
将被解释为包含 toolchain go1.21.0 行。
Go 工具链拒绝加载声明了高于工具链自身版本的最低 Go 版本要求的模块或工作区。
例如,Go 1.21.2 将拒绝加载包含 go 1.21.3 或 go 1.22 行的模块或工作区。
模块的 go 行声明的版本必须大于或等于
require 语句中列出的每个模块所声明的 go 版本。
工作区的 go 行声明的版本必须大于或等于
use 语句中列出的每个模块所声明的 go 版本。
例如,若模块 M 依赖一个声明了 go 1.22.0 的 go.mod 的依赖项 D,
则 M 的 go.mod 不能使用 go 1.21.3。
每个模块的 go 行设置编译器在编译该模块中的包时强制执行的语言版本。
可通过使用构建约束按文件更改语言版本:
如果存在构建约束且暗示最低版本至少为 go1.21,
则编译该文件时使用的语言版本将为该最低版本。
例如,包含使用 Go 1.21 语言版本代码的模块,
其 go.mod 文件应包含 go 1.21 或 go 1.21.3 这样的 go 行。
如果特定源文件仅在使用更新的 Go 工具链时才应编译,
在该源文件中添加 //go:build go1.22 既可确保只有 Go 1.22 及更新版本的工具链会编译该文件,
同时也将该文件的语言版本更改为 Go 1.22。
go 和 toolchain 行最方便且安全的修改方式是使用 go get;
请参阅下文关于 go get 的章节。
在 Go 1.21 之前,Go 工具链将 go 行视为建议性要求:
若构建成功,工具链则假设一切正常;
否则,它会打印一条关于潜在版本不匹配的说明。
Go 1.21 将 go 行改为强制性要求。
此行为部分向后移植到早期语言版本:
从 Go 1.19.13 开始的 Go 1.19 发布版和从 Go 1.20.8 开始的 Go 1.20 发布版,
将拒绝加载声明版本为 Go 1.22 或更高版本的工作区或模块。
在 Go 1.21 之前,工具链不要求模块
或工作区的 go 行版本大于或等于其每个依赖模块所要求的 go 版本。
GOTOOLCHAIN 设置
go 命令根据 GOTOOLCHAIN 设置选择要使用的 Go 工具链。
为查找 GOTOOLCHAIN 设置,go 命令使用适用于任何 Go 环境设置的标准规则:
-
若
GOTOOLCHAIN在进程环境中设置为非空值
(通过os.Getenv查询),则go命令使用该值。 -
否则,若
GOTOOLCHAIN在用户环境默认文件中设置
(通过go env -w和go env -u管理),
则go命令使用该值。 -
否则,若
GOTOOLCHAIN在捆绑的 Go 工具链的环境默认文件
($GOROOT/go.env)中设置,则go命令使用该值。
在标准 Go 工具链中,$GOROOT/go.env 文件设置默认值为 GOTOOLCHAIN=auto,
但重新打包的 Go 工具链可能会更改此值。
若 $GOROOT/go.env 文件缺失或未设置默认值,go 命令
将假定 GOTOOLCHAIN=local。
运行 go env GOTOOLCHAIN 可打印 GOTOOLCHAIN 设置。
Go 工具链选择 {#select}启动时,go 命令会选择要使用的 Go 工具链。
它会检查 GOTOOLCHAIN 设置,该设置采用 <name>、<name>+auto 或 <name>+path 的形式。
GOTOOLCHAIN=auto 是 GOTOOLCHAIN=local+auto 的简写;
类似地,GOTOOLCHAIN=path 是 GOTOOLCHAIN=local+path 的简写。
<name> 设置默认的 Go 工具链:
local 表示捆绑的 Go 工具链(即随正在运行的 go 命令一起分发的那个),否则 <name> 必须是一个特定的 Go 工具链名称,例如 go1.21.0。
go 命令优先运行默认的 Go 工具链。
如上所述,从 Go 1.21 开始,Go 工具链拒绝在需要更新 Go 版本的工作区或模块中运行。
它们会报告错误并退出。
当 GOTOOLCHAIN 设置为 local 时,go 命令始终运行捆绑的 Go 工具链。
当 GOTOOLCHAIN 设置为 <name>(例如 GOTOOLCHAIN=go1.21.0)时,go 命令始终运行该特定的 Go 工具链。
如果在系统 PATH 中找到具有该名称的二进制文件,go 命令会使用它。
否则,go 命令会使用它下载并验证的 Go 工具链。
当 GOTOOLCHAIN 设置为 <name>+auto 或 <name>+path(或简写为 auto 或 path)时,go 命令会根据需要选择并运行更新的 Go 版本。
具体来说,它会检查当前工作区的 go.work 文件中的 toolchain 和 go 行,或者在没有工作区的情况下,检查主模块的 go.mod 文件。
如果 go.work 或 go.mod 文件包含 toolchain <tname> 行且 <tname> 比默认的 Go 工具链新,
那么 go 命令将运行 <tname>。
如果该文件包含 toolchain default 行,
那么 go 命令将运行默认的 Go 工具链,禁用任何超过 <name> 的更新尝试。
否则,如果该文件包含 go <version> 行且 <version> 比默认的 Go 工具链新,
那么 go 命令将改为运行 go<version>。
要运行捆绑 Go 工具链以外的其他工具链,
go 命令会在进程的可执行文件路径中搜索(Unix 和 Plan 9 上是 $PATH,Windows 上是 %PATH%)具有给定名称的程序(例如 go1.21.3)并运行该程序。
如果找不到这样的程序,go 命令会下载并运行指定的 Go 工具链。
使用 GOTOOLCHAIN 的 <name>+path 形式会禁用下载回退,
导致 go 命令在搜索可执行文件路径后停止。
运行 go version 会打印所选 Go 工具链的版本(通过运行所选工具链实现的 go version)。
运行 GOTOOLCHAIN=local go version 会打印捆绑的 Go 工具链的版本。
从 Go 1.24 开始,你可以通过在运行 go 命令时将 toolchaintrace=1 添加到 GODEBUG 环境变量中来跟踪 go 命令的工具链选择过程。
Go 工具链切换
对于大多数命令,由于版本排序配置要求,工作区的 go.work 或主模块的 go.mod 中的 go 行将至少与任何模块依赖项中的 go 行一样新。
在这种情况下,启动时的工具链选择将运行足够新的 Go 工具链来完成命令。
某些命令会在其操作过程中引入新的模块版本:
go get 将新的模块依赖项添加到主模块;
go work use 将新的本地模块添加到工作区;
go work sync 重新同步工作区与自创建工作区以来可能已更新的本地模块;
go install package@version 和 go run package@version 有效地在空的主模块中运行并将 package@version 添加为新依赖项。
所有这些命令都可能遇到一个模块,其 go.mod 中的 go 行要求比当前执行的 Go 版本更新的 Go 版本。
当一个命令遇到需要更新 Go 版本的模块,
并且 GOTOOLCHAIN 允许运行不同的工具链(它是 auto 或 path 形式之一)时,
go 命令会选择并切换到一个合适的更新版本的工具链以继续执行当前命令。
任何时候 go 命令在启动工具链选择之后切换工具链,它都会打印一条消息说明原因。例如:
go: module example.com/widget@v1.2.3 requires go >= 1.24rc1; switching to go 1.27.9
如示例所示,go 命令可能会切换到比发现的要求更新的工具链。
通常,go 命令旨在切换到一个受支持的 Go 工具链。
为了选择工具链,go 命令首先获取可用工具链的列表。
对于 auto 形式,go 命令会下载可用工具链的列表。
对于 path 形式,go 命令会扫描 PATH 中所有名称符合有效工具链规范的可执行文件,并使用它找到的所有工具链的列表。
使用该工具链列表,go 命令会识别最多三个候选版本:
- 未发布 Go 语言版本的最新候选版本(1.N₃rcR₃),
- 最近发布的 Go 语言版本的最新补丁版本(1.N₂.P₂),以及
- 上一个 Go 语言版本的最新补丁版本(1.N₁.P₁)。
这些是根据 Go 的发布政策 所支持的 Go 版本。
与最小版本选择一致,
go 命令随后会保守地使用满足新要求的、版本_最小_(最旧)的候选版本。例如,假设 example.com/widget@v1.2.3 要求 Go 1.24rc1 或更高版本。
go 命令获取可用工具链列表后发现,最近两个 Go 工具链的最新补丁版本是
Go 1.28.3 和 Go 1.27.9,
同时还有一个 Go 1.29rc2 候选版本可用。
在这种情况下,go 命令将选择 Go 1.27.9。
如果 widget 要求 Go 1.28 或更高版本,go 命令将选择 Go 1.28.3,
因为 Go 1.27.9 版本过低。
如果 widget 要求 Go 1.29 或更高版本,go 命令将选择 Go 1.29rc2,
因为 Go 1.27.9 和 Go 1.28.3 都已过时。
当命令引入的新模块版本需要更高 Go 版本时,
会将新的最低 go 版本要求写入当前工作区的 go.work 文件
或主模块的 go.mod 文件,更新其中的 go 行。
为保持可重复性,
任何更新 go 行的命令也会同时更新 toolchain 行
以记录其自身的工具链名称。
下次在该工作区或模块中运行 go 命令时,
将在工具链选择过程中使用更新后的 toolchain 行。
例如,go get example.com/widget@v1.2.3 可能会输出如上所述的切换通知
并切换到 Go 1.27.9。
Go 1.27.9 将完成 go get 操作并将 toolchain 行
更新为 toolchain go1.27.9。
下次在该模块或工作区中运行 go 命令时,
将在启动过程中选择 go1.27.9 并且不会输出任何切换消息。
通常情况下,如果任何 go 命令被运行两次,若第一次输出了切换消息,
第二次就不会输出,因为第一次运行时也更新了 go.work 或 go.mod
以在启动时选择正确的工具链。
例外情况是 go install package@version 和 go run package@version 形式,
它们不在任何工作区或主模块中运行,因此无法写入 toolchain 行。
每当它们需要切换到更新的工具链时,都会输出切换消息。
下载工具链
当使用 GOTOOLCHAIN=auto 或 GOTOOLCHAIN=<name>+auto 时,Go 命令
会根据需要下载更新的工具链。
这些工具链被打包为特殊模块,
模块路径为 golang.org/toolchain
版本为 v0.0.1-goVERSION.GOOS-GOARCH。
工具链的下载方式与其他模块相同,
这意味着可以通过设置 GOPROXY 来代理工具链下载,
并且它们的校验和会由 Go 校验和数据库进行检查。
由于具体使用的工具链取决于系统自身的
默认工具链以及本地操作系统和架构(GOOS 和 GOARCH),
将工具链模块校验和写入 go.sum 并不实际。
相反,如果 GOSUMDB=off,工具链下载会因缺少验证而失败。
GOPRIVATE 和 GONOSUMDB 模式不适用于工具链下载。
使用 go get 管理 Go 版本的模块要求
通常,go 命令将 go 和 toolchain 行
视为主模块声明的带版本的工具链依赖项。
go get 命令可以像管理
指定带版本的模块依赖项的 require 行一样管理这些行。
例如,go get go@1.22.1 toolchain@1.24rc1 会将主模块的
go.mod 文件内容更新为 go 1.22.1 和 toolchain go1.24rc1。
go 命令理解 go 依赖项要求一个具有更高或相等 Go 版本的 toolchain 依赖项。
继续前面的例子,随后的 go get go@1.25.0 也会
将工具链更新为 go1.25.0。
当工具链与 go 行完全匹配时,可以省略 toolchain 行,
这意味着此 go get 命令将删除 toolchain 行。
同样的要求在降级时也反向适用:
如果 go.mod 最初为 go 1.22.1 和 toolchain go1.24rc1,
那么 go get toolchain@go1.22.9 将只更新 toolchain 行,
但 go get toolchain@go1.21.3 会同时将 go 行降级为
go 1.21.3。
最终效果将是只保留 go 1.21.3 而没有 toolchain 行。
特殊形式 toolchain@none 表示移除任何 toolchain 行,
如 go get toolchain@none 或 go get go@1.25.0 toolchain@none。
go 命令理解 go 和 toolchain 依赖项的
版本语法以及查询方式。
例如,就像 go get example.com/widget@v1.2 使用
example.com/widget 的最新 v1.2 版本(可能是 v1.2.3)一样,
go get go@1.22 使用 Go 1.22 语言版本的最新可用发布版本
(可能是 1.22rc3,也可能是 1.22.3)。
go get toolchain@go1.22 也是如此。
go get 和 go mod tidy 命令会维护 go 行,
使其大于或等于任何所需依赖模块的 go 行。
例如,如果主模块为 go 1.22.1,而我们运行
go get example.com/widget@v1.2.3(该版本声明了 go 1.24rc1),
那么 go get 会将主模块的 go 行更新为 go 1.24rc1。
继续前面的例子,随后的 go get go@1.22.1 会
将 example.com/widget 降级到与 Go 1.22.1 兼容的版本,
或者完全移除该依赖项要求,
就像降级 example.com/widget 的任何其他依赖项时一样。
在 Go 1.21 之前,将模块更新到新 Go 版本(例如 Go 1.22)的建议方式是
go mod tidy -go=1.22,以确保在更新 go 行的同时,
对 go.mod 进行所有针对 Go 1.22 的特定调整。
这种形式仍然有效,但现在更推荐使用更简单的 go get go@1.22。
当在工作区根目录下的模块目录中运行 go get 时,
go get 基本上会忽略工作区,
但当工作区因此会遗留一个过旧的 go 行时,
它确实会更新 go.work 文件以升级 go 行。
使用 go work 管理 Go 版本的工作区要求 {#work}如前一节所述,在工作区根目录内的目录中运行 go get 时,它会注意按需更新 go.work 文件中的 go 行,使其不低于该根目录内任何模块所要求的版本。然而,工作区也可以引用根目录外的模块;在这些目录中运行 go get 可能会导致工作区配置无效,即 go.work 中声明的 go 版本低于一个或多个 use 指令中的模块所需版本。
用于添加新 use 指令的 go work use 命令也会检查 go.work 文件中的 go 版本是否足够新以满足所有现有 use 指令的要求。要更新其 go 版本与模块不同步的工作区,请运行不带参数的 go work use。
go work init 和 go work sync 命令也会按需更新 go 版本。
要从 go.work 文件中移除 toolchain 行,请使用 go work edit -toolchain=none。