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