Go 工具链

引言

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

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

在标准配置中,当捆绑的工具链版本至少与主模块或工作区中的 gotoolchain 行指定的一样新时,go 命令会使用自己的捆绑工具链。例如,当在 go.mod 文件中指定 go 1.21.0 的主模块中使用 Go 1.21.3 捆绑的 go 命令时,go 命令会使用 Go 1.21.3。当 gotoolchain 行比捆绑的工具链版本更新时,go 命令会运行更新的工具链。例如,当在 go.mod 文件中指定 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.modgo.work 文件中的 toolchain 行会设置一个首选工具链,当 go 命令决定使用哪个工具链时,该行会优先于 go 行。

gotoolchain 行可以看作是指定模块对 Go 工具链自身的版本要求,就像 go.mod 文件中的 require 行指定对其他模块的依赖版本要求一样。go get 命令管理 Go 工具链的依赖关系,就像它管理对其他模块的依赖关系一样。例如,go get go@latest 会更新模块以要求使用最新的 Go 工具链版本。

GOTOOLCHAIN 环境变量设置可以强制使用特定的 Go 版本,从而覆盖 gotoolchain 行。例如,要使用 Go 1.21rc3 测试一个包

GOTOOLCHAIN=go1.21rc3 go test

默认的 GOTOOLCHAIN 设置是 auto,它启用了前面描述的工具链切换。另一种形式 <name>+auto 会设置在决定是否进一步切换之前的默认工具链。例如,GOTOOLCHAIN=go1.21.3+auto 指示 go 命令在做决定时,默认使用 Go 1.21.3,但如果 gotoolchain 行有指示,仍会使用更新的工具链。由于默认的 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 versionruntime.Version 报告其特定版本(例如 go1.21.0go1.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.21rc1go1.21.0 是工具链名称;go1.21go1.22 不是(初始版本是 go1.21.0go1.22.0),但 go1.20go1.19 是。

非标准工具链使用 goV-suffix 的形式命名,其中 suffix 可以是任何后缀。

工具链的比较是通过比较名称中嵌入的版本 V 进行的(去掉开头的 go 并丢弃任何以 - 开头的后缀)。例如,go1.21.0go1.21.0-custom 在排序时被视为相等。

模块和工作区配置

Go 模块和工作区在其 go.modgo.work 文件中指定与版本相关的配置。

go 行声明了使用模块或工作区所需的最低 Go 版本。出于兼容性原因,如果 go.mod 文件中省略了 go 行,则该模块被认为具有隐式的 go 1.16 行;如果 go.work 文件中省略了 go 行,则该工作区被认为具有隐式的 go 1.18 行。

toolchain 行声明了建议与模块或工作区一起使用的工具链。如下面“Go 工具链选择”部分所述,如果默认工具链的版本低于建议工具链的版本,go 命令在操作该模块或工作区时可能会运行此特定工具链。如果 toolchain 行被省略,则认为该模块或工作区具有隐式的 toolchain goV 行,其中 Vgo 行中的 Go 版本。

例如,一个 go.mod 文件指定 go 1.21.0 但没有 toolchain 行,会被解释为具有 toolchain go1.21.0 行。

Go 工具链拒绝加载声明的最低所需 Go 版本高于工具链自身版本的模块或工作区。

例如,Go 1.21.2 将拒绝加载具有 go 1.21.3go 1.22 行的模块或工作区。

模块的 go 行必须声明的版本大于或等于 require 语句中列出的每个模块声明的 go 版本。工作区的 go 行必须声明的版本大于或等于 use 语句中列出的每个模块声明的 go 版本。

例如,如果模块 M 需要一个依赖 D,其 go.mod 声明了 go 1.22.0,则 M 的 go.mod 不能声明 go 1.21.3

每个模块的 go 行设置了编译器编译该模块中的包时强制执行的语言版本。通过使用构建约束(build constraint),可以针对每个文件更改语言版本:如果存在构建约束并且隐含的最低版本至少为 go1.21,则编译该文件时使用的语言版本将是该最低版本。

例如,包含使用 Go 1.21 语言版本代码的模块应该有一个 go.mod 文件,其中包含 go 1.21go 1.21.3 这样的 go 行。如果某个特定源文件只在使用较新 Go 工具链时才应该被编译,则在该源文件中添加 //go:build go1.22 不仅确保只有 Go 1.22 及更新的工具链会编译该文件,还会将该文件中的语言版本更改为 Go 1.22。

使用 go get 修改 gotoolchain 行是最方便和安全的方式;请参阅下面的 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 环境变量的标准规则

在标准 Go 工具链中,$GOROOT/go.env 文件将默认的 GOTOOLCHAIN 设置为 auto,但重新打包的 Go 工具链可能会更改此值。

如果 $GOROOT/go.env 文件缺失或未设置默认值,go 命令将假定 GOTOOLCHAIN=local

运行 go env GOTOOLCHAIN 会打印 GOTOOLCHAIN 设置。

Go 工具链选择

启动时,go 命令选择要使用的 Go 工具链。它会查看 GOTOOLCHAIN 设置,该设置的形式为 <name><name>+auto<name>+pathGOTOOLCHAIN=autoGOTOOLCHAIN=local+auto 的简写;类似地,GOTOOLCHAIN=pathGOTOOLCHAIN=local+path 的简写。<name> 设置默认的 Go 工具链:local 表示捆绑的 Go 工具链(与当前运行的 go 命令一起提供的),否则 <name> 必须是特定的 Go 工具链名称,例如 go1.21.0go 命令优先运行默认的 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(或简写 autopath)时,go 命令会根据需要选择并运行更新的 Go 版本。具体来说,它会查看当前工作区的 go.work 文件中的 toolchaingo 行,或者在没有工作区时,查看主模块的 go.mod 文件。如果 go.workgo.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@versiongo run package@version 实际上在空的主模块中运行,并将 package@version 添加为新的依赖。所有这些命令都可能遇到 go.mod 文件中的 go 行要求使用比当前执行的 Go 版本更新的 Go 版本的模块。

当一个命令遇到需要更新 Go 版本的模块,并且 GOTOOLCHAIN 允许运行不同的工具链(它是 autopath 形式之一)时,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 发布策略支持的 Go 版本。与最小版本选择(minimal version selection)一致,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.workgo.mod 以在启动时选择正确的工具链。例外情况是 go install package@versiongo run package@version 形式,它们不在任何工作区或主模块中运行,因此无法写入 toolchain 行。每当它们需要切换到更新的工具链时,都会打印切换消息。

下载工具链

当使用 GOTOOLCHAIN=autoGOTOOLCHAIN=<name>+auto 时,Go 命令会根据需要下载更新的工具链。这些工具链被打包成特殊的模块,模块路径为 golang.org/toolchain,版本为 v0.0.1-goVERSION.GOOS-GOARCH。工具链的下载方式与其他模块一样,这意味着可以通过设置 GOPROXY 来代理工具链下载,并通过 Go 校验和数据库检查它们的校验和。由于使用的特定工具链取决于系统自身的默认工具链以及本地操作系统和架构(GOOS 和 GOARCH),将工具链模块校验和写入 go.sum 是不切实际的。因此,如果 GOSUMDB=off,工具链下载会因缺乏验证而失败。GOPRIVATEGONOSUMDB 模式不适用于工具链下载。

使用 go get 管理 Go 版本模块要求

总的来说,go 命令将 gotoolchain 行视为声明主模块版本化工具链依赖关系的方式。go get 命令可以管理这些行,就像它管理指定版本化模块依赖关系的 require 行一样。

例如,go get go@1.22.1 toolchain@1.24rc1 会将主模块的 go.mod 文件更改为 go 1.22.1toolchain go1.24rc1

go 命令理解 go 依赖项需要一个版本大于或等于 Go 版本的 toolchain 依赖项。

继续上面的例子,后续运行 go get go@1.25.0 也会将 toolchain 更新到 go1.25.0。当 toolchaingo 行完全匹配时,可以省略 toolchain 行,因此这个 go get 会删除 toolchain 行。

降级时也适用相同的要求:如果 go.mod 文件以 go 1.22.1toolchain 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@nonego get go@1.25.0 toolchain@none

go 命令理解 gotoolchain 依赖项以及查询的版本语法。

例如,就像 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 getgo mod tidy 命令会维护 go 行版本大于或等于任何所需依赖模块的 go 行版本。

例如,如果主模块的 go 行是 go 1.22.1,并且我们运行 go get example.com/widget@v1.2.3,该依赖声明的 go 行是 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 initgo work sync 命令也会根据需要更新 go 版本。

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