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` 設定,該設定採用 `
當 `GOTOOLCHAIN` 設定為 `local` 時,`go` 命令始終執行捆綁的 Go 工具鏈。
當 `GOTOOLCHAIN` 設定為 `
當 `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`。