Go 工具链

引言

从 Go 1.21 开始,Go 发行版包含一个 `go` 命令和一个捆绑的 Go 工具链,后者是标准库以及编译器、汇编器和其他工具。`go` 命令可以使用其捆绑的 Go 工具链,也可以使用它在本地 `PATH` 中找到或按需下载的其他版本。

所使用的 Go 工具链的选择取决于 `GOTOOLCHAIN` 环境变量设置以及主模块的 `go.mod` 文件或当前工作区的 `go.work` 文件中的 `go` 和 `toolchain` 行。当您在不同的主模块和工作区之间切换时,所使用的工具链版本可能会有所不同,就像模块依赖版本一样。

在标准配置中,当主模块或工作区中的 `go` 或 `toolchain` 行所要求的工具链版本不低于 `go` 命令自身捆绑的工具链版本时,`go` 命令会使用其捆绑的工具链。例如,当在声明 `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.0 安装替换为 Go 1.21.3。

本文档的其余部分将更详细地解释 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 有 Beta 版本,版本号如 1.18beta2。Beta 版本在版本排序中紧接在发布候选版本之前。

例如,1.18beta1 < 1.18beta2 < 1.18rc1 < 1.18 < 1.18.1。

Go 工具链名称

标准 Go 工具链的名称为 `goV`,其中 V 是表示 Beta 版本、发布候选版本或发布版本的 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 需要一个依赖项 D,其 `go.mod` 声明 `go 1.22.0`,那么 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 环境变量设置的标准规则

  • 如果在进程环境中(通过 `os.Getenv` 查询)`GOTOOLCHAIN` 设置为非空值,`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 工具链选择

启动时,`go` 命令选择要使用的 Go 工具链。它会参考 `GOTOOLCHAIN` 设置,该设置采用 ``、`+auto` 或 `+path` 的形式。`GOTOOLCHAIN=auto` 是 `GOTOOLCHAIN=local+auto` 的简写;类似地,`GOTOOLCHAIN=path` 是 `GOTOOLCHAIN=local+path` 的简写。`` 设置默认的 Go 工具链:`local` 表示捆绑的 Go 工具链(与正在运行的 `go` 命令一起提供的工具链),否则 `` 必须是特定的 Go 工具链名称,例如 `go1.21.0`。`go` 命令倾向于运行默认的 Go 工具链。如上所述,从 Go 1.21 开始,Go 工具链拒绝在要求更新 Go 版本的工作区或模块中运行。相反,它们会报告错误并退出。

当 `GOTOOLCHAIN` 设置为 `local` 时,`go` 命令始终运行捆绑的 Go 工具链。

当 `GOTOOLCHAIN` 设置为 ``(例如,`GOTOOLCHAIN=go1.21.0`)时,`go` 命令始终运行该特定 Go 工具链。如果在系统 PATH 中找到具有该名称的二进制文件,`go` 命令将使用它。否则,`go` 命令将使用它下载并验证的 Go 工具链。

当 `GOTOOLCHAIN` 设置为 `<name>+auto` 或 `<name>+path`(或简写 `auto` 或 `path`)时,`go` 命令会根据需要选择并运行更新的 Go 版本。具体来说,它会查看当前工作区的 `go.work` 文件(如果不存在工作区,则查看主模块的 `go.mod` 文件)中的 `toolchain` 和 `go` 行。如果 `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` 命令时向 `GODEBUG` 环境变量添加 `toolchaintrace=1` 来跟踪 `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` 行完全匹配时,它可以省略并隐含,因此此 `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.work` 文件以升级 `go` 行,以防工作区否则会留下过旧的 `go` 行。

使用 `go work` 管理 Go 版本工作区要求

如上一节所述,在工作区根目录内的目录中运行的 `go get` 会注意根据需要更新 `go.work` 文件的 `go` 行,使其大于或等于该根目录内的任何模块。但是,工作区也可以引用根目录之外的模块;在这些目录中运行 `go get` 可能会导致无效的工作区配置,其中 `go.work` 中声明的 `go` 版本小于 `use` 指令中的一个或多个模块。

`go work use` 命令会添加新的 `use` 指令,同时也会检查 `go.work` 文件中的 `go` 版本是否足以满足所有现有 `use` 指令。要更新 `go` 版本与其模块不同步的工作区,请不带任何参数运行 `go work use`。

`go work init` 和 `go work sync` 命令也会根据需要更新 `go` 版本。

要从 `go.work` 文件中删除 `toolchain` 行,请使用 `go work edit -toolchain=none`。