Go 工具链

简介

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

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

在标准配置中,go 命令使用其自己的捆绑工具链,当该工具链至少与主模块或工作区中的 gotoolchain 行一样新时。例如,当在主模块中使用 Go 1.21.3 捆绑的 go 命令时,该主模块显示 go 1.21.0go 命令使用 Go 1.21.3。当 gotoolchain 行比捆绑工具链更新时,go 命令将运行更新的工具链。例如,当在主模块中使用 Go 1.21.3 捆绑的 go 命令时,该主模块显示 go 1.21.9go 命令将查找并运行 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 行指示,仍使用更新的工具链。由于可以使用 go env -w 更改默认 GOTOOLCHAIN 设置,因此如果你已安装 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 等后续版本通常称为补丁版本。

在 1.N.0 之前发布的 Go 1.N 发布候选版本使用版本语法“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 具有测试版,版本类似于 1.18beta2。测试版在版本排序中紧接在发布候选版本之前。

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

Go 工具链名称

标准 Go 工具链的名称为 goV,其中 V 是表示测试版、候选版本或正式版本的 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 版本。

例如,一个没有 toolchain 行的 go.mod 文件,其中写着 go 1.21.0,会被解释为好像它有一行 toolchain go1.21.0

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

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

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

例如,如果模块 M 需要一个依赖项 D,且 Dgo.mod 声明了 go 1.22.0,那么 Mgo.mod 不能声明 go 1.21.3

每个模块的 go 行设置了编译器在编译该模块中的包时强制执行的语言版本。可以使用 构建约束 按文件更改语言版本。

例如,包含使用 Go 1.21 语言版本代码的模块应具有一个 go.mod 文件,其中 go 行类似于 go 1.21go 1.21.3。如果仅在使用较新的 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 或更高版本的 workspace 或模块。

在 Go 1.21 之前,工具链不要求模块或 workspace 的 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>+path 的形式。GOTOOLCHAIN=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 工具链切换

对于大多数命令,工作区的 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/[email protected] requires go >= 1.24rc1; switching to go 1.27.9

如示例所示,go 命令可能会切换到比发现的要求更新的工具链。通常,go 命令的目标是切换到受支持的 Go 工具链。

为了选择工具链,go 命令首先获取可用工具链的列表。对于 auto 形式,go 命令下载可用工具链的列表。对于 path 形式,go 命令扫描 PATH 以查找任何以有效工具链命名的可执行文件,并使用它找到的所有工具链的列表。使用该工具链列表,go 命令最多识别三个候选

根据 Go 的发布策略,这些是受支持的 Go 版本。与最小版本选择一致,go 命令随后保守地使用具有满足新要求的最小(最旧)版本的候选。

例如,假设 example.com/[email protected] 需要 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/[email protected] 可能会打印如上所示的切换通知,并切换到 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 [email protected] [email protected] 会将主模块的 go.mod 文件更改为 go 1.22.1toolchain go1.24rc1

go 命令理解 go 依赖项需要具有更大或相等 Go 版本的 toolchain 依赖项。

继续这个示例,稍后的 go get [email protected] 也将把工具链更新为 go1.25.0。当工具链与 go 行完全匹配时,可以省略它并暗示它,因此此 go get 将删除 toolchain 行。

降级时也适用相同的要求:如果 go.modgo 1.22.1toolchain go1.24rc1 开始,则 go get [email protected] 将仅更新 toolchain 行,但 go get [email protected] 也将把 go 行降级为 go 1.21.3。其效果是仅留下 go 1.21.3 而没有 toolchain 行。

特殊形式 toolchain@none 表示删除任何 toolchain 行,如 go get toolchain@nonego get [email protected] toolchain@none

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

例如,正如 go get example.com/[email protected] 使用 example.com/widget 的最新 v1.2 版本(可能是 v1.2.3),go get [email protected] 使用 Go 1.22 语言版本的最新可用版本(可能是 1.22rc3,也可能是 1.22.3)。go get [email protected] 也适用相同的情况。

go getgo mod tidy 命令维护 go 行,使其大于或等于任何必需的依赖项模块的 go 行。

例如,如果主模块有 go 1.22.1,并且我们运行 go get example.com/[email protected](声明 go 1.24rc1),则 go get 将把主模块的 go 行更新为 go 1.24rc1

继续这个示例,稍后的 go get [email protected] 将把 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 [email protected]

当在工作区根目录中包含的目录中的模块中运行 go get 时,go get 大多忽略工作区,但它确实会更新 go.work 文件以升级 go 行,而工作区原本会保留过旧的 go 行。

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

如前一节所述,在工作区根目录内的目录中运行的 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 initgo work sync 也根据需要更新 go 版本。

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