Go Wiki:Go 模組

本 Wiki 頁面旨在提供使用和故障排除指南。

自 1.11 版起,Go 已包含對版本化模組的支援,如此處所提議。最初的 vgo 原型於 2018 年 2 月釋出。2018 年 7 月,版本化模組進入主 Go 倉庫。

Go 1.14 起,模組支援被認為已為生產環境做好準備,建議所有使用者從其他依賴管理系統遷移到模組。如果您因 Go 工具鏈中的問題而無法遷移,請確保該問題已提交開放問題。(如果問題未在 Go1.16 里程碑中,請評論說明它為何阻止您遷移,以便適當安排優先順序)。您還可以提供體驗報告以獲取更詳細的反饋。

近期變更

Go 1.16

詳情請參閱Go 1.16 釋出說明

  • 在所有情況下,模組模式(GO111MODULE=on)均為預設設定。
  • 命令預設不再修改 go.mod / go.sum-mod=readonly)。
  • go install pkg@version 是全域性安裝包/可執行檔案的推薦方式。
  • retractgo.mod 中可用。

Go 1.15

詳情請參閱Go 1.15 釋出說明

  • 模組快取的位置現在可以透過 GOMODCACHE 環境變數設定。GOMODCACHE 的預設值為 GOPATH[0]/pkg/mod,即此更改之前模組快取的位置。
  • 現在有了一個解決方案,用於解決 Go 命令訪問模組快取時,外部程式併發掃描檔案系統導致的 Windows “訪問被拒絕”錯誤(參見問題 #36568)。此解決方案預設不啟用,因為它在 Go 1.14.2 以下和 1.13.10 以下版本與同一模組快取併發執行時不安全。可以透過顯式設定環境變數 GODEBUG=modcacheunzipinplace=1 來啟用。

Go 1.14

詳情請參閱Go 1.14 釋出說明

  • 當主模組包含頂級 vendor 目錄且其 go.mod 檔案指定 go 1.14 或更高版本時,Go 命令現在對接受該標誌的操作預設為 -mod=vendor
  • go.mod 檔案為只讀且不存在頂級 vendor 目錄時,-mod=readonly 現在預設設定為啟用。
  • -modcacherw 是一個新的標誌,指示 Go 命令將模組快取中新建立的目錄保留其預設許可權,而不是將其設定為只讀。
  • -modfile=file 是一個新標誌,指示 Go 命令讀取(並可能寫入)替代的 go.mod 檔案,而不是模組根目錄中的檔案。
  • 當模組感知模式被顯式啟用(透過設定 GO111MODULE=on)時,如果不存在 go.mod 檔案,大多數模組命令的功能會更受限制。
  • Go 命令現在在模組模式下支援 Subversion 倉庫。

Go 1.13

詳情請參閱Go 1.13 釋出說明

  • go 工具現在預設從公共 Go 模組映象 https://proxy.golang.org 下載模組,並且預設針對公共 Go 校驗和資料庫 https://sum.golang.org 驗證下載的模組(無論來源如何)。
    • 如果您有私有程式碼,您很可能應該配置 GOPRIVATE 設定(例如 go env -w GOPRIVATE=*.corp.com,github.com/secret/repo),或者更細粒度的變體 GONOPROXYGONOSUMDB,它們支援不那麼常見的使用場景。有關更多詳細資訊,請參閱文件
  • GO111MODULE=auto 會在找到任何 go.mod 檔案時啟用模組模式,即使是在 GOPATH 內部。(在 Go 1.13 之前,GO111MODULE=auto 永遠不會在 GOPATH 內部啟用模組模式)。
  • go get 引數已更改。
    • go get -u(不帶任何引數)現在只升級當前“包”的直接和間接依賴項,不再檢查整個“模組”。
    • 從模組根目錄執行 go get -u ./... 會升級模組的所有直接和間接依賴項,現在不包括測試依賴項。
    • go get -u -t ./... 類似,但也會升級測試依賴項。
    • go get 不再支援 -m(因為它會因其他更改而與 go get -d 大致重疊;您通常可以用 go get -d foo 替換 go get -m foo)。

目錄

“快速入門”和“新概念”部分對於剛開始使用模組的人來說特別重要。“如何…”部分涵蓋了更多機制細節。本頁內容最多的部分是回答更具體問題的常見問題解答;至少瀏覽一下此處列出的常見問題解答的簡短描述是值得的。

快速入門

示例

詳細資訊將在本頁的其餘部分介紹,但這裡有一個從頭開始建立模組的簡單示例。

在 GOPATH 之外建立一個目錄,並選擇性地初始化 VCS

$ mkdir -p /tmp/scratchpad/repo
$ cd /tmp/scratchpad/repo
$ git init -q
$ git remote add origin https://github.com/my/repo

初始化一個新模組

$ go mod init github.com/my/repo

go: creating new go.mod: module github.com/my/repo

編寫程式碼

$ cat <<EOF > hello.go
package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}
EOF

構建並執行

$ go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2
$ go build -o hello
$ ./hello
Hello, world.

go.mod 檔案已更新,包含您的依賴項的明確版本,其中 v1.5.2 是一個 semver 標籤。

$ cat go.mod
module github.com/my/repo

go 1.16

require rsc.io/quote v1.5.2

日常工作流程

在 1.16 之前,執行 go build -o hello 之前不需要 go getgo mod tidy。從 1.16 開始,預設停用對 go.modgo.sum 檔案的隱式修改。

您的日常工作流程可以如下

  • 根據需要將匯入語句新增到您的 .go 程式碼中。
  • 諸如 go buildgo test 等標準命令將根據需要自動新增新依賴項以滿足匯入(更新 go.mod 並下載新依賴項)。
  • 需要時,可以使用諸如 go get foo@v1.2.3go get foo@master(Mercurial 為 foo@default)、go get foo@e3702bed2 等命令選擇更具體的依賴項版本,或者直接編輯 go.mod

其他常用功能的簡要介紹

  • go list -m all — 檢視所有直接和間接依賴項在構建中將使用的最終版本(詳情)。
  • go list -u -m all — 檢視所有直接和間接依賴項的可用次要版本和補丁升級(詳情)。
  • go get -u ./...go get -u=patch ./...(從模組根目錄)— 將所有直接和間接依賴項更新到最新的次要版本或補丁升級(忽略預釋出版本)(詳情)。
  • go build ./...go test ./...(從模組根目錄)— 構建或測試模組中的所有包(詳情)。
  • go mod tidy — 從 go.mod 中修剪不再需要的依賴項,並新增其他作業系統、架構和構建標籤組合所需的任何依賴項(詳情)。
  • replace 指令或 gohack — 使用依賴項的分支、本地副本或確切版本(詳情)。
  • go mod vendor — 建立 vendor 目錄的可選步驟(詳情)。

閱讀完接下來關於“新概念”的四個部分後,您將掌握足夠的資訊,可以開始使用模組進行大多數專案。檢視上面的目錄(包括其中的常見問題解答簡短描述)也很有用,以熟悉更詳細的主題列表。

新概念

這些部分對主要新概念進行了高階介紹。有關更多詳細資訊和原理,請參閱 Russ Cox 描述設計理念的 40 分鐘介紹影片官方提案文件,或更詳細的初始 vgo 部落格系列

模組

模組是相關 Go 包的集合,它們作為單個單元進行版本控制。

模組記錄精確的依賴項需求並建立可重現的構建。

通常,一個版本控制倉庫只包含一個在倉庫根目錄中定義的模組。(單個倉庫支援多個模組,但通常這會導致比每個倉庫單個模組更多的持續工作)。

總結倉庫、模組和包之間的關係

  • 一個倉庫包含一個或多個 Go 模組。
  • 每個模組包含一個或多個 Go 包。
  • 每個包由一個目錄中的一個或多個 Go 原始檔組成。

模組必須按照 semver 進行語義版本控制,通常採用 v(major).(minor).(patch) 的形式,例如 v0.1.0v1.2.3v1.5.0-rc.1。開頭的 v 是必需的。如果使用 Git,請用其版本標記已釋出的提交。公共和私有模組倉庫和代理正在變得可用(請參閱下面的常見問題解答)。

go.mod

模組由一個 Go 原始檔樹定義,並在該樹的根目錄中包含一個 go.mod 檔案。模組原始碼可以位於 GOPATH 之外。有四個指令:modulerequirereplaceexclude

以下是定義模組 github.com/my/thinggo.mod 檔案示例

module github.com/my/thing

require (
    github.com/some/dependency v1.2.3
    github.com/another/dependency/v4 v4.0.0
)

模組透過 module 指令在 go.mod 中宣告其身份,該指令提供了“模組路徑”。模組中所有包的匯入路徑都以模組路徑作為公共字首。模組路徑和從 go.mod 到包目錄的相對路徑共同決定了包的匯入路徑。

例如,如果您正在為一個倉庫 github.com/user/mymod 建立一個模組,該模組將包含兩個匯入路徑為 github.com/user/mymod/foogithub.com/user/mymod/bar 的包,那麼您的 go.mod 檔案中的第一行通常會將您的模組路徑宣告為 module github.com/user/mymod,並且相應的磁碟結構可能是

mymod
|-- bar
|   `-- bar.go
|-- foo
|   `-- foo.go
`-- go.mod

在 Go 原始碼中,包使用包括模組路徑在內的完整路徑匯入。例如,如果上面示例中,我們在 go.mod 中宣告模組身份為 module github.com/user/mymod,則使用者可以執行

import "github.com/user/mymod/bar"

這從模組 github.com/user/mymod 匯入了包 bar

excludereplace 指令僅作用於當前(“主”)模組。在構建主模組時,主模組以外的模組中的 excludereplace 指令將被忽略。因此,replaceexclude 語句允許主模組完全控制其自身的構建,而無需受制於依賴項的完全控制。(有關何時使用 replace 指令的討論,請參閱下面的常見問題解答)。

版本選擇

如果您向原始碼添加了新匯入,但尚未被 go.mod 中的 require 覆蓋,大多數 go 命令(如“go build”和“go test”)將自動查詢正確的模組,並將該新直接依賴項的“最高”版本作為 require 指令新增到您的模組的 go.mod 中。例如,如果您的新匯入對應於依賴項 M,其最新標記釋出版本為 v1.2.3,您的模組的 go.mod 最終將包含 require M v1.2.3,這表示模組 M 是一個依賴項,允許版本 >= v1.2.3(且 < v2,因為 v2 被認為與 v1 不相容)。

“最小版本選擇”演算法用於選擇構建中使用的所有模組的版本。對於構建中的每個模組,透過最小版本選擇選擇的版本始終是主模組或其依賴項中 require 指令明確列出的版本中語義“最高”的版本。

舉例來說,如果您的模組依賴於模組 A,該模組包含 require D v1.0.0,並且您的模組還依賴於模組 B,該模組包含 require D v1.1.1,那麼最小版本選擇將選擇 D 的 v1.1.1 以包含在構建中(因為它是在 require 中列出的最高版本)。即使 D 的 v1.2.0 在稍後某個時間變得可用,這種 v1.1.1 的選擇仍保持一致。這是一個模組系統如何提供 100% 可重現構建的示例。準備就緒後,模組作者或使用者可以選擇升級到 D 的最新可用版本,或為 D 選擇一個明確的版本。

有關最小版本選擇演算法的簡要原理和概述,請參閱官方提案的“高保真構建”部分,或參閱更詳細的 vgo 部落格系列

要檢視選定的模組版本列表(包括間接依賴項),請使用 go list -m all

另請參閱下面的“如何升級和降級依賴項”部分以及下面的“如何將版本標記為不相容?”常見問題解答。

語義匯入版本控制

多年來,官方 Go 常見問題解答中包含了關於包版本控制的以下建議

“用於公共用途的包在演進時應儘量保持向後相容性。Go 1 相容性指南在這方面是一個很好的參考:不要刪除匯出的名稱,鼓勵使用帶標籤的複合字面量等等。如果需要不同的功能,請新增一個新名稱,而不是更改舊名稱。如果需要完全中斷相容性,請建立具有新匯入路徑的新包。”

最後一句話尤其重要——如果您破壞了相容性,您應該更改您的包的匯入路徑。在 Go 1.11 模組中,該建議被正式化為“匯入相容性規則”

“如果一箇舊包和一個新包具有相同的匯入路徑,則新包必須與舊包向後相容。”

回想一下 semver 要求當 v1 或更高版本的包進行向後不相容的更改時,必須更改主版本。遵循匯入相容性規則和 semver 的結果稱為“語義匯入版本控制”,其中主版本包含在匯入路徑中——這確保了每當主版本因相容性中斷而增加時,匯入路徑也會隨之更改。

由於語義匯入版本控制,選擇加入 Go 模組的程式碼“必須遵守這些規則”

  • 遵循 semver。(VCS 標籤示例為 v1.2.3)。
  • 如果模組的版本為 v2 或更高版本,則模組的主版本“必須”包含在 go.mod 檔案中使用的模組路徑(例如,module github.com/my/mod/v2, require github.com/my/mod/v2 v2.0.1)和包匯入路徑(例如,import "github.com/my/mod/v2/mypkg")的末尾,作為 /vN。這包括在 go get 命令中使用的路徑(例如,go get github.com/my/mod/v2@v2.0.1。請注意,此示例中既有 /v2 也有 @v2.0.1。一種思考方式是模組名稱現在包含 /v2,因此在使用模組名稱時都要包含 /v2)。
  • 如果模組版本為 v0 或 v1,則“不要”在模組路徑或匯入路徑中包含主版本。

通常,具有不同匯入路徑的包是不同的包。例如,math/randcrypto/rand 是不同的包。如果不同的匯入路徑是由於匯入路徑中出現不同的主版本,這也是成立的。因此 example.com/my/mod/mypkgexample.com/my/mod/v2/mypkg 是不同的包,並且兩者都可以在單個構建中匯入,這除了其他好處外,還有助於解決菱形依賴問題,並且允許 v1 模組根據其 v2 替代品實現,反之亦然。

有關語義匯入版本控制的更多詳細資訊,請參閱 go 命令文件的“模組相容性和語義版本控制”部分,有關語義版本控制的更多資訊,請參閱 https://semver.org

到目前為止,本節主要關注已選擇加入模組並匯入其他模組的程式碼。然而,將主版本放入 v2+ 模組的匯入路徑可能會與舊版本的 Go 或尚未選擇加入模組的程式碼產生不相容性。為了解決這個問題,有三個重要的過渡性特殊情況或例外,與上述行為和規則不同。隨著越來越多的包選擇加入模組,這些過渡性例外將變得不那麼重要。

三個過渡性例外

  1. gopkg.in

    使用以 gopkg.in 開頭的匯入路徑(例如 gopkg.in/yaml.v1gopkg.in/yaml.v2)的現有程式碼,即使在選擇加入模組後,也可以繼續使用這些形式作為其模組路徑和匯入路徑。

  2. 匯入非模組 v2+ 包時使用“+incompatible”

    模組可以匯入一個 v2+ 包,即使該包本身尚未選擇加入模組。具有有效 v2+ semver 標籤的非模組 v2+ 包將在匯入模組的 go.mod 檔案中記錄 +incompatible 字尾。+incompatible 字尾表示即使 v2+ 包具有有效的 v2+ semver 標籤,例如 v2.0.0,該 v2+ 包尚未主動選擇加入模組,因此假定該 v2+ 包在建立時未理解語義匯入版本控制的含義以及如何在匯入路徑中使用主版本。因此,當在模組模式下操作時,go 工具會將非模組 v2+ 包視為包 v1 版本系列的不相容擴充套件,並假定該包不瞭解語義匯入版本控制,而 +incompatible 字尾表示 go 工具正在這樣做。

  3. 未啟用模組模式時的“最小模組相容性”

    為了幫助實現向後相容性,Go 1.9.7+、1.10.3+ 和 1.11 版本已經更新,以便使用這些版本構建的程式碼能夠更輕鬆地正確使用 v2+ 模組,而無需修改現有程式碼。此行為稱為“最小模組相容性”,它僅在 go 工具停用完整模組模式時生效,例如在 Go 1.11 中設定了 GO111MODULE=off,或者使用 Go 1.9.7+ 或 1.10.3+ 版本時。當依賴 Go 1.9.7+、1.10.3+ 和 1.11 中的這種“最小模組相容性”機制時,尚未選擇模組的包在匯入任何 v2+ 模組時將“不”在匯入路徑中包含主版本。相反,已選擇模組的包“必須”在匯入路徑中包含主版本才能匯入任何 v2+ 模組(以便在 go 工具在具有語義匯入版本控制完全意識的完整模組模式下執行時正確匯入 v2+ 模組)。

有關釋出 v2+ 模組所需的精確機制,請參閱下面的“釋出模組(v2 或更高版本)”部分。

如何使用模組

如何安裝和啟用模組支援

要使用模組,有兩種安裝選項

安裝後,您可以選擇以下兩種方式之一啟用模組支援

  • $GOPATH/src 樹之外的目錄中呼叫 go 命令,當前目錄或其任何父目錄中包含有效的 go.mod 檔案,並且環境變數 GO111MODULE 未設定(或顯式設定為 auto)。
  • 呼叫 go 命令時,設定 GO111MODULE=on 環境變數。

如何定義模組

為現有專案建立 go.mod

  1. 導航到模組原始碼樹的根目錄,位於 GOPATH 之外。

    $ cd <project path outside $GOPATH/src>         # e.g., cd ~/projects/hello
    

    請注意,在 GOPATH 之外,您不需要設定 GO111MODULE 來啟用模組模式。

    或者,如果您想在 GOPATH 中工作

    $ export GO111MODULE=on                         # manually active module mode
    $ cd $GOPATH/src/<project path>                 # e.g., cd $GOPATH/src/you/hello
    
  2. 建立初始模組定義並將其寫入 go.mod 檔案

    $ go mod init
    

    go mod init 通常能夠使用輔助資料(例如 VCS 元資料)自動確定適當的模組路徑,但是如果 go mod init 宣告無法自動確定模組路徑,或者您需要以其他方式覆蓋該路徑,您可以將模組路徑作為可選引數提供給 go mod init,例如

    $ go mod init github.com/my/repo
    

    請注意,如果您的依賴項包含 v2+ 模組,或者您正在初始化 v2+ 模組,那麼在執行 go mod init 之後,您可能還需要編輯您的 go.mod.go 程式碼,以按照上面的“語義匯入版本控制”部分所述,將 /vN 新增到匯入路徑和模組路徑中。即使 go mod init 自動將您的依賴項資訊從 dep 或其他依賴項管理器轉換過來,這也適用。(因此,在執行 go mod init 之後,您通常不應執行 go mod tidy,直到您已成功執行 go build ./... 或類似命令,這就是本節中所示的序列)。

如果與 go 1.21.13 或更早版本一起使用,此步驟還會從任何現有的 dep Gopkg.lock 檔案或所有九種受支援的依賴項格式中的任何一種進行轉換,新增 require 語句以匹配現有配置。

  1. 構建模組。當從模組的根目錄執行時,./... 模式匹配當前模組中的所有包。go build 將自動新增缺失或未轉換的依賴項,以滿足此特定構建呼叫的匯入

    $ go build ./...
    
  2. 測試模組,確保其按配置與選定版本相容。

    $ go test ./...
    
  3. (可選)執行模組的測試以及所有直接和間接依賴項的測試,以檢查不相容性

    $ go test all
    

在釋出版本之前,請參閱下面的“如何準備釋出”部分。

有關所有這些主題的更多資訊,官方模組文件的主要入口點可在 golang.org 上找到

如何升級和降級依賴項

日常依賴項的升級和降級應使用“go get”完成,它會自動更新 go.mod 檔案。或者,您可以直接編輯 go.mod

此外,諸如“go build”、“go test”甚至“go list”之類的 go 命令將根據需要自動新增新依賴項以滿足匯入(更新 go.mod 並下載新依賴項)。

要將依賴項升級到最新版本

go get example.com/package

要將依賴項“及其所有依賴項”升級到最新版本

go get -u example.com/package

檢視所有直接和間接依賴項的可用次要版本和補丁升級

go list -u -m all

要“僅”檢視直接依賴項的可用次要版本和補丁升級,請執行

go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all 2> /dev/null

要將當前模組的所有直接和間接依賴項升級到最新版本,可以從模組根目錄執行以下命令

  • go get -u ./... 以使用最新的“次要版本或補丁”釋出(並新增 -t 以同時升級測試依賴項)
  • go get -u=patch ./... 以使用最新的“補丁”釋出(並新增 -t 以同時升級測試依賴項)

go get foo 更新到 foo 的最新版本。go get foo 等同於 go get foo@latest — 換句話說,如果未指定 @ 版本,則 @latest 是預設值。

在本節中,“最新”是指帶有 semver 標籤的最新版本,如果沒有任何 semver 標籤,則為最新已知提交。除非倉庫上沒有其他 semver 標籤,否則預釋出標籤不會被選為“最新”(詳情)。

一個常見的誤解是認為 go get -u foo 只獲取 foo 的最新版本。實際上,go get -u foogo get -u foo@latest 中的 -u 意味著“也”獲取 foo 的所有直接和間接依賴項的最新版本。升級 foo 時常見的起點是執行不帶 -ugo get foogo get foo@latest(在一切正常後,考慮 go get -u=patch foogo get -u=patchgo get -u foogo get -u)。

要升級或降級到更具體的版本,“go get”允許透過在包引數中新增 @version 字尾或“模組查詢”來覆蓋版本選擇,例如 go get foo@v1.6.2go get foo@e3702bed2go get foo@'<v1.6.2'

使用分支名稱(例如 go get foo@master (Mercurial 為 foo@default))是獲取最新提交的一種方式,無論其是否具有 semver 標籤。

通常,不解析為 semver 標籤的模組查詢將以偽版本的形式記錄在 go.mod 檔案中。

有關此處主題的更多資訊,請參閱 go 命令文件的“模組感知 go get”“模組查詢”部分。

模組能夠使用尚未選擇模組的包,包括在 go.mod 中記錄任何可用的 semver 標籤,並使用這些 semver 標籤進行升級或降級。模組還可以使用尚未具有任何正確 semver 標籤的包(在這種情況下,它們將使用 go.mod 中的偽版本記錄)。

升級或降級任何依賴項後,您可能希望再次執行構建中所有包的測試(包括直接和間接依賴項)以檢查不相容性

$ go test all

如何準備釋出

釋出模組(所有版本)

建立模組釋出版本的最佳實踐預計將作為初始模組實驗的一部分出現。其中許多最終可能會被未來的 “go release”工具自動化。

當前建議在釋出前考慮的一些最佳實踐

  • 執行 go mod tidy 以可能修剪任何多餘的需求(如此處所述),並確保當前的 go.mod 反映了所有可能的構建標籤/作業系統/架構組合(如此處所述)。

    • 相比之下,go buildgo test 等其他命令不會從 go.mod 中刪除不再需要的依賴項,只會根據當前構建呼叫的標籤/作業系統/架構更新 go.mod
  • 執行 go test all 來測試您的模組(包括執行您的直接和間接依賴項的測試),以此驗證當前選定的包版本是相容的。

    • 可能的版本組合數量隨模組數量呈指數增長,因此通常情況下,您不能指望您的依賴項已經針對其依賴項的所有可能組合進行了測試。
    • 作為模組工作的一部分,go test all 已經被重新定義以更有用:包括當前模組中的所有包,以及它們透過一個或多個匯入所依賴的所有包,同時排除在當前模組中無關緊要的包。
  • 確保您的 go.sum 檔案與 go.mod 檔案一起提交。有關更多詳細資訊和原理,請參閱下面的常見問題解答

釋出模組(v2 或更高版本)

如果您要釋出 v2 或更高版本模組,請首先回顧上面“語義匯入版本控制”部分中的討論,其中包括為什麼主版本包含在 v2+ 模組的模組路徑和匯入路徑中,以及 Go 1.9.7+ 和 1.10.3+ 版本如何更新以簡化該過渡。

請注意,如果您是首次為在採用模組之前已標記為 v2.0.0 或更高版本的現有倉庫或包採用模組,那麼推薦的最佳實踐是在首次採用模組時增加主版本。例如,如果您是 foo 的作者,並且 foo 倉庫的最新標籤是 v2.2.2,而 foo 尚未採用模組,那麼最佳實踐是使用 v3.0.0 作為 foo 首次採用模組的版本(因此也是 foo 首次包含 go.mod 檔案的版本)。在這種情況下增加主版本為 foo 的消費者提供了更高的清晰度,允許在需要時在 foo 的 v2 系列上進行額外的非模組補丁或次要釋出,併為 foo 的基於模組的消費者提供了強烈訊號,即如果您執行 import "foo" 和相應的 require foo v2.2.2+incompatible,與 import "foo/v3" 和相應的 require foo/v3 v3.0.0 相比,會產生不同的主版本。(請注意,此關於首次採用模組時增加主版本的建議“不”適用於其最新版本為 v0.x.x 或 v1.x.x 的現有倉庫或包)。

有兩種替代機制可以釋出 v2 或更高版本模組。請注意,無論使用哪種技術,當模組作者推送新標籤時,新的模組釋出都會對消費者可用。以建立 v3.0.0 釋出為例,兩種選項是

  1. 主分支:更新 go.mod 檔案,使其 module 指令中的模組路徑末尾包含 /v3(例如,module github.com/my/module/v3)。更新模組內的匯入語句,使其也使用 /v3(例如,import "github.com/my/module/v3/mypkg")。使用 v3.0.0 標記釋出。

    • Go 1.9.7+、1.10.3+ 和 1.11 版本能夠正確使用和構建使用此方法建立的 v2+ 模組,而無需更新尚未選擇加入模組的消費者程式碼(如上述“語義匯入版本控制”部分所述)。
    • 社群工具 github.com/marwan-at-work/mod 有助於自動化此過程。有關概述,請參閱倉庫或下面的社群工具常見問題解答
    • 為避免與此方法混淆,請考慮將模組的 v3.*.* 提交放到單獨的 v3 分支上。
    • 注意:不需要建立新分支。如果您之前一直在 master 上釋出,並且更願意在 master 上標記 v3.0.0,那也是一個可行的選擇。(但是,請注意,在 master 中引入不相容的 API 更改可能會給非模組使用者帶來問題,他們會發出 go get -u,因為 go 工具在 Go 1.11 之前或在 Go 1.11+ 中模組模式未啟用時,不瞭解 semver)。
    • 現有依賴項管理解決方案(如 dep)目前在消費以這種方式建立的 v2+ 模組時可能會遇到問題。例如,請參見 dep#1962
  2. 主子目錄:建立一個新的 v3 子目錄(例如,my/module/v3),並在該子目錄中放置一個新的 go.mod 檔案。模組路徑必須以 /v3 結尾。將程式碼複製或移動到 v3 子目錄中。更新模組內的匯入語句,使其也使用 /v3(例如,import "github.com/my/module/v3/mypkg")。使用 v3.0.0 標記釋出。

    • 這提供了更大的向後相容性。特別是,Go 1.9.7 和 1.10.3 之前的版本也能夠正確使用和構建透過此方法建立的 v2+ 模組。
    • 這裡更復雜的方法可以利用類型別名(Go 1.9 中引入)和存在於不同子目錄中的主要版本之間的轉發墊片。這可以提供額外的相容性,並允許一個主要版本根據另一個主要版本實現,但這會給模組作者帶來更多工作。一個正在進行中的自動化工具是 goforward。請參閱此處以獲取更多詳細資訊和原理,以及 goforward 的一個功能性初始版本。
    • 預先存在的依賴管理解決方案(如 dep)應該能夠使用這種方式建立的 v2+ 模組。

有關這些替代方案的更深入討論,請參閱 https://research.swtch.com/vgo-module

釋出版本

新模組版本可以透過將標籤推送到包含模組原始碼的倉庫來發布。標籤由兩個字串拼接而成:一個“字首”和一個“版本”。

“版本”是釋出的語義匯入版本。它應該根據語義匯入版本控制的規則進行選擇。

“字首”指示模組在倉庫中的定義位置。如果模組定義在倉庫的根目錄,則字首為空,標籤就是版本。然而,在多模組倉庫中,字首區分不同模組的版本。字首是倉庫中定義模組的目錄。如果倉庫遵循上述主要子目錄模式,則字首不包含主要版本字尾。

例如,假設我們有一個模組 example.com/repo/sub/v2,我們想要釋出版本 v2.1.6。倉庫根目錄對應於 example.com/repo,模組定義在倉庫中的 sub/v2/go.mod 中。此模組的字首是 sub/。此釋出的完整標籤應該是 sub/v2.1.6

遷移到模組

本節旨在簡要列出遷移到模組時要做的主要決策,並列出其他與遷移相關的主題。詳細資訊通常會引用其他部分。

本材料主要基於模組實驗中社群出現的最佳實踐;因此,這是一個正在進行中的部分,將隨著社群經驗的增長而改進。

總結

  • 模組系統的設計旨在允許整體 Go 生態系統中的不同包以不同的速率選擇加入。
  • 版本已達到 v2 或更高的包有更多的遷移考慮,主要原因是語義匯入版本控制的影響。
  • 新包和 v0 或 v1 的包在採用模組時考慮因素要少得多。
  • Go 1.11 定義的模組可供舊版 Go 使用(儘管確切的 Go 版本取決於主模組及其依賴項所使用的策略,如下所述)。

遷移主題

從之前的依賴管理器自動遷移

  • 在 go 1.21.13 或更早版本中,go mod init 會自動將 dep、glide、govendor、godep 和其他 5 個現有依賴管理器所需的資訊轉換為 go.mod 檔案,生成等效的構建。
  • 如果您正在建立 v2+ 模組,請確保您的 module 指令在轉換後的 go.mod 中包含適當的 /vN(例如,module foo/v3)。
  • 請注意,如果您正在匯入 v2+ 模組,那麼在初始轉換後,您可能需要進行一些手動調整,以便將 /vN 新增到 go mod init 在從以前的依賴項管理器轉換後生成的 require 語句中。有關更多詳細資訊,請參閱上面的“如何定義模組”部分。
  • 此外,go mod init 不會編輯您的 .go 程式碼來新增任何必需的 /vN 到匯入語句中。有關必需步驟,包括一些自動化轉換的社群工具選項,請參閱上面的“語義匯入版本控制”“釋出模組(v2 或更高版本)”部分。

向舊版 Go 和非模組消費者提供依賴項資訊

  • 舊版 Go 知道如何使用 go mod vendor 建立的 vendor 目錄,Go 1.11 和 1.12+ 在停用模組模式時也如此。因此,vendoring 是一種模組向不完全理解模組的舊版 Go 以及尚未啟用模組的消費者提供依賴項的方式。有關更多詳細資訊,請參閱vendoring 常見問題解答go 命令文件

更新現有安裝說明

  • 在模組之前,安裝說明通常包含 go get -u foo。如果您正在釋出模組 foo,請考慮為基於模組的消費者在說明中刪除 -u
    • -u 要求 go 工具升級 foo 的所有直接和間接依賴項。
    • 模組使用者可能會在以後選擇執行 go get -u foo,但是如果 -u 不是初始安裝說明的一部分,則“高保真構建”會有更多好處。有關更多詳細資訊,請參閱“如何升級和降級依賴項”
    • go get -u foo 仍然有效,並且仍然是安裝說明的有效選擇。
  • 此外,對於基於模組的消費者來說,go get foo 並非嚴格必要。
    • 只需新增匯入語句 import "foo" 即可。(隨後的命令,如 go buildgo test,將自動下載 foo 並根據需要更新 go.mod)。
  • 基於模組的消費者預設不會使用 vendor 目錄。
    • 當在 go 工具中啟用模組模式時,使用模組時並非嚴格需要 vendor(鑑於 go.mod 中包含的資訊和 go.sum 中的加密校驗和),但一些現有的安裝說明假定 go 工具預設會使用 vendor。有關更多詳細資訊,請參閱vendoring 常見問題解答
  • 包含 go get foo/... 的安裝說明在某些情況下可能會出現問題(請參閱 #27215 中的討論)。

避免破壞現有匯入路徑

模組透過 module 指令在 go.mod 中宣告其身份,例如 module github.com/my/module。模組內的所有包都必須由任何模組感知消費者使用與模組宣告的模組路徑匹配的匯入路徑(對於根包是完全匹配,或者模組路徑作為匯入路徑的字首)進行匯入。如果匯入路徑與相應模組宣告的模組路徑不匹配,go 命令會報告 unexpected module path 錯誤。

為一套現有的包採用模組時,應注意避免破壞現有消費者使用的現有匯入路徑,除非您在採用模組時增加了主版本。

例如,如果您的現有 README 一直告訴消費者使用 import "gopkg.in/foo.v1",並且如果您隨後釋出 v1 版本並採用模組,那麼您的初始 go.mod 幾乎肯定應該讀取 module gopkg.in/foo.v1。如果您想擺脫使用 gopkg.in,那將是對您當前消費者的一個重大更改。一種方法是,如果您以後遷移到 v2,則更改為類似 module github.com/repo/foo/v2

請注意,模組路徑和匯入路徑是區分大小寫的。例如,將模組從 github.com/Sirupsen/logrus 更改為 github.com/sirupsen/logrus 對消費者來說是一個破壞性更改,即使 GitHub 自動從一個倉庫名稱轉發到新倉庫名稱。

在您採用模組後,更改 go.mod 中的模組路徑是一個破壞性更改。

總的來說,這類似於模組之前透過“匯入路徑註釋”強制執行規範匯入路徑,有時也稱為“匯入 pragma”或“匯入路徑強制”。例如,包 go.uber.org/zap 目前託管在 github.com/uber-go/zap,但使用包宣告旁邊的匯入路徑註釋,這會為使用錯誤基於 GitHub 的匯入路徑的任何模組前消費者觸發錯誤

package zap // import "go.uber.org/zap"

匯入路徑註釋已被 go.mod 檔案的 module 語句廢棄。

首次採用包含 v2+ 包的模組時增加主版本

  • 如果您在採用模組之前已經標記了 v2.0.0 或更高版本的包,那麼推薦的最佳實踐是在首次採用模組時增加主版本。例如,如果您當前版本為 v2.0.1 且尚未採用模組,那麼您將在首次採用模組時使用 v3.0.0。有關更多詳細資訊,請參閱上面的“釋出模組(v2 或更高版本)”部分。

v2+ 模組允許在單個構建中包含多個主版本

  • 如果一個模組是 v2 或更高版本,這意味著單個構建中可以包含多個主版本(例如,foofoo/v3 可能最終出現在同一個構建中)。
    • 這自然源於“具有不同匯入路徑的包是不同的包”的規則。
    • 當這種情況發生時,將會有多份包級狀態(例如,foo 的包級狀態和 foo/v3 的包級狀態),並且每個主版本都將執行其自己的 init 函式。
    • 這種方法有助於模組系統的多個方面,包括幫助解決菱形依賴問題,在大型程式碼庫中逐步遷移到新版本,以及允許將一個主版本實現為另一個主版本的墊片。
  • 請參閱 https://research.swtch.com/vgo-import 的“避免單例問題”部分或 #27514 以獲取一些相關討論。

模組消費非模組程式碼

  • 模組能夠消費尚未選擇加入模組的包,並在匯入模組的 go.mod 中記錄相應的包版本資訊。模組可以消費尚未具有任何正確語義版本標籤的包。有關更多詳細資訊,請參閱下面的常見問題解答
  • 模組還可以匯入一個尚未選擇加入模組的 v2+ 包。如果匯入的 v2+ 包具有有效的 semver 標籤,則它將以 +incompatible 字尾記錄。有關更多詳細資訊,請參閱下面的常見問題解答

非模組程式碼消費模組

  • 非模組程式碼消費 v0 和 v1 模組:

    • 尚未選擇加入模組的程式碼可以消費和構建 v0 和 v1 模組(不要求與使用的 Go 版本相關)。
  • 非模組程式碼消費 v2+ 模組:

現有 v2+ 包作者的策略

對於考慮選擇加入模組的現有 v2+ 包的作者,總結替代方法的一種方式是選擇三種頂級策略。每種選擇都有後續的決策和變體(如上所述)。這些替代的頂級策略是

  1. 要求客戶端使用 Go 1.9.7+、1.10.3+ 或 1.11+ 版本.

    該方法使用“主分支”方法,並依賴於已回溯到 1.9.7 和 1.10.3 的“最小模組感知”。有關更多詳細資訊,請參閱上述“語義匯入版本控制”“釋出模組(v2 或更高版本)”部分。

  2. 允許客戶端使用甚至更舊的 Go 版本,例如 Go 1.8.

    此方法使用“主要子目錄”方法,並涉及建立子目錄,例如 /v2/v3。有關更多詳細資訊,請參閱上面的“語義匯入版本控制”“釋出模組(v2 或更高版本)”部分。

  3. 推遲選擇加入模組.

    在此策略中,無論客戶端程式碼是否選擇加入模組,一切都將繼續正常工作。隨著時間的推移,Go 1.9.7+、1.10.3+ 和 1.11+ 版本將存在更長的時間,未來某個時候,要求 Go 1.9.7+/1.10.3+/1.11+ 版本會更自然或對客戶端更友好,屆時您可以實施上述策略 1(要求 Go 1.9.7+、1.10.3+ 或 1.11+ 版本),甚至可以實施上述策略 2(但是,如果您最終要選擇上述策略 2 以支援 Go 1.8 等舊版本,那麼現在就可以這樣做)。

其他資源

文件與提案

  • 官方文件
    • golang.org 上最新的模組 HTML 文件
    • 執行 go help modules 以獲取有關模組的更多資訊。(這是透過 go help 訪問模組主題的主要入口點)
    • 執行 go help mod 以獲取有關 go mod 命令的更多資訊。
    • 執行 go help module-get 以獲取有關在模組感知模式下 go get 行為的更多資訊。
    • 執行 go help goproxy 以獲取有關模組代理的更多資訊,包括透過 file:/// URL 實現的純檔案選項。
  • Russ Cox 關於 vgo 的初始“Go 與版本控制”系列博文(首次釋出於 2018 年 2 月 20 日)
  • 官方 golang.org 部落格文章介紹該提案 (2018 年 3 月 26 日)
    • 這提供了比完整 vgo 部落格系列更簡潔的提案概述,以及一些提案背後的歷史和過程
  • 官方版本化 Go 模組提案(上次更新日期為 2018 年 3 月 20 日)

入門材料

額外資料

自最初 vgo 提案以來的變更

作為提案、原型和測試版流程的一部分,社群總共建立了 400 多個問題。請繼續提供反饋。

以下是一些主要變更和改進的部分列表,其中幾乎所有都主要基於社群反饋

  • 保留了頂級供應商支援,而不是 vgo 基於的構建完全忽略供應商目錄(討論CL
  • 回溯了最小模組感知,以允許舊版 Go 1.9.7+ 和 1.10.3+ 更輕鬆地使用 v2+ 專案的模組(討論CL
  • 預設允許 vgo 對尚未擁有 go.mod 的現有包使用 v2+ 標籤(相關行為的最新更新此處描述)
  • 透過命令 go get -u=patch 添加了支援,以將所有傳遞性依賴項更新到同一次要版本上可用的最新補丁級別版本(討論文件
  • 透過環境變數進行額外控制(例如,#26585 中的 GOFLAGS,CL
  • 對是否允許更新 go.mod、如何使用 vendor 目錄以及是否允許網路訪問進行更細粒度的控制(例如,-mod=readonly、-mod=vendor、GOPROXY=off;相關CL 用於最近的更改)
  • 添加了更靈活的替換指令(CL
  • 增加了更多查詢模組的方式(供人閱讀,也為了更好地整合編輯器/IDE)
  • Go CLI 的使用者體驗一直在根據目前的經驗進行改進(例如,#26581CL
  • 透過 go mod download 為 CI 或 Docker 構建等用例提供預熱快取的額外支援(#26610
  • 最有可能:更好地支援將特定版本的程式安裝到 GOBIN(#24250

GitHub 問題

常見問題解答

如何將版本標記為不相容?

require 指令允許任何模組宣告它應該使用依賴項 D 的版本 >= x.y.z 構建(這可能是由於與模組 D 的版本 < x.y.z 不相容而指定的)。經驗資料表明,這是 depcargo 中使用的主要約束形式。此外,構建中的頂級模組可以 exclude 特定版本的依賴項或 replace 其他模組使用不同的程式碼。有關更多詳細資訊和原理,請參閱完整提案。

版本化模組提案的關鍵目標之一是為工具和開發者新增關於 Go 程式碼版本的通用詞彙和語義。這為未來宣告其他形式的不相容性奠定了基礎,例如可能

  • 在初始 vgo 部落格系列中描述了宣告已棄用的版本
  • 在提案過程中,例如在此處討論的,在外部系統中宣告模組之間的成對不相容性
  • 在釋出後宣告模組的成對不相容版本或不安全版本。例如,請參見#24031#26829 中正在進行的討論

何時使用舊行為與新的基於模組的行為?

通常,Go 1.11 中的模組是可選的,因此預設情況下保留舊行為。

總結何時獲得舊的 1.10 現狀行為與新的選擇加入模組化行為

  • GOPATH 內部 — 預設為舊的 1.10 行為(忽略模組)
  • GOPATH 之外,但在包含 go.mod 的檔案樹內 — 預設為模組行為
  • GO111MODULE 環境變數
    • 未設定或 auto — 上述預設行為
    • on — 強制開啟模組支援,無論目錄位置如何
    • off — 強制關閉模組支援,無論目錄位置如何

為什麼透過 go get 安裝工具會失敗並顯示錯誤 cannot find main module

當您設定 GO111MODULE=on,但在執行 go get 時不在包含 go.mod 的檔案樹中時,就會發生這種情況。

最簡單的解決方案是取消設定 GO111MODULE(或等效地顯式設定為 GO111MODULE=auto),這樣可以避免此錯誤。

回想一下模組存在的主要原因之一是記錄精確的依賴資訊。此依賴資訊寫入您的當前 go.mod。如果您不在包含 go.mod 的檔案樹中,但您已透過設定 GO111MODULE=on 告訴 go get 命令在模組模式下操作,那麼執行 go get 將導致錯誤 cannot find main module,因為沒有可用的 go.mod 來記錄依賴資訊。

解決方案包括

  1. GO111MODULE 保持未設定(預設值,或顯式設定為 GO111MODULE=auto),這會帶來更友好的行為。當您不在模組內時,這將為您提供 Go 1.10 行為,從而避免 go get 報告 cannot find main module

  2. 保持 GO111MODULE=on,但根據需要在 go get 期間臨時停用模組並啟用 Go 1.10 行為,例如透過 GO111MODULE=off go get example.com/cmd。這可以轉換為簡單的指令碼或 shell 別名,例如 alias oldget='GO111MODULE=off go get'

  3. 建立一個臨時 go.mod 檔案,然後將其丟棄。這已由 @rogpeppe 透過一個簡單的 shell 指令碼實現自動化。此指令碼允許透過 vgoget example.com/cmd[@version] 選擇性地提供版本資訊。(這可以作為避免錯誤 cannot use path@version syntax in GOPATH mode 的解決方案)。

  4. gobin 是一個模組感知命令,用於安裝和執行主包。預設情況下,gobin 無需手動建立模組即可安裝/執行主包,但透過 -m 標誌可以告知它使用現有模組解析依賴項。請參閱 gobin READMEFAQ 以獲取詳細資訊和更多用例。

  5. 建立一個 go.mod 檔案,用於跟蹤您全域性安裝的工具,例如在 ~/global-tools/go.mod 中,並在執行任何全域性安裝工具的 go getgo install 之前 cd 到該目錄。

  6. 為每個工具在單獨的目錄中建立 go.mod,例如 ~/tools/gorename/go.mod~/tools/goimports/go.mod,並在執行該工具的 go getgo install 之前 cd 到相應的目錄。

目前的這個限制將得到解決。然而,主要問題是模組目前是可選的,完整的解決方案可能要等到 GO111MODULE=on 成為預設行為。有關更多討論,包括此評論,請參閱 #24250

這顯然最終是可行的。我不確定的是,這在版本方面具體做了什麼:它是否建立了一個臨時模組根和 go.mod,執行安裝,然後將其丟棄?可能。但我並不完全確定,目前我不想透過讓 vgo 在 go.mod 樹之外執行操作來混淆大家。當然,最終的 go 命令整合必須支援這一點。

本常見問題解答一直在討論跟蹤全域性安裝的工具。

如果您想跟蹤特定模組所需的工具,請參閱下一個常見問題解答。

如何跟蹤模組的工具依賴項?

如果您

  • 在處理模組時希望使用基於 go 的工具(例如 stringer),並且
  • 希望確保每個人都使用該工具的相同版本,同時在模組的 go.mod 檔案中跟蹤該工具的版本

那麼在 Go 1.24 及更高版本中,您可以向 go.mod 新增一個 tool 指令

go 1.24

...

tool golang.org/x/tools/cmd/stringer

在 Go 1.24 之前,推薦的方法是在模組中新增一個 tools.go 檔案,其中包含所需工具的匯入語句(例如 import _ "golang.org/x/tools/cmd/stringer"),以及一個 //go:build tools 構建約束。匯入語句允許 go 命令在模組的 go.mod 中精確記錄工具的版本資訊,而 //go:build tools 構建約束可以防止您的正常構建實際匯入您的工具。

有關如何執行此操作的具體示例,請參閱此 “Go Modules by Example” 演練

您還可以(自 Go 1.16 起)使用 go install tool@version 來安裝特定版本,或者(自 Go 1.17 起)使用 go run tool@version 來在不安裝的情況下執行工具,這在 #42088#40276 中實現,這可以消除對 tools.go 的需求。

IDE、編輯器和 goimports、gorename 等標準工具中模組支援的現狀如何?

對模組的支援正在進入編輯器和 IDE。

例如

  • GoLand:目前完全支援 GOPATH 內部和外部的模組,包括 此處 描述的完成、語法分析、重構、導航。
  • VS Code:工作已完成,MS 推薦模組而非 GOPATH,之前的跟蹤問題 (#1532) 已關閉。文件可在 VS Code 模組儲存庫 中找到。
  • Atom with go-plus:跟蹤問題是 #761
  • vim with vim-go:對 go.mod 的語法高亮和格式化的初始支援已經 落地。更廣泛的支援在 #1906 中跟蹤。
  • emacs with go-mode.el:跟蹤問題在 #237 中。

goimports、guru、gorename 等其他工具的狀況正在一個總括問題 #24661 中跟蹤。請參閱該總括問題以獲取最新狀態。

某些特定工具的跟蹤問題包括

  • gocode:跟蹤問題在 mdempsky/gocode/#46 中。請注意,nsf/gocode 建議人們從 nsf/gocode 遷移到 mdempsky/gocode
  • go-tools(dominikh 的工具,如 staticcheck、megacheck、gosimple):示例跟蹤問題 dominikh/go-tools#328

通常,即使您的編輯器、IDE 或其他工具尚未支援模組,如果您在 GOPATH 中使用模組並執行 go mod vendor(因為這樣可以透過 GOPATH 獲取正確的依賴項),它們的許多功能也應該與模組配合使用。

完整的解決方案是將載入包的程式從 go/build 移至 golang.org/x/tools/go/packages,後者能夠以模組感知的方式定位包。這很可能最終會成為 go/packages

常見問題解答 — 附加控制

有哪些社群工具可以用於模組?

社群正在模組之上構建工具。例如

  • github.com/rogpeppe/gohack
    • 一個新的社群工具,用於自動化並極大簡化 replace 和多模組工作流,包括允許您輕鬆修改您的一個依賴項
    • 例如,gohack example.com/some/dependency 自動克隆相應的儲存庫並向您的 go.mod 新增必要的 replace 指令
    • 使用 gohack undo 刪除所有 gohack 替換語句
    • 該專案正在繼續擴充套件,以簡化其他模組相關工作流
  • github.com/marwan-at-work/mod
    • 用於自動升級/降級模組主要版本的命令列工具
    • 自動調整 go.mod 檔案和 Go 原始碼中相關的匯入語句
    • 有助於升級,或首次使用 v2+ 包選擇模組時
  • github.com/akyoto/mgit
    • 讓您檢視和控制所有本地專案的 semver 標籤
    • 顯示未標記的提交,並允許您一次性標記它們(mgit -tag +0.0.1
  • github.com/goware/modvendor
    • 幫助將其他檔案複製到 vendor/ 資料夾,例如 shell 指令碼、.cpp 檔案、.proto 檔案等。
  • github.com/psampaz/go-mod-outdated
    • 以使用者友好的方式顯示過時的依賴項
    • 提供一種篩選間接依賴項和沒有更新的依賴項的方法
    • 提供一種在依賴項過時時中斷 CI 管道的方法
  • github.com/oligot/go-mod-upgrade
    • 互動式更新過時的 Go 依賴項

何時應該使用 replace 指令?

上面的“go.mod”概念部分 所述,replace 指令在頂層 go.mod 中提供額外控制,用於實際滿足 Go 原始碼或 go.mod 檔案中找到的依賴項,而在主模組之外的模組中的 replace 指令在構建主模組時將被忽略。

replace 指令允許您提供另一個匯入路徑,該路徑可能是位於 VCS(GitHub 或其他地方)中的另一個模組,或者位於您本地檔案系統中具有相對或絕對檔案路徑的模組。來自 replace 指令的新匯入路徑無需更新實際原始碼中的匯入路徑即可使用。

replace 允許頂級模組控制依賴項使用的確切版本,例如

  • replace example.com/some/dependency => example.com/some/dependency v1.2.3

replace 還允許使用分叉依賴項,例如

  • replace example.com/some/dependency => example.com/some/dependency-fork v1.2.3

您還可以引用分支,例如

  • replace example.com/some/dependency => example.com/some/dependency-fork master

一個示例用例是,如果您需要在依賴項中修復或調查某些內容,您可以有一個本地分支並在您的頂級 go.mod 中新增類似以下內容

  • replace example.com/original/import/path => /your/forked/import/path

replace 還可以用於告知 go 工具在多模組專案中模組的相對或絕對磁碟位置,例如

  • replace example.com/project/foo => ../foo

注意:如果 replace 指令的右側是一個檔案系統路徑,則目標必須在該位置有一個 go.mod 檔案。如果 go.mod 檔案不存在,可以使用 go mod init 建立一個。

通常,您可以選擇在 replace 指令的 => 左側指定一個版本,但如果您省略該版本(例如,如上述所有 replace 示例中所示),它通常對更改不那麼敏感。

每個直接依賴項的 replace 指令都需要一個 require 指令。當從檔案系統路徑替換依賴項時,相應的 require 指令的版本基本上被忽略;在這種情況下,偽版本 v0.0.0 是一個很好的選擇,可以使之清晰,例如 require example.com/module v0.0.0

您可以透過執行 go list -m all 來確認您正在獲取預期的版本,該命令顯示您的構建中將使用的實際最終版本,包括考慮 replace 語句。

有關更多詳細資訊,請參閱 “go mod edit” 文件

github.com/rogpeppe/gohack 使這些型別的工作流變得更加容易,特別是如果您的目標是模組依賴項的可變檢出。有關概述,請參閱 儲存庫 或緊接的常見問題解答。

有關使用 replace 完全在 VCS 外部工作的詳細資訊,請參閱下一個常見問題解答。

我能否完全脫離 VCS 在本地檔案系統上工作?

是的。不需要版本控制系統。

如果您想一次編輯一個模組而不在版本控制系統之外(並且您總共只有一個模組,或者其他模組位於版本控制系統中),這非常簡單。在這種情況下,您可以將包含單個 go.mod 的檔案樹放置在方便的位置。您的 go buildgo test 和類似命令將起作用,即使您的單個模組在版本控制系統之外(無需在 go.mod 中使用任何 replace)。

如果您希望在本地磁碟上同時編輯多個相互關聯的模組,那麼 replace 指令是一種方法。這是一個示例 go.mod,它使用帶有相對路徑的 replacehello 模組指向 goodbye 模組的磁碟位置(不依賴任何版本控制系統)

module example.com/me/hello

require (
  example.com/me/goodbye v0.0.0
)

replace example.com/me/goodbye => ../goodbye

帖子 中顯示了一個可執行的小示例。

如何在模組中使用 vendoring?vendoring 會消失嗎?

最初的 vgo 部落格文章確實提出了完全放棄 vendoring,但來自社群的 反饋 導致保留了對 vendoring 的支援。

簡而言之,要將 vendoring 與模組一起使用

  • go mod vendor 根據 go.mod 檔案和 Go 原始碼的狀態,重置主模組的供應商目錄,以包含構建和測試模組所有包所需的所有包。
  • 預設情況下,當處於模組模式時,go build 等 go 命令會忽略 vendor 目錄。
  • -mod=vendor 標誌(例如,go build -mod=vendor)指示 go 命令使用主模組的頂級 vendor 目錄來滿足依賴項。在此模式下,go 命令因此會忽略 go.mod 中的依賴項描述,並假定 vendor 目錄包含正確的依賴項副本。請注意,僅使用主模組的頂級 vendor 目錄;其他位置的 vendor 目錄仍將被忽略。
  • 有些人會希望透過設定 GOFLAGS=-mod=vendor 環境變數來定期選擇 vendoring。

Go 的舊版本(如 1.10)知道如何使用 go mod vendor 建立的 vendor 目錄,Go 1.11 和 1.12+ 在停用 模組模式 時也是如此。因此,vendoring 是一種模組向不完全理解模組的舊版本 Go 以及未啟用模組本身的消費者提供依賴項的方式。

如果您正在考慮使用 vendoring,值得閱讀 tip 文件的 “Modules and vendoring”“Make vendored copy of dependencies” 部分。

是否有“始終線上”的模組倉庫和企業代理?

公共託管的“始終線上”不可變模組儲存庫以及可選的私有託管代理和儲存庫正在變得可用。

例如

請注意,您不需要執行代理。相反,Go 1.11 中的 go 工具透過 GOPROXY 添加了可選的代理支援,以實現更多的企業用例(例如更大的控制權),並且更好地處理“GitHub 宕機”或人們刪除 GitHub 儲存庫等情況。

我能否控制何時更新 go.mod 以及何時 go 工具使用網路來滿足依賴項?

預設情況下,go build 等命令會根據需要訪問網路以滿足匯入。

一些團隊希望在某些時候禁止 go 工具訪問網路,或者希望對 go 工具何時更新 go.mod、如何獲取依賴項以及如何使用 vendoring 有更大的控制權。

go 工具提供了相當大的靈活性來調整或停用這些預設行為,包括透過 -mod=readonly-mod=vendorGOFLAGSGOPROXY=offGOPROXY=file:///filesystem/pathgo mod vendorgo mod download

這些選項的詳細資訊分散在官方文件中。社群嘗試對與這些行為相關的旋鈕進行綜合概述,請參閱 此處,其中包含指向官方文件的連結以獲取更多資訊。

如何在 Travis 或 CircleCI 等 CI 系統中使用模組?

最簡單的方法可能是設定環境變數 GO111MODULE=on,這應該適用於大多數 CI 系統。

但是,在 Go 1.11 中,同時啟用和停用模組在 CI 中執行測試可能很有價值,因為您的一些使用者可能尚未選擇模組。vendoring 也是一個需要考慮的話題。

以下兩篇部落格文章更具體地涵蓋了這些主題

如何下載構建特定包或測試所需的模組?

go mod download 命令(或等效的 go mod download all)下載構建列表中的所有模組(如 go list -m all 報告)。這些模組中有許多不需要構建主模組中的包,因為完整的構建列表包含其他模組的測試依賴項和工具依賴項。因此,使用 go mod download 準備的 Docker 映象可能會比必要的大。

相反,請考慮使用 go list。例如,go list ./... 將下載構建 ./... 包所需的模組(從模組根目錄執行時,主模組中的包集)。

要下載測試依賴項,請使用 go list -test ./...

預設情況下,go list 只會考慮當前平臺所需的依賴項。您可以設定 GOOSGOARCH 以使 go list 考慮其他平臺,例如,GOOS=linux GOARCH=amd64 go list ./...-tags 標誌也可以用於選擇具有特定構建標籤的包。

當實現延遲模組載入時(請參閱 #36460),這種技術在將來可能不那麼必要,因為模組模式 all 將包含更少的模組。

常見問題解答 — go.mod 和 go.sum

為什麼“go mod tidy”會在我的“go.mod”中記錄間接和測試依賴項?

模組系統在您的 go.mod 中記錄精確的依賴項需求。(有關更多詳細資訊,請參閱上面的 go.mod 概念 部分或 go.mod tip 文件)。

go mod tidy 更新您當前的 go.mod 以包含模組中測試所需的依賴項 — 如果測試失敗,我們必須知道使用了哪些依賴項才能重現失敗。

go mod tidy 還確保您當前的 go.mod 反映了所有可能的 OS、架構和構建標籤組合的依賴項要求(如 此處 所述)。相比之下,go buildgo test 等其他命令只更新 go.mod 以提供當前 GOOSGOARCH 和構建標籤下請求包匯入的包(這也是 go mod tidy 可能新增 go build 或類似命令未新增的要求的原因之一)。

如果您的模組的依賴項本身沒有 go.mod(例如,因為該依賴項本身尚未選擇模組),或者如果其 go.mod 檔案缺少其一個或多個依賴項(例如,因為模組作者沒有執行 go mod tidy),則缺少的傳遞依賴項將新增到您的模組的要求中,並附帶 // indirect 註釋,以表明該依賴項並非來自您的模組中的直接匯入。

請注意,這也意味著您的直接或間接依賴項中任何缺少的測試依賴項也將記錄在您的 go.mod 中。(一個重要的示例:go test all 執行您的模組的所有直接和間接依賴項的測試,這是驗證您當前版本組合是否協同工作的一種方式。如果當您執行 go test all 時,您的一個依賴項中的測試失敗,則記錄一套完整的測試依賴項資訊非常重要,以便您擁有可重現的 go test all 行為)。

您的 go.mod 檔案中可能存在 // indirect 依賴項的另一個原因是,您已將您的一個間接依賴項升級(或降級)到超出您的直接依賴項所需的範圍,例如如果您運行了 go get -ugo get foo@1.2.3。go 工具需要一個地方來記錄這些新版本,它會在您的 go.mod 檔案中執行此操作(並且它不會深入到您的依賴項中修改它們go.mod 檔案)。

通常,上述行為是模組透過記錄精確的依賴項資訊提供 100% 可重現構建和測試的方式的一部分。

如果您好奇為什麼特定模組會出現在您的 go.mod 中,您可以執行 go mod why -m <module>回答這個問題。其他用於檢查需求和版本的有用工具包括 go mod graphgo list -m all

“go.sum”是鎖檔案嗎?為什麼“go.sum”包含我不再使用的模組版本資訊?

不,go.sum 不是鎖定檔案。構建中的 go.mod 檔案提供了 100% 可重現構建所需的資訊。

為了驗證目的,go.sum 包含特定模組版本內容的預期加密校驗和。有關 go.sum 的更多詳細資訊(包括為什麼您通常應該提交 go.sum),請參閱下面的常見問題解答,以及 tip 文件中的 “Module downloading and verification” 部分。

此外,您的模組的 go.sum 記錄了構建中使用的所有直接和間接依賴項的校驗和(因此您的 go.sum 通常會比您的 go.mod 列出更多模組)。

我應該提交“go.sum”檔案和“go.mod”檔案嗎?

通常,您的模組的 go.sum 檔案應該與您的 go.mod 檔案一起提交。

  • go.sum 包含特定模組版本內容的預期加密校驗和。
  • 如果有人克隆您的儲存庫並使用 go 命令下載您的依賴項,如果他們的下載副本與您的 go.sum 中相應的條目之間存在任何不匹配,他們將收到錯誤。
  • 此外,go mod verify 檢查模組下載的磁碟快取副本是否仍然與 go.sum 中的條目匹配。
  • 請注意,go.sum 不是某些替代依賴項管理系統中使用的鎖定檔案。(go.mod 提供了可重現構建所需的足夠資訊)。
  • 請參閱 Filippo Valsorda 此處 提供的關於為何應提交 go.sum 的簡要理由。有關更多詳細資訊,請參閱 tip 文件的 “模組下載和驗證” 部分。有關可能討論的未來擴充套件,請參閱 #24117#25530 等。

如果我沒有任何依賴項,我是否仍應新增“go.mod”檔案?

是的。這支援在 GOPATH 之外工作,有助於向生態系統傳達您正在選擇模組,此外,您的 go.mod 中的 module 指令作為您程式碼身份的明確宣告(這也是匯入註釋最終可能被棄用的原因之一)。當然,模組在 Go 1.11 中純粹是一種可選功能。

常見問題解答 — 語義匯入版本控制

為什麼主版本號必須出現在匯入路徑中?

請參閱上面 “語義匯入版本控制” 概念部分中關於語義匯入版本控制和匯入相容性規則的討論。另請參閱 宣佈該提案的部落格文章,其中更詳細地討論了匯入相容性規則的動機和理由。

為什麼主要版本 v0, v1 從匯入路徑中省略?

請參閱之前 官方提案討論中的常見問題解答 中的問題“為什麼主要版本 v0, v1 從匯入路徑中省略?”。

用主版本 v0、v1 標記我的專案,或者用 v2+ 進行重大更改會有哪些影響?

針對關於“k8s 進行次要版本釋出,但在每次次要版本釋出中都更改 Go API”的評論,Russ Cox 發表了以下 回覆,其中強調了選擇 v0、v1 與您的專案頻繁進行破壞性更改(v2、v3、v4 等)的一些影響

我並不完全理解 k8s 的開發週期等,但我認為通常 k8s 團隊需要決定/確認他們打算向用戶保證穩定性的內容,然後相應地應用版本號來表達這一點。

  • 如果要做出關於 API 相容性的承諾(這似乎是最佳使用者體驗!),那麼就開始這樣做並使用 1.X.Y。
  • 為了靈活地在每個版本中進行向後不相容的更改,但允許大型程式的各個部分在不同的時間表上升級其程式碼,這意味著不同部分可以在一個程式中使用不同主要版本的 API,那麼請使用 X.Y.0,以及像 k8s.io/client/vX/foo 這樣的匯入路徑。
  • 如果不對 API 相容性做出任何承諾,並且要求每個構建只包含一份 k8s 庫的副本,無論如何,隱含地強制構建的所有部分使用相同的版本,即使並非所有部分都已為此做好準備,那麼請使用 0.X.Y。

順便提一下,Kubernetes 有一些非典型的構建方法(目前包括基於 godep 的自定義包裝指令碼),因此 Kubernetes 對於許多其他專案來說是一個不完美的例子,但隨著 Kubernetes 轉向採用 Go 1.11 模組,它很可能成為一個有趣的例子。

模組能否使用尚未選擇模組的包?

是的。

如果一個儲存庫尚未選擇模組,但已用有效的 semver 標籤(包括必需的引導 v)進行標記,那麼這些 semver 標籤可以在 go get 中使用,並且相應的 semver 版本將記錄在匯入模組的 go.mod 檔案中。如果儲存庫沒有任何有效的 semver 標籤,那麼儲存庫的版本將使用 “偽版本” 記錄,例如 v0.0.0-20171006230638-a6e239ea1c69(其中包含時間戳和提交雜湊,旨在允許在 go.mod 中記錄的版本之間進行完全排序,並使判斷哪個記錄版本“更晚”變得更容易)。

例如,如果包 foo 的最新版本標記為 v1.2.3,但 foo 本身尚未選擇模組,那麼從模組 M 內部執行 go get foogo get foo@v1.2.3 將在模組 M 的 go.mod 檔案中記錄如下

require  foo  v1.2.3

go 工具還將在其他工作流中使用非模組包的可用 semver 標籤(例如 go list -u=patch,它將模組的依賴項升級到可用的補丁版本,或 go list -u -m all,它顯示可用的升級等)。

有關尚未選擇模組的 v2+ 包的其他詳細資訊,請參閱下一個常見問題解答。

模組能否使用尚未選擇模組的 v2+ 包?“+incompatible”是什麼意思?

是的,一個模組可以匯入一個尚未選擇模組的 v2+ 包,如果匯入的 v2+ 包具有有效的 semver 標籤,它將以 +incompatible 字尾記錄。

附加詳細資訊

請熟悉上面 “語義匯入版本控制” 部分中的內容。

首先回顧一些通常有用但在此常見問題解答中思考行為時特別重要的核心原則。

go 工具在模組模式下執行時(例如,GO111MODULE=on),以下核心原則始終成立

  1. 包的匯入路徑定義了包的身份。
    • 匯入路徑不同的包被視為不同的包。
    • 匯入路徑相同的包被視為相同的包(即使 VCS 標籤表明這些包具有不同的主版本,這也是正確的)。
  2. 不帶 /vN 的匯入路徑被視為 v1 或 v0 模組(即使匯入的包尚未選擇模組並且 VCS 標籤表明主要版本大於 1,這也是正確的)。
  3. 在模組的 go.mod 檔案開頭宣告的模組路徑(例如 module foo/v2)既是
    • 該模組身份的明確宣告
    • 該模組必須如何由消費程式碼匯入的明確宣告

正如我們將在下一個常見問題解答中看到的,當 go 工具在模組模式下執行時,這些原則並非總是正確的,但當 go 工具模組模式下執行時,這些原則始終是正確的。

簡而言之,當以下情況為真時,+incompatible 字尾表示上述原則 2 生效

  • 匯入的包尚未選擇模組,並且
  • 其 VCS 標籤表示主要版本大於 1,並且
  • 原則 2 覆蓋了 VCS 標籤——不帶 /vN 的匯入路徑被視為 v1 或 v0 模組(即使 VCS 標籤另有說明)

go 工具處於模組模式時,它會假定一個非模組 v2+ 包不瞭解語義匯入版本控制,並將其視為該包 v1 版本系列的一個(不相容的)擴充套件(+incompatible 字尾表示 go 工具正在這樣做)。

示例

假設

  • oldpackage 是一個早於模組引入的包
  • oldpackage 從未選擇模組(因此它本身沒有 go.mod
  • oldpackage 具有有效的 semver 標籤 v3.0.1,這是它的最新標籤

在這種情況下,例如從模組 M 內部執行 go get oldpackage@latest 將在模組 M 的 go.mod 檔案中記錄以下內容

require  oldpackage  v3.0.1+incompatible

請注意,在上述 go get 命令或記錄的 require 指令中,oldpackage 的末尾沒有使用 /v3 —— 在模組路徑和匯入路徑中使用 /vN語義匯入版本控制 的一個特性,並且 oldpackage 尚未透過在 oldpackage 本身中擁有 go.mod 檔案來表示其接受和理解語義匯入版本控制。換句話說,即使 oldpackage 具有 v3.0.1semver 標籤,oldpackage 也沒有獲得 語義匯入版本控制 的權利和責任(例如在匯入路徑中使用 /vN),因為 oldpackage 尚未宣告其這樣做的意願。

+incompatible 字尾表示 oldpackagev3.0.1 版本尚未主動選擇模組,因此 oldpackagev3.0.1 版本被假定理解語義匯入版本控制或如何在匯入路徑中使用主要版本。因此,當在 模組模式 下操作時,go 工具會將 oldpackage 的非模組 v3.0.1 版本視為 oldpackage 的 v1 版本系列的一個(不相容的)擴充套件,並假定 oldpackagev3.0.1 版本不瞭解語義匯入版本控制,而 +incompatible 字尾表示 go 工具正在這樣做。

oldpackagev3.0.1 版本根據語義匯入版本控制被認為是 v1 釋出系列的一部分,這意味著例如 v1.0.0v2.0.0v3.0.1 版本都始終使用相同的匯入路徑匯入

import  "oldpackage"

請再次注意,oldpackage 的末尾沒有使用 /v3

通常,具有不同匯入路徑的包是不同的包。在此示例中,鑑於 oldpackagev1.0.0v2.0.0v3.0.1 版本都將使用相同的匯入路徑匯入,因此它們被構建視為相同的包(再次因為 oldpackage 尚未選擇語義匯入版本控制),並且在任何給定構建中只會有一個 oldpackage 的副本。(使用的版本將是任何 require 指令中列出的版本的語義最高版本;請參閱 “版本選擇”)。

如果我們假設後來建立了一個新的 oldpackagev4.0.0 版本,它採用了模組並因此包含一個 go.mod 檔案,這表明 oldpackage 現在理解語義匯入版本控制的權利和責任,因此基於模組的消費者現在將在匯入路徑中使用 /v4 進行匯入

import  "oldpackage/v4"

版本將被記錄為

require  oldpackage/v4  v4.0.0

oldpackage/v4 現在是一個與 oldpackage 不同的匯入路徑,因此是一個不同的包。如果在構建中一些消費者有 import "oldpackage/v4",而其他消費者有 import "oldpackage",則模組感知構建中將有兩個副本(每個匯入路徑一個)。這是允許模組逐漸採用策略的一部分。此外,即使模組脫離其當前的過渡階段,這種行為也是可取的,以允許隨著時間的推移程式碼逐漸演進,不同的消費者以不同的速度升級到新版本(例如,允許大型構建中的不同消費者選擇以不同的速度從 oldpackage/v4 升級到未來的 oldpackage/v5)。

如果未啟用模組支援,v2+ 模組在構建中如何處理?1.9.7+、1.10.3+ 和 1.11 中的“最小模組相容性”如何工作?

在考慮較舊的 Go 版本或尚未選擇模組的 Go 程式碼時,語義匯入版本控制對 v2+ 模組具有重要的向後相容性影響。

“語義匯入版本控制” 部分所述

  • 版本為 v2 或更高版本的模組必須在其 go.mod 中宣告的模組路徑中包含 /vN
  • 基於模組的消費者(即已選擇模組的程式碼)必須在匯入路徑中包含 /vN 才能匯入 v2+ 模組。

然而,預計生態系統將以不同的速度採用模組和語義匯入版本控制。

“如何釋出 v2+ 模組” 部分中更詳細地描述的,在“主子目錄”方法中,v2+ 模組的作者建立諸如 mymodule/v2mymodule/v3 之類的子目錄,並將適當的包移動或複製到這些子目錄中。這意味著傳統的匯入路徑邏輯(即使在 Go 1.8 或 1.7 等較舊的 Go 版本中)在看到諸如 import "mymodule/v2/mypkg" 的匯入語句時也會找到適當的包。因此,即使未啟用模組支援(無論是由於您正在執行 Go 1.11 但未啟用模組,還是由於您正在執行 Go 1.7、1.8、1.9 或 1.10 等沒有完整模組支援的舊版本),位於“主子目錄”v2+ 模組中的包也將被找到並使用。有關“主子目錄”方法的更多詳細資訊,請參閱 “如何釋出 v2+ 模組” 部分。

本 FAQ 的其餘部分重點關注 “如何釋出 v2+ 模組” 部分中描述的“主分支”方法。在“主分支”方法中,不建立 /vN 子目錄,而是透過 go.mod 檔案和對提交應用 semver 標籤(通常在 master 上,但也可以在不同的分支上)來傳達模組版本資訊。

為了幫助當前過渡時期,Go 1.11 引入了“最小模組相容性”,以提供對尚未選擇模組的 Go 程式碼的更大相容性,並且“最小模組相容性”也已回溯到 Go 1.9.7 和 1.10.3(在這些版本中,由於這些較舊的 Go 版本沒有完整的模組支援,它們實際上始終停用完整的模組模式)。

“最小模組相容性”的主要目標是

  1. 允許舊的 Go 版本 1.9.7+ 和 1.10.3+ 更容易地編譯使用語義匯入版本控制並在匯入路徑中包含 /vN 的模組,並在 Go 1.11 中停用 模組模式 時提供相同的行為。

  2. 允許舊程式碼能夠在不要求舊消費者程式碼在消費 v2+ 模組時立即更改為使用新的 /vN 匯入路徑的情況下消費 v2+ 模組。

  3. 在不依賴模組作者建立 /vN 子目錄的情況下完成此操作。

附加詳細資訊——“最小模組相容性”

“最小模組相容性”僅在 go 工具的完整 模組模式 被停用時生效,例如,如果您在 Go 1.11 中設定了 GO111MODULE=off,或者正在使用 Go 1.9.7+ 或 1.10.3+ 版本。

當 v2+ 模組作者建立 /v2/vN 子目錄,而您正在依賴 Go 1.9.7+、1.10.3+ 和 1.11 中的“最小模組相容性”機制時

  • 尚未選擇模組的包不會在匯入任何 v2+ 模組的匯入路徑中包含主要版本。
  • 相反,已選擇模組的包必須在匯入任何 v2+ 模組的匯入路徑中包含主要版本。
    • 如果一個包已經選擇模組,但在匯入 v2+ 模組時沒有在匯入路徑中包含主要版本,那麼當 go 工具在完整模組模式下執行時,它將不會匯入該模組的 v2+ 版本。(一個已經選擇模組的包被假定為“說”語義匯入版本控制。如果 foo 是一個具有 v2+ 版本的模組,那麼在語義匯入版本控制下,說 import "foo" 意味著匯入 foo 的 v1 語義匯入版本控制系列)。
  • 用於實現“最小模組相容性”的機制故意非常狹窄
    • 所有邏輯都是——在 GOPATH 模式下操作時,如果包含 /vN 的不可解析匯入語句位於已選擇模組的程式碼中(即,有效 go.mod 檔案樹中的 .go 檔案中的匯入語句),則在刪除 /vN 後會再次嘗試該匯入語句。
    • 最終效果是,在 1.9.7+、1.10.3+ 和 1.11 的 GOPATH 模式下,模組程式碼中的 import "foo/v2" 等匯入語句仍將正確編譯,並且它將解析為 import "foo"(不帶 /v2),這意味著它將使用位於 GOPATH 中的 foo 版本,而不會被額外的 /v2 混淆。
    • “最小模組相容性”不影響任何其他內容,包括它不影響 go 命令列中使用的路徑(例如 go getgo list 的引數)。
  • 這種過渡性的“最小模組感知”機制故意打破了“具有不同匯入路徑的包被視為不同包”的規則,以實現一個非常具體的向後相容性目標——允許舊程式碼在消費 v2+ 模組時無需修改即可編譯。更詳細地說
    • 如果舊程式碼消費 v2+ 模組的唯一方法是先更改舊程式碼,那麼這對於整個生態系統來說將是一個更沉重的負擔。
    • 如果我們不修改舊程式碼,那麼舊程式碼必須與 v2+ 模組的預模組匯入路徑一起工作。
    • 另一方面,選擇模組的新程式碼或更新程式碼必須使用 v2+ 模組的新 /vN 匯入。
    • 新的匯入路徑不等於舊的匯入路徑,但兩者都可以在單個構建中工作,因此我們有兩個不同的功能匯入路徑解析到同一個包。
    • 例如,在 GOPATH 模式下操作時,模組程式碼中出現的 import "foo/v2" 會解析到 GOPATH 中與 import "foo" 相同的程式碼,並且構建最終會得到一個 foo 的副本——特別是 GOPATH 磁碟上的任何版本。這允許包含 import "foo/v2" 的模組程式碼甚至在 1.9.7+、1.10.3+ 和 1.11 的 GOPATH 模式下也能編譯。
  • 相反,當 go 工具在完整的模組模式下執行時
    • “具有不同匯入路徑的包是不同包”的規則沒有例外(包括 vendoring 在完整的模組模式下也已完善以遵守此規則)。
    • 例如,如果 go 工具處於完整的模組模式,並且 foo 是一個 v2+ 模組,那麼 import "foo" 是請求 foo 的 v1 版本,而 import "foo/v2" 是請求 foo 的 v2 版本。

如果我建立了 go.mod 但沒有對我的倉庫應用 semver 標籤,會發生什麼?

semver 是模組系統的基礎。為了給消費者提供最佳體驗,鼓勵模組作者應用 semver VCS 標籤(例如 v0.1.0v1.2.3-rc.1),但 semver VCS 標籤並非嚴格要求

  1. 模組必須遵循semver 規範,以便 go 命令按文件所述執行。這包括遵循 semver 規範中關於何時以及如何允許破壞性更改的規定。

  2. 沒有 semver VCS 標籤的模組將由消費者使用 偽版本 形式的 semver 版本進行記錄。通常這將是一個 v0 主要版本,除非模組作者按照 “主要子目錄” 方法構建了 v2+ 模組。

  3. 因此,未應用 semver VCS 標籤且未建立“主子目錄”的模組實際上是在宣告自己屬於 semver v0 主要版本系列,並且基於模組的消費者將將其視為具有 semver v0 主要版本。

模組能否依賴其自身的不同版本?

一個模組可以依賴於自身的另一個主要版本:總的來說,這與依賴於另一個模組類似。這可能因不同原因而有用,包括允許模組的主要版本作為另一個主要版本的墊片來實現。

此外,一個模組可以以迴圈方式依賴於自身的另一個主要版本,就像兩個完全不同的模組可以以迴圈方式相互依賴一樣。

然而,如果您不期望一個模組依賴於自身的另一個版本,這可能是一個錯誤的跡象。例如,意圖匯入 v3 模組中的包的 .go 程式碼可能缺少匯入語句中必需的 /v3。這個錯誤可能會表現為 v3 模組依賴於自身的 v1 版本。

如果您驚訝地看到一個模組依賴於自身的另一個版本,那麼值得回顧上面的 “語義匯入版本控制” 部分以及常見問題解答 “如果我沒有看到預期的依賴項版本,我應該檢查什麼?”

兩個不能以迴圈方式相互依賴仍然是一個限制。

常見問題解答 — 多模組儲存庫

什麼是多模組倉庫?

多模組儲存庫是包含多個模組的儲存庫,每個模組都有自己的 go.mod 檔案。每個模組從包含其 go.mod 檔案的目錄開始,並遞迴地包含該目錄及其子目錄中的所有包,但不包括包含另一個 go.mod 檔案的任何子樹。

每個模組都有自己的版本資訊。儲存庫根目錄以下模組的版本標籤必須包含相對目錄作為字首。例如,考慮以下儲存庫

my-repo
`-- foo
    `-- rop
        `-- go.mod

模組“my-repo/foo/rop”的版本 1.2.3 的標籤是“foo/rop/v1.2.3”。

通常,儲存庫中一個模組的路徑將是其他模組的路徑的字首。例如,考慮此儲存庫

my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    |-- go.mod
    `-- vub

Fig. A top-level module’s path is a prefix of another module’s path.

圖 A 頂級模組的路徑是另一個模組路徑的字首。

此儲存庫包含兩個模組。但是,模組“my-repo”是模組“my-repo/mig”路徑的字首。

我是否應該在一個倉庫中擁有多個模組?

在這種配置中新增模組、刪除模組和版本化模組需要相當多的謹慎和考慮,因此管理單個模組儲存庫幾乎總是比在現有儲存庫中管理多個模組更容易和簡單。

Russ Cox 在 #26664 中評論道

對於除了高階使用者之外的所有人,您可能希望遵循一個倉庫一個模組的慣例。對於程式碼儲存選項的長期演進而言,一個倉庫可以包含多個模組很重要,但它幾乎肯定不是您預設想要做的事情。

多模組可能更麻煩的兩個例子

  • 從倉庫根目錄執行 go test ./... 將不再測試倉庫中的所有內容
  • 您可能需要透過 replace 指令日常管理模組之間的關係。

但是,除了這兩個例子之外,還有更多的細微之處。如果您正在考慮在單個倉庫中擁有多個模組,請仔細閱讀本小節中的常見問題解答。

在儲存庫中擁有多個 go.mod 檔案的兩個示例場景

  1. 如果您有使用示例,其中示例本身具有複雜的依賴關係集(例如,您可能有一個小包,但包含一個使用 Kubernetes 的包的示例)。在這種情況下,您的儲存庫有一個帶自己的 go.modexample_example 目錄可能很有意義,例如 此處 所示。

  2. 如果您有一個具有複雜依賴關係集的儲存庫,但您的客戶端 API 具有較小的依賴關係集。在某些情況下,擁有一個帶自己的 go.modapiclientapi 或類似目錄可能很有意義,或者將該 clientapi 分離到自己的儲存庫中。

然而,對於這兩種情況,如果您正在考慮為了大量間接依賴項的效能或下載大小而建立多模組儲存庫,強烈建議您首先嚐試使用 GOPROXY,它將在 Go 1.13 中預設啟用。使用 GOPROXY 基本上等於建立多模組儲存庫可能帶來的任何效能優勢或依賴項下載大小優勢。

是否可以將模組新增到多模組倉庫?

是的。然而,這類問題有兩種

第一類:要新增模組的包尚未在版本控制中(新包)。這種情況很簡單:在同一提交中新增包和 go.mod,標記提交,然後推送。

第二類:要新增模組的路徑已在版本控制中,幷包含一個或多個現有包。這種情況需要相當多的注意。為了說明這一點,再次考慮以下儲存庫(現在在 github.com 位置以更好地模擬真實世界)

github.com/my-repo
|-- bar
|-- foo
|   |-- rop
|   `-- yut
|-- go.mod
`-- mig
    `-- vub

考慮新增模組“github.com/my-repo/mig”。如果按照上述相同方法,包 /my-repo/mig 可以由兩個不同的模組提供:舊版本的“github.com/my-repo”和新的獨立模組“github.com/my-repo/mig”。如果兩個模組都處於活動狀態,匯入“github.com/my-repo/mig”將在編譯時導致“ambiguous import”錯誤。

解決這個問題的方法是,讓新新增的模組依賴於它“切分”出來的模組,且版本在該模組被切分之後。

讓我們用上面的儲存庫來逐步說明,假設“github.com/my-repo”當前版本為 v1.2.3

  1. 新增 github.com/my-repo/mig/go.mod

    cd path-to/github.com/my-repo/mig
    go mod init github.com/my-repo/mig
    
    # Note: if "my-repo/mig" does not actually depend on "my-repo", add a blank
    # import.
    # Note: version must be at or after the carve-out.
    go mod edit -require github.com/myrepo@v1.3
    
  2. git commit

  3. git tag v1.3.0

  4. git tag mig/v1.0.0

  5. 接下來,讓我們測試這些。我們不能直接 go buildgo test,因為 go 命令會嘗試從模組快取中獲取每個依賴模組。因此,我們需要使用 replace 規則來使 go 命令使用本地副本

    cd path-to/github.com/my-repo/mig
    go mod edit -replace github.com/my-repo@v1.3.0=../
    go test ./...
    go mod edit -dropreplace github.com/my-repo@v1.3.0
    
  6. git push origin master v1.3.0 mig/v1.0.0 推送提交和兩個標籤

另請注意,將來 golang.org/issue/28835 應該會使測試步驟更加直接。

還要注意,程式碼已從模組“github.com/my-repo”中刪除了次要版本。這可能看起來奇怪,不將其視為一個重大更改,但在這種情況下,傳遞依賴項繼續在其原始匯入路徑提供已刪除包的相容實現。

是否可以從多模組倉庫中刪除模組?

是的,與上述兩個案例和類似步驟相同。

模組能否依賴另一個模組的 internal/?

是的。一個模組中的包可以匯入另一個模組中的內部包,只要它們共享相同的路徑字首,直到 internal/ 路徑元件。例如,考慮以下儲存庫

my-repo
|-- foo
|   `-- go.mod
|-- go.mod
`-- internal

在這裡,只要模組“my-repo/foo”依賴於模組“my-repo”,包 foo 就可以匯入 /my-repo/internal。同樣,在以下儲存庫中

my-repo
|-- foo
|   `-- go.mod
`-- internal
    `-- go.mod

在這裡,只要模組“my-repo/foo”依賴於模組“my-repo/internal”,包 foo 就可以匯入 my-repo/internal。語義在這兩種情況下都是相同的:因為 my-repo 是 my-repo/internal 和 my-repo/foo 之間共享的路徑字首,所以包 foo 被允許匯入包 internal。

額外的 go.mod 能否排除不必要的內容?模組是否有類似 .gitignore 檔案的東西?

在單個儲存庫中擁有多個 go.mod 檔案的另一個用例是,如果儲存庫中有應該從模組中修剪掉的檔案。例如,儲存庫可能包含 Go 模組不需要的非常大的檔案,或者多語言儲存庫可能包含許多非 Go 檔案。

目錄中的空 go.mod 將導致該目錄及其所有子目錄從頂級 Go 模組中排除。

如果排除的目錄不包含任何 .go 檔案,除了放置空的 go.mod 檔案之外,不需要額外的步驟。如果排除的目錄確實包含 .go 檔案,請首先仔細閱讀本多模組儲存庫部分中的其他常見問題解答。

常見問題解答 — 最小版本選擇

最小版本選擇不會阻止開發者獲取重要更新嗎?

請參閱之前 官方提案討論中的常見問題解答 中的問題“最小版本選擇是否會阻止開發人員獲取重要更新?”。

常見問題解答 — 可能的問題

如果我發現問題,我可以檢查哪些一般情況?

  • 透過執行 go env 再次檢查模組是否已啟用,以確認只讀 GOMOD 變數沒有顯示空值。
    • 注意:您從不將 GOMOD 設定為變數,因為它實際上是 go env 輸出的只讀除錯輸出。
    • 如果您正在設定 GO111MODULE=on 來啟用模組,請再次檢查它是否不是意外的複數 GO111MODULES=on。(人們有時會自然地包含 S,因為該功能通常被稱為“模組”)。
  • 如果預期使用 vendoring,請檢查是否將 -mod=vendor 標誌傳遞給 go build 或類似命令,或者是否設定了 GOFLAGS=-mod=vendor
    • 除非您要求 go 工具使用 vendor 目錄,否則模組預設會忽略它。
  • 經常有助於檢查 go list -m all 以檢視為您的構建選擇的實際版本列表
    • 與僅檢視 go.mod 檔案相比,go list -m all 通常會為您提供更多詳細資訊。
  • 如果執行 go get foo 失敗,或者 go build 在特定包 foo 上失敗,檢查 go get -v foogo get -v -x foo 的輸出通常很有幫助
    • 通常,go get 通常會提供比 go build 更詳細的錯誤訊息。
    • go get-v 標誌要求列印更詳細的細節,但請注意,某些“錯誤”如 404 錯誤可能是根據遠端倉庫的配置而預期的。
    • 如果問題性質仍然不清楚,您還可以嘗試更詳細的 go get -v -x foo,它還會顯示正在發出的 git 或其他 VCS 命令。(如果需要,您通常可以在 go 工具上下文之外執行相同的 git 命令以進行故障排除)。
  • 您可以檢查您是否正在使用特別舊的 git 版本
    • 舊版本的 git 是 vgo 原型和 Go 1.11 beta 版常見問題來源,但在 GA 1.11 中頻率低得多。
  • Go 1.11 中的模組快取有時會引起各種錯誤,主要是在之前存在網路問題或多個 go 命令並行執行時(請參閱 #26794,此問題已在 Go 1.12 中解決)。作為故障排除步驟,您可以將 $GOPATH/pkg/mod 複製到備份目錄(以備將來需要進一步調查),執行 go clean -modcache,然後檢視原始問題是否仍然存在。
  • 如果您正在使用 Docker,檢查是否可以在 Docker 之外重現該行為可能很有幫助(如果該行為僅發生在 Docker 中,則上面的專案符號列表可以用作比較 Docker 內部與外部結果的起點)。

您當前正在檢查的錯誤可能是由於您的構建中沒有特定模組或包的預期版本而導致的次要問題。因此,如果某個特定錯誤的原因不明顯,檢查您的版本(如下一個常見問題解答所述)可能會有所幫助。

如果我沒有看到預期的依賴項版本,我可以檢查什麼?

  1. 一個好的第一步是執行 go mod tidy。這有可能解決問題,但它也會使您的 go.mod 檔案與您的 .go 原始碼保持一致狀態,這將有助於簡化後續的調查。(如果 go mod tidy 本身以您不期望的方式更改了依賴項的版本,請首先閱讀 有關 'go mod tidy' 的常見問題解答。如果這無法解釋,您可以嘗試重置您的 go.mod,然後執行 go list -mod=readonly all,這可能會給出有關要求更改其版本的更具體訊息)。

  2. 第二步通常是檢查 go list -m all 以檢視為您的構建選擇的實際版本列表。go list -m all 向您顯示最終選擇的版本,包括間接依賴項和解決任何共享依賴項的版本之後。它還顯示任何 replaceexclude 指令的結果。

  3. 下一步可能是檢查 go mod graphgo mod graph | grep <module-of-interest> 的輸出。go mod graph 列印模組需求圖(包括考慮替換)。輸出中的每一行都有兩個欄位:第一列是消費模組,第二列是該模組的一個需求(包括該消費模組所需的版本)。這可以快速檢視哪些模組需要特定依賴項,包括當您的構建中存在來自構建中不同消費者具有不同所需版本的依賴項時(如果發生這種情況,熟悉上面 “版本選擇” 部分描述的行為很重要)。

go mod why -m <module> 在這裡也很有用,儘管它通常更常用於檢視為什麼包含依賴項(而不是為什麼依賴項最終具有特定版本)。

go list 提供了許多更多的查詢變體,如果需要,可以用於查詢您的模組。一個示例如下,它將顯示構建中使用的確切版本,不包括僅限測試的依賴項

go list -deps -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' ./... | sort -u

在可執行的“Go Modules by Example” 演練 中可以看到更詳細的查詢模組命令和示例。

版本意外的原因之一可能是有人建立了意想不到的或無效的 go.mod 檔案,或相關錯誤(例如:模組的 v2.0.1 版本可能錯誤地在 go.mod 中宣告自己為 module foo,而沒有必需的 /v2.go 程式碼中意圖匯入 v3 模組的匯入語句可能缺少必需的 /v3;v4 模組的 go.mod 中的 require 語句可能缺少必需的 /v4)。因此,如果某個特定問題的原因不明顯,值得首先重新閱讀上面 “go.mod”“語義匯入版本控制” 部分中的內容(因為這些內容包含模組必須遵循的重要規則),然後花幾分鐘時間檢查最相關的 go.mod 檔案和匯入語句。

為什麼我收到錯誤“cannot find module providing package foo”?

這是一個通用錯誤訊息,可能由於多種不同的底層原因而發生。

在某些情況下,此錯誤僅僅是由於路徑拼寫錯誤,因此第一步可能應該是根據錯誤訊息中列出的詳細資訊再次檢查不正確的路徑。

如果您還沒有這樣做,下一個好的步驟通常是嘗試 go get -v foogo get -v -x foo

  • 通常,go get 通常會提供比 go build 更詳細的錯誤訊息。
  • 有關更多詳細資訊,請參閱本節中 上面 的第一個故障排除常見問題解答。

其他一些可能的原因

  • 如果您執行了 go buildgo build .,但當前目錄中沒有任何 .go 原始檔,您可能會看到錯誤 cannot find module providing package foo。如果您遇到這種情況,解決方案可能是另一種呼叫方式,例如 go build ./...(其中 ./... 擴充套件為匹配當前模組中的所有包)。請參閱 #27122

  • Go 1.11 中的模組快取可能導致此錯誤,包括在網路問題或多個 go 命令並行執行時。此問題已在 Go 1.12 中解決。有關更多詳細資訊和可能的糾正步驟,請參閱本節中 上面 的第一個故障排除常見問題解答。

為什麼“go mod init”會報錯“cannot determine module path for source directory”?

不帶任何引數的 go mod init 將嘗試根據 VCS 元資料等不同提示猜測正確的模組路徑。但是,不期望 go mod init 始終能夠猜測正確的模組路徑。

如果 go mod init 給了您這個錯誤,則這些啟發式方法無法猜測,您必須自己提供模組路徑(例如 go mod init github.com/you/hello)。

我有一個複雜的依賴項問題,該依賴項尚未選擇模組。我可以使用其當前依賴項管理器中的資訊嗎?

是的。這需要一些手動步驟,但在某些更復雜的情況下可能會有所幫助。

Go 1.21 及更早版本嘗試將之前的依賴項管理器格式轉換為 go.mod 格式。因此,以下說明需要 1.21.13 或更早版本;您需要使用 GOTOOLCHAIN=go1.21.13 執行以下命令,或者手動安裝更早版本的 go。

當您初始化自己的模組時執行 go mod init,它將透過將 Gopkg.lockglide.lockvendor.json 等配置檔案轉換為包含相應 require 指令的 go.mod 檔案來自動從之前的依賴項管理器進行轉換。例如,預先存在的 Gopkg.lock 檔案中的資訊通常描述了您的所有直接和間接依賴項的版本資訊。

但是,如果您要新增一個尚未選擇模組本身的新依賴項,則沒有類似的從您的新依賴項可能一直在使用的任何先前依賴項管理器進行自動轉換的過程。如果該新依賴項本身具有發生破壞性更改的非模組依賴項,那麼在某些情況下可能會導致不相容問題。換句話說,您的新依賴項的先前依賴項管理器不會自動使用,這在某些情況下可能會導致您的間接依賴項出現問題。

一種方法是,在您的有問題的非模組直接依賴項上執行 go mod init,以從其當前的依賴項管理器進行轉換,然後使用生成的臨時 go.mod 中的 require 指令來填充或更新您模組中的 go.mod

例如,如果 github.com/some/nonmodule 是您的模組的一個有問題的直接依賴項,當前正在使用另一個依賴項管理器,您可以執行類似以下的操作

$ git clone -b v1.2.3 https://github.com/some/nonmodule /tmp/scratchpad/nonmodule
$ cd /tmp/scratchpad/nonmodule
$ go mod init
$ cat go.mod

臨時 go.mod 中生成的 require 資訊可以手動移動到您的模組的實際 go.mod 中,或者您可以考慮使用 https://github.com/rogpeppe/gomodmerge,這是一個針對此用例的社群工具。此外,您需要向您的實際 go.mod 新增 require github.com/some/nonmodule v1.2.3,以匹配您手動克隆的版本。

在 docker 中遵循此技術的具體示例見此 #28489 評論,其中說明了如何獲取一致的 docker 依賴項版本集,以避免 github.com/sirupsen/logrusgithub.com/Sirupsen/logrus 之間的區分大小寫問題。

如何解決匯入路徑與宣告的模組身份不匹配導致的“parsing go.mod: unexpected module path”和“error loading module requirements”錯誤?

為什麼會出現這個錯誤?

通常,模組透過 module 指令在 go.mod 中宣告其身份,例如 module example.com/m。這是該模組的“模組路徑”,並且 go 工具會強制該宣告的模組路徑與任何消費者使用的匯入路徑之間保持一致。如果模組的 go.mod 檔案內容為 module example.com/m,則消費者必須使用以該模組路徑開頭的匯入路徑匯入該模組中的包(例如,import "example.com/m"import "example.com/m/sub/pkg")。

如果消費者使用的匯入路徑與相應的宣告模組路徑不匹配,go 命令會報告 parsing go.mod: unexpected module path 致命錯誤。此外,在某些情況下,go 命令隨後會報告一個更通用的 error loading module requirements 錯誤。

此錯誤最常見的原因是名稱更改(例如,從 github.com/Sirupsen/logrusgithub.com/sirupsen/logrus),或者模組在模組之前由於虛榮匯入路徑而有時透過兩個不同的名稱使用(例如,github.com/golang/sync 與推薦的 golang.org/x/sync)。

如果您有一個依賴項仍然透過舊名稱(例如 github.com/Sirupsen/logrus)或非規範名稱(例如 github.com/golang/sync)匯入,但該依賴項隨後採用了模組並在其 go.mod 中宣告其規範名稱,這可能會導致問題。當升級後的模組版本被發現聲明瞭一個不再與舊匯入路徑匹配的規範模組路徑時,此錯誤可能在升級期間觸發。

示例問題場景

  • 您間接依賴於 github.com/Quasilyte/go-consistent
  • 專案採用模組,然後將其名稱更改為 github.com/quasilyte/go-consistent(將 Q 更改為小寫 q),這是一個重大更改。GitHub 將舊名稱轉發到新名稱。
  • 您執行 go get -u,它嘗試升級您的所有直接和間接依賴項。
  • github.com/Quasilyte/go-consistent 嘗試升級,但找到的最新 go.mod 現在顯示 module github.com/quasilyte/go-consistent
  • 整體升級操作未能完成,並出現錯誤

go: github.com/Quasilyte/go-consistent@v0.0.0-20190521200055-c6f3937de18c: 解析 go.mod: 意外的模組路徑 “github.com/quasilyte/go-consistent” go get: 錯誤載入模組要求

解決

錯誤的最常見形式是

go: example.com/some/OLD/name@vX.Y.Z: 解析 go.mod: 意外的模組路徑 “example.com/some/NEW/name”

如果您訪問 example.com/some/NEW/name(來自錯誤的右側)的儲存庫,您可以檢查最新發布版本或 master 分支的 go.mod 檔案,檢視它是否在 go.mod 的第一行宣告自己為 module example.com/some/NEW/name。如果是,則表示您遇到“舊模組名”與“新模組名”的問題。

本節的其餘部分將重點介紹透過按順序執行以下步驟來解決“舊名稱”與“新名稱”形式的錯誤

  1. 檢查您自己的程式碼,看是否使用了 example.com/some/OLD/name 匯入。如果是,請更新您的程式碼以使用 example.com/some/NEW/name 匯入。

  2. 如果您在升級過程中收到此錯誤,您應該嘗試使用 Go 的 tip 版本進行升級,它具有更有針對性的升級邏輯 (#26902),通常可以繞過此問題,並且通常對這種情況有更好的錯誤訊息。請注意,tip / 1.13 中的 go get 引數與 1.12 中的不同。獲取 tip 並使用它升級依賴項的示例

go get golang.org/dl/gotip && gotip download
gotip get -u all
gotip mod tidy

由於有問題的舊匯入通常存在於間接依賴項中,因此使用 tip 升級然後執行 go mod tidy 通常可以將您升級到有問題的版本之後,然後還將有問題的版本從您的 go.mod 中刪除,因為不再需要它,這樣您就可以在使用 Go 1.12 或 1.11 進行日常使用時進入正常工作狀態。例如,請參閱 此處 的方法,以解決 github.com/golang/lintgolang.org/x/lint 的問題。

  1. 如果您在執行 go get -u foogo get -u foo@latest 時收到此錯誤,請嘗試刪除 -u。這將為您提供 foo@latest 使用的依賴項集,而不會將 foo 的依賴項升級到 foo 作者在釋出 foo 時可能驗證為可用的版本。這尤其重要,特別是在這個過渡時期,foo 的某些直接和間接依賴項可能尚未採用 semver 或模組。(一個常見的錯誤是認為 go get -u foo 僅僅獲取 foo 的最新版本。實際上,go get -u foogo get -u foo@latest 中的 -u 意味著要獲取 foo所有直接和間接依賴項的最新版本;這可能就是您想要的,但如果由於深層間接依賴項而失敗,則可能不是)。

  2. 如果上述步驟未能解決錯誤,那麼接下來的方法會稍微複雜一些,但大多數情況下應該能夠解決“舊名稱”與“新名稱”形式的此類錯誤。這僅使用錯誤訊息本身的資訊,外加對一些 VCS 歷史的簡要檢視。

    4.1. 轉到 example.com/some/NEW/name 倉庫

    4.2. 確定 go.mod 檔案是在何時引入的(例如,透過檢視 go.mod 的 blame 或歷史檢視)。

    4.3. 選擇在 go.mod 檔案引入之前的釋出版本或提交。

    4.4. 在您的 go.mod 檔案中,新增一個 replace 語句,在 replace 語句的兩側都使用舊名稱:replace example.com/some/OLD/name => example.com/some/OLD/name <version-just-before-go.mod>。以上面的例子為例,其中 github.com/Quasilyte/go-consistent 是舊名稱,github.com/quasilyte/go-consistent 是新名稱,我們可以看到 go.mod 首次引入是在提交 00c5b0cf371a。該倉庫沒有使用語義版本標籤,所以我們將使用緊接之前的提交 00dd7fb039e,並在 replace 語句的兩側都使用舊的 Quasilyte 大寫名稱將其新增到 replace 中。

replace github.com/Quasilyte/go-consistent => github.com/Quasilyte/go-consistent 00dd7fb039e

replace 語句使我們能夠升級超過有問題的“舊名稱”與“新名稱”不匹配問題,透過有效地阻止舊名稱在存在 go.mod 的情況下升級到新名稱。通常,透過 go get -u 或類似操作進行的升級現在可以避免該錯誤。如果升級完成,您可以檢查是否仍有匯入舊名稱的情況(例如,go mod graph | grep github.com/Quasilyte/go-consistent),如果沒有,則可以刪除 replace。(這之所以通常有效,是因為如果使用了舊的、有問題的匯入路徑,即使在升級完成後的最終結果中可能不會使用它,升級本身也可能失敗,這在 #30831 中有記錄)。

  1. 如果以上步驟未能解決問題,可能是因為一個或多個依賴項的最新版本仍在使用有問題的舊匯入路徑。在這種情況下,重要的是要找出誰仍在引用有問題的舊匯入路徑,並查詢或提出一個問題,要求有問題的匯入者更改為使用現在規範的匯入路徑。在步驟 2 中使用 gotip 可能會識別出有問題的匯入者,但並非在所有情況下都如此,尤其是在升級時 (#30661)。如果不清楚是誰在使用有問題的舊匯入路徑進行匯入,通常可以透過建立乾淨的模組快取,執行觸發錯誤的操作,然後在使用模組快取中 grep 舊的有問題的匯入路徑來找出。例如
export GOPATH=$(mktemp -d)
go get -u foo               # perform operation that generates the error of interest
cd $GOPATH/pkg/mod
grep -R --include="*.go" github.com/Quasilyte/go-consistent
  1. 如果這些步驟不足以解決問題,或者如果您是由於迴圈引用而無法刪除對舊的、有問題的匯入路徑引用的專案的維護者,請參閱單獨的 wiki 頁面上對該問題的更詳細說明。

最後,上述步驟側重於如何解決潛在的“舊名稱”與“新名稱”問題。但是,如果 go.mod 放置位置錯誤或模組路徑不正確,也可能會出現相同的錯誤訊息。如果是這種情況,匯入該模組應該總是失敗。如果您正在匯入一個剛建立且從未成功匯入過的新模組,您應該檢查 go.mod 檔案是否放置正確,並且它是否具有與該位置對應的正確模組路徑。(最常見的方法是每個倉庫一個 go.mod,單個 go.mod 檔案放置在倉庫根目錄,並使用倉庫名稱作為 module 指令中宣告的模組路徑)。有關更多詳細資訊,請參閱 “go.mod” 部分。

為什麼“go build”需要 gcc,以及為什麼不使用 net/http 等預構建包?

簡而言之

因為預構建的包是非模組構建,無法重用。抱歉。暫時停用 cgo 或安裝 gcc。

這隻有在選擇使用模組時(例如,透過 GO111MODULE=on)才是一個問題。有關更多討論,請參閱 #26988

模組是否適用於 import "./subdir" 等相對匯入?

不。請參閱 #26645,其中包括

在模組中,子目錄最終有了名稱。如果父目錄宣告“module m”,那麼子目錄將被匯入為“m/subdir”,不再是“./subdir”。

填充的 vendor 目錄中可能缺少一些必要檔案。

不含 .go 檔案的目錄不會被 go mod vendor 複製到 vendor 目錄中。這是設計使然。

簡而言之,拋開任何特定的 vendoring 行為不談,Go 構建的總體模型是構建包所需的檔案應該與 .go 檔案在同一個目錄中。

以 cgo 為例,修改其他目錄中的 C 原始碼不會觸發重新構建,而是會使用過時的快取條目。cgo 文件現在 包含

請注意,對其他目錄中檔案的更改不會導致包重新編譯,因此包的所有非 Go 原始碼都應儲存在包目錄中,而不是子目錄中。

社群工具 https://github.com/goware/modvendor 允許您輕鬆地將一組完整的 .c、.h、.s、.proto 或其他檔案從模組複製到 vendor 目錄中。儘管這可能很有用,但如果您需要構建包的檔案位於 .go 檔案所在的目錄之外,則需要謹慎確保您的 Go 構建總體上(無論是否使用 vendoring)得到正確處理。

請參閱 #26366 中的其他討論。

傳統供應商模式的另一種方法是檢入模組快取。它最終可以達到與傳統供應商模式相似的優勢,並且在某些方面最終可以實現更高保真度的複製。此方法在“Go Modules by Example” 演練中有所解釋。


此內容是 Go Wiki 的一部分。