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
是建議用於全球安裝套件 / 可執行檔的方式retract
在go.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 發行說明 以取得詳細資訊。
- 當主模組包含頂層供應商目錄,且其
go.mod 檔案
指定go 1.14
或更高版本時,go 指令現在會將接受該旗標的作業預設為-mod=vendor
。 - 當 go.mod 檔案為唯讀且不存在頂層供應商目錄時,現在會預設設定
-mod=readonly
。 -modcacherw
是新的旗標,指示 go 指令將模組快取中新建立的目錄保留在預設權限,而不是將它們設為唯讀。-modfile=file
是新的旗標,指示 go 指令讀取(且可能寫入)備用的go.mod
檔案,而不是模組根目錄中的檔案。- 當明確啟用模組感知模式(透過設定
GO111MODULE=on
)時,如果沒有go.mod
檔案,大多數模組指令的功能會受到更多限制。 - go 指令現在支援模組模式中的 Subversion 儲存庫。
Go 1.13
有關詳細資訊,請參閱 Go 1.13 發行說明。
go
工具現在預設從 https://proxy.golang.org 上的公開 Go 模組鏡像下載模組,並預設根據 https://sum.golang.org 上的公開 Go 雜湊資料庫驗證已下載的模組 (無論來源為何)。- 如果您有私人程式碼,您很可能會設定
GOPRIVATE
設定 (例如go env -w GOPRIVATE=*.corp.com,github.com/secret/repo
),或更精細的變數GONOPROXY
或GONOSUMDB
,這些變數支援較不頻繁的使用案例。有關更多詳細資訊,請參閱 文件。
- 如果您有私人程式碼,您很可能會設定
- 如果找到任何 go.mod,即使在 GOPATH 內部,
GO111MODULE=auto
會啟用模組模式。(在 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
)。
目錄
「快速入門」和「新概念」部分對於開始使用模組的人來說特別重要。「如何…」部分涵蓋更多關於機制的詳細資訊。此頁面中數量最多的內容是常見問題解答,回答更具體的問題;至少瀏覽這裡列出的常見問題解答一覽表是值得的。
- 快速入門
- 新概念
- 如何使用模組
- 移轉到模組
- 其他資源
- 自 Vgo 初始提案以來的變更
- GitHub 問題
- 常見問題
- 常見問題 — 其他控制
- 常見問題 — go.mod 和 go.sum
- 常見問題集 — 語意化匯入版本控制
- 常見問題集 — 多模組儲存庫
- 常見問題集 — 最小版本選擇
- 常見問題集 — 可能的問題
- 如果我遇到問題,有哪些一般事項可以進行重點檢查?
- 如果我未看到預期的依賴項版本,我可以檢查哪些事項?
- 為什麼我會收到「找不到提供套件 foo 的模組」錯誤訊息?
- 為什麼「go mod init」會出現「無法確定來源目錄的模組路徑」錯誤訊息?
- 我有一個複雜的相依性問題,它尚未選擇加入模組。我可以使用其目前相依性管理員的資訊嗎?
- 我該如何解決「剖析 go.mod:意外的模組路徑」和「載入模組需求時發生錯誤」,這些錯誤是由於匯入路徑與宣告的模組身分不符所造成?
- 為什麼「go build」需要 gcc,而且為什麼不使用預先建置的套件,例如 net/http?
- 模組是否可以使用相對匯入,例如
import "./subdir"
? - 某些必要的檔案可能不存在於已填入的供應商目錄中
快速入門
範例
本頁面其餘部分將涵蓋詳細資訊,但以下是一個從頭開始建立模組的簡單範例。
在 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 get
或 go mod tidy
。在 1.16 中,預設會停用 go.mod
和 go.sum
檔案的隱式修改。
您典型的日常工作流程可以是
- 視需要將匯入陳述式新增到您的
.go
程式碼中。 - 標準命令,例如
go build
或go test
,會自動視需要新增相依性,以滿足匯入(更新go.mod
和下載新的相依性)。 - 需要時,可以使用
go get foo@v1.2.3
、go 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(主版本).(次要版本).(修補版本)
,例如 v0.1.0
、v1.2.3
或 v1.5.0-rc.1
。開頭的 v
是必要的。如果使用 Git,請使用〈標籤〉標記已發行的提交版本。公開和私人模組存放庫和代理伺服器已開始提供(請參閱〈下方〉的常見問題解答)。
go.mod
模組由一個 Go 原始檔樹狀結構定義,樹狀結構的根目錄中有一個 go.mod
檔。模組原始碼可以位於 GOPATH 之外。有四個指令:module
、require
、replace
、exclude
。
以下是定義模組 github.com/my/thing
的 go.mod
檔範例
module github.com/my/thing
require (
github.com/some/dependency v1.2.3
github.com/another/dependency/v4 v4.0.0
)
模組透過 go.mod
中的 module
指令宣告其身分,此指令提供 *模組路徑*。模組中所有套件的匯入路徑都將模組路徑作為共同字首。模組路徑和從 go.mod
到套件目錄的相對路徑共同決定套件的匯入路徑。
例如,如果您要為儲存庫 github.com/user/mymod
建立一個模組,其中將包含兩個匯入路徑為 github.com/user/mymod/foo
和 github.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
。
exclude
和 replace
指令僅對目前的(「主要」)模組運作。在建立主要模組時,會忽略主要模組以外模組中的 exclude
和 replace
指令。因此,replace
和 exclude
陳述式允許主要模組完全控制其自己的建置,而不會同時受到相依項的完全控制。(請參閱 下方 常見問題集,以了解何時使用 replace
指令)。
版本選取
如果您在尚未由 go.mod
中的 require
涵蓋的原始碼中新增新的匯入,大部分的 go 指令(例如「go build」和「go test」)會自動查詢適當的模組,並將該新直接相依項的最高版本新增到您的模組的 go.mod
中,作為 require
指令。例如,如果您的新匯入對應到相依項 M,其最新的標記發行版本為 v1.2.3
,您的模組的 go.mod
將會以 require M v1.2.3
結尾,這表示模組 M 是允許版本 >= v1.2.3(且 < v2,因為 v2 被視為與 v1 不相容)的相依項。
最小版本選取演算法用於選取在建置中使用的所有模組的版本。對於建置中的每個模組,最小版本選取選取的版本永遠是主要模組或其相依項中的 require
指令明確列出的版本中語意上最高的版本。
例如,如果您的模組依賴於具有 require D v1.0.0
的模組 A,而您的模組也依賴於具有 require D v1.1.1
的模組 B,則最小版本選取會選取 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/rand
是與 crypto/rand
不同的套件。如果不同的匯入路徑是因為匯入路徑中出現不同的主要版本,也是如此。因此 example.com/my/mod/mypkg
是與 example.com/my/mod/v2/mypkg
不同的套件,而且兩者都可以在單一建置中匯入,這除了其他好處之外,還有助於解決菱形相依性問題,並允許 v1 模組根據其 v2 替換或反之實作。
請參閱 go
命令文件中的 「模組相容性和語意版本控管」 區段,以取得語意匯入版本控管的更多詳細資訊,並參閱 https://semver.org 以取得更多關於語意版本控管的資訊。
到目前為止,本區段一直專注於已選擇加入模組並匯入其他模組的程式碼。然而,在 v2+ 模組的匯入路徑中放入主要版本可能會與較舊版本的 Go 或尚未選擇加入模組的程式碼產生不相容性。為了解決這個問題,有三個重要的過渡期特例或例外情況,適用於上述行為和規則。隨著越來越多的套件選擇加入模組,這些過渡期例外情況將變得越來越不重要。
三個過渡期例外情況
-
gopkg.in
使用從
gopkg.in
開始的匯入路徑的現有程式碼(例如gopkg.in/yaml.v1
和gopkg.in/yaml.v2
)即使在選擇加入模組後,仍可繼續將這些格式用於其模組路徑和匯入路徑。 -
匯入非模組 v2+ 套件時的「+incompatible」
模組可以匯入一個尚未選擇加入模組的 v2+ 套件。具有有效 v2+ semver 標籤的非模組 v2+ 套件將會在匯入模組的
go.mod
檔案中記錄一個+incompatible
字尾。+incompatible
字尾表示,即使 v2+ 套件具有有效的 v2+ semver 標籤,例如v2.0.0
,v2+ 套件尚未主動選擇加入模組,因此假設 v2+ 套件並未在了解語意匯入版本控管的含意和如何在匯入路徑中使用主要版本的狀況下建立。因此,在 模組模式 中執行時,go
工具會將非模組 v2+ 套件視為套件 v1 版本系列的(不相容)延伸,並假設套件不了解語意匯入版本控管,而+incompatible
字尾表示go
工具正在執行此操作。 -
當模組模式未啟用時,「最低限度模組相容性」
為了協助向後相容性,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
)。 - 設定環境變數
GO111MODULE=on
來呼叫go
指令。
如何定義模組
為現有專案建立 go.mod
-
導覽至 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
-
建立初始模組定義並寫入
go.mod
檔案$ go mod init
此步驟會從任何現有的
dep
Gopkg.lock
檔案或其他 九種總計支援的相依性格式 轉換,並加入需求陳述,以符合現有設定。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 build
會自動新增遺失或未轉換的相依性,以滿足此特定建置呼叫的匯入需求$ go build ./...
-
測試模組的設定,以確保它能與所選版本搭配使用
$ go test ./...
-
(選用)執行模組測試以及所有直接和間接相依性的測試,以檢查不相容性
$ 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 foo
或 go get -u foo@latest
中的 -u
表示也會取得 foo
的所有直接和間接依賴項的最新版本。升級 foo
時的常見起點是執行 go get foo
或 go get foo@latest
,而不用 -u
(在一切正常後,可以考慮 go get -u=patch foo
、go get -u=patch
、go get -u foo
或 go get -u
)。
若要升級或降級到更具體的版本,「go get」允許透過在套件引數中加入 @version 字尾或 「模組查詢」來覆寫版本選取,例如 go get foo@v1.6.2
、go get foo@e3702bed2
或 go 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 build
和go 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
的第一個版本(因此也是第一個包含 go.mod
檔案的 foo
版本)。在此情況下增加主要版本可為 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
版本的範例,這兩個選項為
-
主要分支:更新
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.*.*
commit 放置在個別的 v3 分支上。 - 注意:建立新分支並非必要。如果您之前已在 master 上發布,且偏好於在 master 上標記
v3.0.0
,這是一個可行的選項。(不過,請注意在master
中引入不相容的 API 變更可能會對非模組使用者造成問題,因為在 Go 1.11 之前,go
工具並未察覺 semver,或者在 Go 1.11+ 中未啟用 模組模式 時)。 - 現有的相依性管理解決方案,例如
dep
,目前在使用此方式建立的 v2+ 模組時可能會遇到問題。例如,請參閱 dep#1962。
-
主要子目錄:建立新的
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 mod init
自動將 dep、glide、govendor、godep 和其他 5 個現有相依性管理員 中的必要資訊轉換成go.mod
檔案,以產生等效的建置。- 如果您正在建立 v2+ 模組,請務必確保已轉換的
go.mod
中的module
指令包含適當的/vN
(例如,module foo/v3
)。 - 請注意,如果您正在匯入 v2+ 模組,您可能需要在初始轉換後進行一些手動調整,才能將
/vN
新增到go mod init
在從先前相依性管理員轉換後產生的require
陳述式。請參閱上方的 「如何定義模組」 部分,以取得更多詳細資訊。 - 此外,
go mod init
也不會編輯您的.go
程式碼,以將任何必要的/vN
新增到匯入陳述式。請參閱上方的 「語意匯入版本控制」 和 「發布模組 (v2 或更高版本)」 部分,以取得必要的步驟,包括一些關於自動化轉換的社群工具選項。
提供相依性資訊給較舊版本的 Go 和非模組使用者
- 較舊版本的 Go 了解如何使用 `go mod vendor` 建立的供應商目錄,而 Go 1.11 和 1.12+ 在停用模組模式時也能理解。因此,供應是模組提供相依性給不完全了解模組的較舊 Go 版本以及尚未啟用模組的使用者的一種方式。請參閱 供應常見問題集 和 `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 build` 或 `go test`,會自動下載 `foo` 並視需要更新 `go.mod`)。
- 只需加入一個匯入陳述式
- 基於模組的消費者預設不會使用
vendor
目錄。- 當在
go
工具中啟用模組模式時,在使用模組時vendor
並非絕對必要(已知go.mod
中包含的資訊和go.sum
中的加密檢查碼),但某些現有的安裝說明假設go
工具預設會使用vendor
。請參閱 供應商常見問答 以取得更多詳細資料。
- 當在
- 在某些情況下,包含
go get foo/...
的安裝說明可能會產生問題(請參閱 #27215 中的討論)。
避免中斷現有的匯入路徑
模組透過 go.mod
中的 module
指令宣告其身分,例如 module github.com/my/module
。模組中的所有套件都必須由任何模組感知消費者使用與模組宣告的模組路徑相符的匯入路徑來匯入(對於根套件完全相符,或使用模組路徑作為匯入路徑的前置詞)。如果匯入路徑與對應模組宣告的模組路徑不符,go
指令會報告 意外的模組路徑
錯誤。
在為現有套件組採用模組時,應小心避免中斷現有消費者使用的現有匯入路徑,除非您在採用模組時遞增主要版本。
例如,如果您的現有 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
中變更模組路徑是一個重大變更。
整體而言,這類似於模組前強制執行正規匯入路徑的方式,透過 「匯入路徑註解」,有時也稱為「匯入指令」或「匯入路徑強制執行」。例如,套件 go.uber.org/zap
目前託管在 github.com/uber-go/zap
,但使用匯入路徑註解 在套件宣告旁邊,會觸發錯誤,給任何使用錯誤的基於 GitHub 的匯入路徑的模組前消費者
套件 zap // 匯入 "go.uber.org/zap"
go.mod 檔案的模組陳述已淘汰匯入路徑註解。
首次採用 v2+ 套件的模組時,遞增主要版本
- 如果你有套件在採用模組前已標記為 v2.0.0 或更高,建議的最佳做法是在首次採用模組時遞增主要版本。例如,如果你在
v2.0.1
且尚未採用模組,則你會對首次採用模組的版本使用v3.0.0
。請參閱上方的 「發布模組 (v2 或更高)」 章節,以取得更多詳細資訊。
v2+ 模組允許在單一建置中有多個主要版本
- 如果模組在 v2 或更高,則暗示在單一建置中可以有多個主要版本(例如,
foo
和foo/v3
可能會出現在單一建置中)。- 這自然而然地從「具有不同匯入路徑的套件是不同的套件」的規則而來。
- 當這發生時,會有套件層級狀態的複本(例如,
foo
的套件層級狀態和foo/v3
的套件層級狀態),而且每個主要版本都會執行自己的init
函式。 - 此方法有助於模組系統的許多面向,包括協助解決鑽石依賴性問題、在大型程式碼庫中逐步移轉至新版本,以及允許將主要版本實作為不同主要版本周圍的墊片。
- 請參閱 https://research.swtch.com/vgo-import 或 #27514 的「避免單例問題」區段,以取得一些相關討論。
使用非模組程式碼的模組
- 模組能夠使用尚未選擇加入模組的套件,並將適當的套件版本資訊記錄在匯入模組的
go.mod
中。模組可以使用尚未有任何適當 semver 標籤的套件。請參閱 下方 的常見問答,以取得更多詳細資料。 - 模組也可以匯入尚未選擇加入模組的 v2+ 套件。如果匯入的 v2+ 套件具有有效的 semver 標籤,它將會以
+incompatible
字尾記錄。請參閱 下方 的常見問答,以取得更多詳細資料。
使用模組的非模組程式碼
-
使用 v0 和 v1 模組的非模組程式碼:
- 尚未選擇加入模組的程式碼可以使用並建置 v0 和 v1 模組(與所使用的 Go 版本無關)。
-
使用 v2+ 模組的非模組程式碼:
-
Go 版本 1.9.7+、1.10.3+ 和 1.11 已更新,因此使用這些版本建置的程式碼可以適當地使用 v2+ 模組,而不需要修改現有程式碼,如上方的 「語意匯入版本控制」 和 「發布模組 (v2 或更高)」 區段所述。
-
如果 v2+ 模組是按照 「發布模組 (v2 或更高)」 區段中概述的「主要子目錄」方法建立,則早於 1.9.7 和 1.10.3 的 Go 版本可以使用 v2+ 模組。
-
現有 v2+ 套件作者的策略
對於考慮採用模組的現有 v2+ 套件作者,可以將替代方法總結為在三種頂層策略之間進行選擇。然後,每個選擇都有後續決策和變體(如上所述)。這些替代的頂層策略是
-
要求客戶端使用 Go 版本 1.9.7+、1.10.3+ 或 1.11+.
此方法使用「主要分支」方法,並依賴於已回溯移植到 1.9.7 和 1.10.3 的「最低模組感知」。請參閱上面的 「語意匯入版本控制」 和 「發布模組 (v2 或更高版本)」 部分以了解更多詳情。
-
允許客戶端使用更舊的 Go 版本,例如 Go 1.8.
此方法使用「主要子目錄」方法,並涉及建立子目錄,例如
/v2
或/v3
。請參閱上面的 「語意匯入版本控制」 和 「發布模組 (v2 或更高版本)」 部分以了解更多詳情。 -
等待採用模組.
在此策略中,事情會繼續與已採用模組的客戶端程式碼以及尚未採用模組的客戶端程式碼一起運作。隨著時間的推移,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 日)
簡介材料
- Russ Cox 在 GopherCon Singapore 發表的 40 分鐘簡介影片 「Go 中版本的原則」(2018 年 5 月 2 日)
- 簡潔地介紹版本化 Go 模組設計背後的哲學,包括「相容性」、「可重複性」和「合作」這三個核心原則
- Paul Jolly 在 2018 年 8 月 15 日發表的 35 分鐘範例簡介影片 「什麼是 Go 模組,我該如何使用它們?」(投影片)
- Dave Cheney 在 2018 年 7 月 14 日發表的簡介部落格文章 「試用 Go 模組」
- Chris Hines 在 2018 年 7 月 16 日發表的 Go Meetup 模組投影片
- 由 Francesc Campoy 製作的 30 分鐘入門影片 「Go 模組和 SemVer 簡介」(2018 年 11 月 15 日)
其他素材
- 部落格文章 「在 Travis CI 上使用支援供應商的 Go 模組」,作者 Fatih Arslan(2018 年 8 月 26 日)
- 部落格文章 「Go 模組和 CircleCI」,作者 Todd Keech(2018 年 7 月 30 日)
- 部落格文章 「vgo 提案已通過。現在呢?」,作者 Russ Cox(2018 年 5 月 29 日)
- 包含版本化模組目前為實驗性選擇加入功能的摘要
- 部落格文章 「如何從 tip 建置 go 並開始使用 go 模組」,作者 Carolyn Van Slyck(2018 年 7 月 16 日)
自 Vgo 初始提案以來的變更
在提案、原型和 beta 程序中,整體社群已建立超過 400 個問題。請持續提供意見回饋。
以下是部分較大的變更和改善事項清單,幾乎所有變更和改善事項都是主要根據社群的意見回饋
- 保留頂層供應商支援,而非讓基於 vgo 的建置完全忽略供應商目錄(討論,CL)
- 反向移植最小模組感知,讓較舊的 Go 版本 1.9.7+ 和 1.10.3+ 能更輕鬆地使用 v2+ 專案的模組(討論,CL)
- 允許 vgo 預設使用 v2+ 標籤,因為先前存在的套件尚未有 go.mod(在此處說明相關行為的最新更新)
- 透過指令碼
go get -u=patch
新增支援,以將所有傳遞相依性更新至相同次要版本中可用的最新修補程式層級版本 (討論,文件) - 透過環境變數進行額外控制 (例如,#26585 中的 GOFLAGS,CL)
- 更細緻地控制是否允許更新 go.mod、如何使用供應商目錄,以及是否允許網路存取 (例如,-mod=readonly、-mod=vendor、GOPROXY=off;相關 CL 用於最近變更)
- 新增更彈性的替換指令 (CL)
- 新增其他方法來查詢模組 (供人類使用,以及更好的編輯器/IDE 整合)
- go CLI 的 UX 已根據目前的經驗持續優化 (例如,#26581,CL)
- 透過
go mod download
新增額外支援,以預熱快取供 CI 或 docker 建置等使用案例使用 (#26610) - 極有可能:提供更好的支援,以安裝特定版本的程式至 GOBIN (#24250)
GitHub 問題
- 目前開啟的模組問題
- 已關閉的模組問題
- 已關閉的 vgo 問題
- 提交一個 新的模組議題,使用「cmd/go:」作為開頭
常見問題
如何將版本標記為不兼容?
require
指令允許任何模組宣告它應該使用相依模組 D 的版本 >= x.y.z 來建置(這可能是因為與模組 D 的版本 < x.y.z 不相容而指定的)。經驗數據顯示 這是用於 dep
和 cargo
的主要約束形式。此外,建置中的頂層模組可以 exclude
相依模組的特定版本,或使用不同的程式碼 replace
其他模組。請參閱完整提案以 取得更多詳細資料和依據。
版本化模組提案的主要目標之一,是為工具和開發人員新增一個圍繞 Go 程式碼版本的共用詞彙和語意。這為未來宣告其他形式的不相容性奠定基礎,例如可能
- 宣告已棄用的版本,如 所述,在最初的
vgo
部落格系列中 - 宣告模組之間的成對不相容性,如在提案過程中 在此處 所討論
- 在發布版本後宣告模組的成對不相容版本或不安全的版本。例如,請參閱 #24031 和 #26829 中正在進行的討論
什麼時候會取得舊行為與新的基於模組的行為?
一般來說,模組是 Go 1.11 的選用功能,因此舊行為在預設情況下會被保留。
在取得舊的 1.10 現狀行為與新的選用式模組行為時,進行摘要
- 在 GOPATH 內部 — 預設為舊的 1.10 行為(忽略模組)
- 在 GOPATH 外部,同時在具有
go.mod
的檔案樹中 — 預設為模組行為 - GO111MODULE 環境變數
- 未設定或
auto
— 上述預設行為 on
— 強制啟用模組支援,不論目錄位置off
— 強制停用模組支援,不論目錄位置
- 未設定或
為什麼透過 go get
安裝工具會失敗,並出現錯誤 無法找到主模組
?
當您設定 GO111MODULE=on
,但在執行 go get
時不在具有 go.mod
的檔案樹中,就會發生此情況。
最簡單的解決方案是讓 GO111MODULE
未設定(或等效地明確設定為 GO111MODULE=auto
),這可以避免此錯誤。
回想一下模組存在的主要原因之一是記錄精確的依賴資訊。此依賴資訊會寫入您目前的 go.mod
。如果您不在具有 go.mod
的檔案樹中,但已透過設定 GO111MODULE=on
告訴 go get
指令在模組模式中執行,則執行 go get
會導致錯誤 無法找到主模組
,因為沒有可用的 go.mod
來記錄依賴資訊。
解決方案替代方案包括
-
讓
GO111MODULE
未設定(預設值,或明確設定GO111MODULE=auto
),這會產生更友善的行為。當您在模組之外時,這將提供 Go 1.10 行為,因此會避免go get
回報無法找到主模組
。 -
讓
GO111MODULE=on
,但在需要時暫時停用模組並在go get
期間啟用 Go 1.10 行為,例如透過GO111MODULE=off go get example.com/cmd
。這可以轉換成簡單的指令碼或 shell 別名,例如alias oldget='GO111MODULE=off go get'
-
建立一個暫時的
go.mod
檔案,然後捨棄。這已由 一個簡單的 shell 指令碼 自動化,作者為 @rogpeppe。此指令碼允許透過vgoget example.com/cmd[@version]
選擇性地提供版本資訊。(這可以作為避免錯誤cannot use path@version syntax in GOPATH mode
的解決方案)。 -
gobin
是一個模組感知指令,用於安裝和執行主套件。預設情況下,gobin
安裝/執行主套件時不需要先手動建立模組,但使用-m
旗標時,可以指示它使用現有的模組來解析相依性。請參閱gobin
README 和 FAQ 以取得詳細資料和額外的使用案例。 -
建立一個
go.mod
來追蹤您全球安裝的工具,例如在~/global-tools/go.mod
中,並在針對任何全球安裝的工具執行go get
或go install
之前,cd
到該目錄。 -
為每個工具在不同的目錄中建立一個
go.mod
,例如~/tools/gorename/go.mod
和~/tools/goimports/go.mod
,並在針對該工具執行go get
或go install
之前,cd
到適當的目錄。
這個目前的限制將會解決。然而,主要的問題是模組目前是選擇加入的,而完整的解決方案可能會等到 GO111MODULE=on 成為預設行為時才會出現。請參閱 #24250 以取得更多討論,包括此則留言
這最終肯定會奏效。我不確定的是,就版本而言,這究竟會做什麼:它會建立一個暫時的模組根目錄和 go.mod,執行安裝,然後將其丟棄嗎?很可能。但我並不能完全確定,而且就目前而言,我不希望透過讓 vgo 在 go.mod 樹之外執行操作來混淆人們。當然,最終的 go 指令整合必須支援這一點。
此常見問題解答討論追蹤全域安裝的工具。
如果您想追蹤特定模組所需的工具,請參閱下一個常見問題解答。
如何追蹤模組的工具相依性?
如果您
- 在處理模組時想要使用基於 go 的工具(例如
stringer
),而且 - 希望確保每個人都使用相同版本的該工具,同時在模組的
go.mod
檔案中追蹤該工具的版本
那麼目前建議的方法之一是將 tools.go
檔案新增到模組中,其中包含對目標工具的 import 陳述式(例如 import _ "golang.org/x/tools/cmd/stringer"
),以及 //go:build tools
建置約束。import 陳述式允許 go
指令在模組的 go.mod
中精確記錄工具的版本資訊,而 //go:build tools
建置約束可防止您的正常建置實際上匯入您的工具。
有關如何執行此操作的具體範例,請參閱此 “Go 模組範例” 逐步解說。
有關此方法的討論以及如何執行此操作的較早具體範例,請參閱 此 #25922 中的留言。
簡要的理由(也來自 #25922)
我認為 tools.go 檔案事實上是工具相依性的最佳實務,對於 Go 1.11 來說更是如此。
我喜歡它,因為它沒有引入新的機制。
它只是重複使用現有的機制。
你也可以(自 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
會自動複製適當的存放庫,並將必要的replace
指令新增到您的go.mod
- 使用
gohack undo
移所有 gohack replace 陳述式 - 此專案持續擴充,以便簡化其他模組相關工作流程
- 一個新的社群工具,用於自動化和大幅簡化
- github.com/marwan-at-work/mod
- 用於自動升級/降級模組主要版本的命令列工具
- 自動調整 go 原始碼中的
go.mod
檔案和相關匯入陳述式 - 協助升級,或在首次選擇使用 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
讓頂層模組可以控制相依性所使用的確切版本,例如
替換 example.com/some/dependency => example.com/some/dependency v1.2.3
替換
也允許使用分岔的相依性,例如
替換 example.com/some/dependency => example.com/some/dependency-fork v1.2.3
您也可以參考分支,例如
替換 example.com/some/dependency => example.com/some/dependency-fork master
一個範例使用案例是如果您需要修復或調查相依性中的某個項目,您可以在本機建立分岔並在頂層 go.mod
中新增類似以下的項目
替換 example.com/original/import/path => /your/forked/import/path
替換
也可用於告知 go 工具組在多模組專案中模組的相對或絕對磁碟位置,例如
替換 example.com/project/foo => ../foo
注意:如果 替換
指令的右側是一個檔案系統路徑,則目標必須在該位置有一個 go.mod
檔案。如果沒有 go.mod
檔案,您可以使用 go mod init
建立一個。
一般來說,您可以在替換指令中 =>
的左側指定版本,但如果您省略它,通常對變更較不敏感(例如,如上述所有 替換
範例中所做的那樣)。
對於每個直接相依性的 替換
指令,都需要一個 需要
指令。當從檔案系統路徑替換相依性時,對應需要指令的版本基本上會被忽略;在這種情況下,偽版本 v0.0.0
是個不錯的選擇,可以讓這一點更清楚,例如 需要 example.com/module v0.0.0
。
您可以執行 go list -m all
來確認您取得預期的版本,它會顯示實際上將在您的建置中使用的最終版本,包括考量 替換
陳述。
請參閱 「go mod edit」文件 以取得更多詳細資訊。
github.com/rogpeppe/gohack 讓這些類型的工作流程變得容易許多,特別是如果您的目標是擁有模組相依性的可變結帳。請參閱 儲存庫 或緊接在前的常見問題集以取得概觀。
請參閱下一個常見問題集以取得使用 replace
完全在 VCS 外部工作的詳細資訊。
是否可以在我的本機檔案系統上完全在 VCS 之外工作?
是的。不需要 VCS。
如果您一次想在 VCS 外部編輯單一模組(而且您總共只有一個模組,或其他模組駐留在 VCS 中),這非常簡單。在這種情況下,您可以將包含單一 go.mod
的檔案樹放置在方便的位置。您的 go build
、go test
和類似指令會運作,即使您的單一模組在 VCS 外部(不需要在您的 go.mod
中使用任何 replace
)。
如果您想在您的本機磁碟上擁有多個相互關聯的模組,而且您想同時編輯這些模組,那麼 replace
指令就是一種方法。以下是使用 replace
搭配相對路徑的 go.mod
範例,將 hello
模組指向 goodbye
模組的磁碟上位置(不依賴任何 VCS)
module example.com/me/hello
require (
example.com/me/goodbye v0.0.0
)
replace example.com/me/goodbye => ../goodbye
這個 討論串 顯示了一個小型可執行範例。
如何將供應商與模組搭配使用?供應商會消失嗎?
最初一系列的 vgo
部落格文章確實提議完全捨棄供應,但 來自社群的意見回饋 導致保留了對供應的支持。
簡而言之,要將供應與模組一起使用
go mod vendor
會將主模組的 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
環境變數。
1.10 等較舊版本的 Go 了解如何使用 go mod vendor
建立的 vendor 目錄,當 模組模式 已停用時,Go 1.11 和 1.12+ 也是如此。因此,供應是一種模組提供相依性給不完全了解模組的較舊版本 Go,以及尚未自行啟用模組的使用者的方法。
如果您考慮使用供應,建議閱讀提示文件中的 「模組和供應」 和 「建立相依性的供應副本」 部分。
是否有「始終開啟」的模組存放庫和企業代理伺服器?
公開託管的「持續開啟」不可變模組儲存庫和選擇性的私人託管代理和儲存庫正變得可用。
例如
- proxy.golang.org - 官方專案 - 由 Google 執行 - 由 Go 團隊建置的預設 Go 模組代理。
- proxy.golang.com.cn - 中國代理專案 - 由 中國 Go 貢獻者俱樂部 執行 - 中國 Go 模組代理。
- mirrors.tencent.com/go - 商業專案 - 由 騰訊雲 執行 - Go 模組代理備用。
- mirrors.aliyun.com/goproxy - 商業專案 - 由 阿里雲 執行 - Go 模組代理備用。
- goproxy.cn - 開源專案 - 由 七牛雲 執行 - 中國最受信任的 Go 模組代理。
- goproxy.io - 開源專案 - 由 中國 Go 貢獻者俱樂部 執行 - Go 模組的全球代理。
- Athens - 開源專案 - 自行託管 - Go 模組資料儲存庫和代理。
- athens.azurefd.net - 開源專案 - 執行 Athens 的託管模組代理。
- Goproxy - 開源專案 - 自行託管 - 簡約的 Go 模組代理處理程式。
- THUMBAI - 開源專案 - 自行託管 - Go 模組代理伺服器和 Go 虛榮匯入路徑伺服器。
請注意,您不需要執行代理程式。相反地,1.11 中的 go 工具已透過 GOPROXY 新增了選用的代理程式支援,以啟用更多企業使用案例(例如更大的控制權),並能更好地處理「GitHub 已關閉」或人們刪除 GitHub 儲存庫等情況。
我可以控制 go.mod 何時更新,以及 go 工具何時使用網路來滿足相依性嗎?
預設情況下,像 go build
的指令會視需要連線網路,以滿足匯入。
有些團隊會希望禁止 go 工具在特定時間點連線網路,或希望在 go 工具更新 go.mod
、取得相依性,以及如何使用供應商時,能有更大的控制權。
go 工具提供了相當大的彈性,可以調整或停用這些預設行為,包括透過 -mod=readonly
、-mod=vendor
、GOFLAGS
、GOPROXY=off
、GOPROXY=file:///filesystem/path
、go mod vendor
,以及 go mod download
。
這些選項的詳細資料散布在官方文件當中。社群嘗試將與這些行為相關的旋鈕進行彙整的概觀,請見 此處,其中包含連結至官方文件,以取得更多資訊。
如何將模組與 CI 系統(例如 Travis 或 CircleCI)搭配使用?
最簡單的方法可能是設定環境變數 GO111MODULE=on
,這應該適用於大多數 CI 系統。
不過,在 CI 中執行測試時,最好在啟用和停用模組的情況下執行 Go 1.11,因為有些使用者尚未選擇加入模組。供應商也是需要考慮的主題。
以下兩篇部落格文章更具體地說明這些主題
- 「使用 Travis CI 上的供應商支援的 Go 模組」,作者:Fatih Arslan
- 「Go 模組和 CircleCI」,作者:Todd Keech
如何下載建置特定套件或測試所需的模組?
go mod download
指令(或等效的 go mod download all
)會下載建置清單中的所有模組(由 go list -m all
回報)。由於完整的建置清單包含其他模組的測試相依項和工具相依項等項目,因此許多這些模組並不需要來建置主模組中的套件。因此,使用 go mod download
準備的 Docker 映像可能會比必要的還大。
請考慮改用 go list
。例如,go list ./...
會下載建置套件 ./...
所需的模組(當從模組根目錄執行時,主模組中的套件組)。
若要同時下載測試相依項,請使用 go list -test ./...
。
預設情況下,go list
只會考慮當前平台所需的相依項。您可以設定 GOOS
和 GOARCH
,讓 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 提示文件)。
go mod tidy
會更新您目前的 go.mod
,以納入模組中測試所需的相依性,如果測試失敗,我們必須知道使用哪些相依性才能重現失敗。
go mod tidy
也會確保您目前的 go.mod
反映所有作業系統、架構和建置標籤的可能組合的相依性需求(如 此處 所述)。相反地,其他命令,例如 go build
和 go test
,只會更新 go.mod
以提供在目前的 GOOS
、GOARCH
和建置標籤下,由所要求的套件所匯入的套件(這是 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 -u
或 go get foo@1.2.3
。go 工具需要一個地方來記錄這些新版本,它會在您的 go.mod
檔案中執行此動作(而且它不會深入您的依賴項來修改它們的 go.mod
檔案)。
一般而言,上述行為是模組如何透過記錄精確的依賴項資訊來提供 100% 可重現的建置和測試的一部分。
如果您好奇特定模組為何會出現在您的 go.mod
中,您可以執行 go mod why -m <module>
來回答這個問題。其他用於檢查需求和版本的實用工具包括 go mod graph
和 go list -m all
。
「go.sum」是鎖定檔嗎?為什麼「go.sum」包含我已不再使用的模組版本的資訊?
不,go.sum
不是鎖定檔案。建置中的 go.mod
檔案提供了足夠的資訊,可進行 100% 可重現的建置。
為了驗證目的,go.sum
包含特定模組版本內容的預期加密檢查碼。請參閱 下方常見問題 以取得有關 go.sum
的更多詳細資訊(包括為何您通常應簽入 go.sum
)以及提示文件中的 「模組下載和驗證」 部分。
此外,模組的 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
。請參閱提示文件中的 「模組下載和驗證」 部分以取得更多詳細資訊。請參閱在 #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 foo
或 go 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
),下列核心原則永遠為真
- 套件的匯入路徑定義套件的身分。
- 具有不同匯入路徑的套件會被視為不同的套件。
- 具有相同匯入路徑的套件會被視為相同的套件(即使 VCS 標籤表示套件具有不同的主要版本,這也是真的)。
- 沒有
/vN
的匯入路徑會被視為 v1 或 v0 模組(即使匯入的套件尚未選擇加入模組,而且 VCS 標籤表示主要版本大於 1,這也是真的)。 - 模組
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
本身並未在 oldpackage
內擁有 go.mod
檔案,因此並未選擇加入模組。換句話說,即使 oldpackage
有一個 semver 標籤 v3.0.1
,oldpackage
也不會被授予 語意匯入版本管理 的權利和責任(例如在匯入路徑中使用 /vN
),因為 oldpackage
尚未表示希望這麼做。
+incompatible
字尾表示 oldpackage
的 v3.0.1
版本尚未主動選擇加入模組,因此假設 oldpackage
的 v3.0.1
版本不了解語意匯入版本管理或如何在匯入路徑中使用主要版本。因此,在 模組模式 中執行時,go
工具會將非模組的 oldpackage
的 v3.0.1
版本視為 oldpackage
的 v1 版本系列的(不兼容)延伸,並假設 oldpackage
的 v3.0.1
版本不了解語意匯入版本管理,而 +incompatible
字尾表示 go
工具正在這麼做。
根據語意匯入版本管理,oldpackage
的 v3.0.1
版本被視為 v1 發行系列的一部分,這表示例如版本 v1.0.0
、v2.0.0
和 v3.0.1
始終使用相同的匯入路徑匯入
import "oldpackage"
再次注意,oldpackage
的結尾並未使用 /v3
。
一般而言,具有不同匯入路徑的套件是不同的套件。在此範例中,假設 oldpackage 的版本為 v1.0.0、v2.0.0 和 v3.0.1,則會使用相同的匯入路徑匯入所有版本,因此,組建會將它們視為同一個套件(再次強調,這是因為 oldpackage 尚未選擇加入語意化匯入版本控制),在任何給定的組建中,只會有一個 oldpackage 副本。(所使用的版本會是任何 require 指令中所列版本中語意化最高的版本;請參閱 「版本選擇」)。
如果我們假設稍後建立了一個新的 oldpackage v4.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/v2` 或 `mymodule/v3`,並將適當的套件移至或複製到這些子目錄下。這表示傳統的匯入路徑邏輯(即使在較舊的 Go 版本中,例如 Go 1.8 或 1.7)在看到類似於 `import "mymodule/v2/mypkg"` 的匯入陳述式時,將會找到適當的套件。因此,即使未啟用模組支援(無論是因為您執行 Go 1.11 且未啟用模組,還是因為您執行較舊的版本,例如 Go 1.7、1.8、1.9 或 1.10,這些版本不具備完整的模組支援),位於「主要子目錄」v2+ 模組中的套件仍會被找到並使用。請參閱 「如何發佈 v2+ 模組」 區段,以取得「主要子目錄」方法的更多詳細資料。
本常見問題集的其餘部分著重於 「如何發布 v2+ 模組」 區段中所述的「主要分支」方法。在「主要分支」方法中,不會建立任何 /vN
子目錄,而是由 go.mod
檔案傳達模組版本資訊,並將 semver 標籤套用至提交(通常會在 master
上,但也可以在不同的分支上)。
為了在目前的過渡期間提供協助,Go 1.11 引入了 「最低模組相容性」,以提供與尚未選擇加入模組的 Go 程式碼有更高的相容性,而「最低模組相容性」也回溯套用至 Go 1.9.7 和 1.10.3(在這些版本中,由於較舊的 Go 版本不支援完整模組,因此這些版本實際上總是停用完整模組模式)。
「最低模組相容性」的主要目標是
-
讓較舊的 Go 版本 1.9.7+ 和 1.10.3+ 能夠更輕鬆地編譯使用語意化匯入版本控管的模組,並在 Go 1.11 中停用 模組模式 時提供相同的行為。
-
讓舊程式碼能夠使用 v2+ 模組,而不需要舊的使用者程式碼在使用 v2+ 模組時立即變更為使用新的
/vN
匯入路徑。 -
在不依賴模組作者建立
/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 語意匯入版本控制系列)。
- 如果套件已選擇加入模組,但在匯入 v2+ 模組時未在匯入路徑中包含主要版本,則當
- 用於實作「最小模組相容性」的機制故意非常狹窄
- 邏輯的整體是 – 在 GOPATH 模式下運行時,如果匯入陳述式在已選擇加入模組的程式碼中(也就是說,具有有效
go.mod
檔案的樹狀結構中.go
檔案中的匯入陳述式),則會在移除/vN
之後再次嘗試無法解析的包含/vN
的匯入陳述式。 - 淨效應是,在 1.9.7+、1.10.3+ 和 1.11 中,模組內部程式碼中的匯入陳述式,例如
import "foo/v2"
,仍會在 GOPATH 模式中正確編譯,且會解析為import "foo"
(不含/v2
),這表示它會使用位於 GOPATH 中的foo
版本,而不會受到額外的/v2
混淆。 - 「最小模組相容性」不會影響其他任何項目,包括它不會影響
go
命令列中使用的路徑(例如go get
或go list
的引數)。
- 邏輯的整體是 – 在 GOPATH 模式下運行時,如果匯入陳述式在已選擇加入模組的程式碼中(也就是說,具有有效
- 此過渡性的「最小模組感知」機制故意打破了「具有不同匯入路徑的套件會被視為不同套件」的規則,以追求非常具體的向後相容性目標,即允許舊程式碼在使用 v2+ 模組時無需修改即可編譯。更詳細一點來說
- 如果舊程式碼使用 v2+ 模組的唯一方法是先變更舊程式碼,這對整個生態系統來說將會是一個更大的負擔。
- 如果我們不修改舊程式碼,則該舊程式碼必須與 v2+ 模組的模組前匯入路徑搭配使用。
- 另一方面,選擇加入模組的新程式碼或已更新程式碼必須對 v2+ 模組使用新的
/vN
匯入。 - 新的匯入路徑不等於舊的匯入路徑,但兩者都可以在單一建置中運作,因此我們有兩個不同的運作匯入路徑解析為同一個套件。
- 例如,在 GOPATH 模式中操作時,出現在基於模組的程式碼中的
import "foo/v2"
會解析為存在於您的 GOPATH 中的與import "foo"
相同的程式碼,而建置最終會產生一個foo
副本,特別是 GOPATH 中磁碟上的任何版本。這允許基於模組的程式碼,即使在 1.9.7+、1.10.3+ 和 1.11 中的 GOPATH 模式中,也能夠使用import "foo/v2"
編譯。
- 相反地,當
go
工具在完整模組模式中操作時- 「具有不同匯入路徑的套件是不同的套件」規則沒有例外(包括在完整模組模式中已修改套件管理,以遵守此規則)。
- 例如,如果
go
工具處於完整模組模式,而foo
是 v2+ 模組,則import "foo"
要求foo
的 v1 版本,而import "foo/v2"
要求foo
的 v2 版本。
如果我建立 go.mod 但未將 semver 標籤套用至我的儲存庫,會發生什麼事?
semver 是模組系統的基礎。為了提供消費者最佳體驗,建議模組作者套用 semver VCS 標籤(例如,v0.1.0
或 v1.2.3-rc.1
),但 semver VCS 標籤並非絕對必要
-
模組必須遵循semver 規範,才能讓
go
指令按照文件說明執行。這包括遵循 semver 規範,關於如何以及何時允許重大變更。 -
沒有 semver VCS 標籤 的模組,由消費者使用 偽版本 形式的 semver 版本記錄。通常這會是 v0 主要版本,除非模組作者按照 “主要子目錄” 方法建構 v2+ 模組。
-
因此,未套用 semver VCS 標籤且未建立「主要子目錄」的模組,實際上宣告自己屬於 semver v0 主要版本系列,而基於模組的使用者會將其視為具有 semver v0 主要版本。
模組是否能依賴其本身的不同版本?
模組可以依賴其自身不同主要版本:大致上,這類似於依賴不同模組。這可能出於不同原因,包括允許模組的主要版本實作為不同主要版本周圍的 shim。
此外,模組可以循環依賴其自身不同主要版本,就像兩個完全不同的模組可以循環依賴彼此一樣。
然而,如果您不預期模組會依賴其自身不同版本,這可能是錯誤的徵兆。例如,打算從 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
圖。頂層模組的路徑是另一個模組路徑的前置詞。
此儲存庫包含兩個模組。但是,模組「my-repo」是模組「my-repo/mig」路徑的前置詞。
我是否應在單一儲存庫中擁有多個模組?
在這樣的組態中新增模組、移除模組和模組版本控制需要相當的謹慎和深思熟慮,因此管理單一模組儲存庫幾乎總是比在現有儲存庫中管理多個模組更簡單。
Russ Cox 在 #26664 中評論
對於所有非進階使用者,您可能希望採用通常的慣例,即一個儲存庫等於一個模組。對於程式碼儲存選項的長期演進而言,一個儲存庫可以包含多個模組非常重要,但這幾乎肯定不是您預設想做的事。
多模組如何造成更多工作量的兩個範例
- 從儲存庫根目錄執行的
go test ./...
將不再測試儲存庫中的所有內容 - 您可能需要透過
replace
指令常規管理模組之間的關係。
但是,除了這兩個範例之外,還有其他細微差別。如果您考慮在單一儲存庫中有多個模組,請仔細閱讀這個 小節 中的常見問題。
在儲存庫中有多個 go.mod
可能有意義的兩個範例情境
-
如果你有使用範例,而範例本身有一組複雜的相依性(例如,你可能有一個小型套件,但包含一個使用你的套件與 Kubernetes 的範例)。在這種情況下,讓你的儲存庫有一個有自己
go.mod
的example
或_example
目錄是有意義的,例如 這裡 所示。 -
如果你有一個有複雜相依性的儲存庫,但你有一個相依性較少的用戶端 API。在某些情況下,讓一個有自己
go.mod
的api
或clientapi
或類似目錄是有意義的,或將那個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」會在編譯時造成「模稜兩可的匯入」錯誤。
解決此問題的方法是讓新增加的模組依賴於它被「分割出來」的模組,版本必須在分割出來之後。
讓我們使用上述儲存庫來逐步執行此操作,假設「github.com/my-repo」目前為 v1.2.3
-
新增 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
-
git commit
-
git tag v1.3.0
-
git tag mig/v1.0.0
-
接下來,我們來測試這些。我們無法天真地使用
go build
或go test
,因為 go 指令會嘗試從模組快取中擷取每個依賴模組。因此,我們需要使用取代規則,讓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
-
git push origin master v1.3.0 mig/v1.0.0
推送 commit 和兩個標籤
請注意,未來 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
,因為該功能通常稱為「模組」)。
- 注意:你永遠不會將
- 如果預計要使用供應商,請檢查是否將
-mod=vendor
旗標傳遞給go build
或類似旗標,或設定GOFLAGS=-mod=vendor
。- 預設情況下,模組會忽略
vendor
目錄,除非您要求go
工具使用vendor
。
- 預設情況下,模組會忽略
- 經常會檢查
go list -m all
來查看為您的建置選取的實際版本清單會很有幫助- 與其僅查看
go.mod
檔案相比,go list -m all
通常會提供給您更多詳細資訊。
- 與其僅查看
- 如果執行
go get foo
失敗,或者如果go build
在特定套件foo
上失敗,則經常會檢查go get -v foo
或go 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 中卻不常見。
- 較舊版本的 git 是
- Go 1.11 中的模組快取有時會導致各種錯誤,特別是在先前有網路問題或多個
go
指令同時執行時(請參閱 #26794,Go 1.12 已解決此問題)。作為一個故障排除步驟,你可以將 $GOPATH/pkg/mod 複製到備份目錄(以防稍後需要進一步調查),執行go clean -modcache
,然後查看原始問題是否仍然存在。 - 如果你正在使用 Docker,檢查你是否可以在 Docker 外部重現此行為會很有幫助(如果此行為只發生在 Docker 中,上述要點清單可以用作在 Docker 內部與外部之間比較結果的起點)。
你目前正在檢查的錯誤可能是因為你的建置中沒有特定模組或套件的預期版本所導致的次要問題。因此,如果特定錯誤的原因不明顯,根據下一個常見問題解答中所述,檢查你的版本會很有幫助。
如果我未看到預期的依賴項版本,我可以檢查哪些事項?
-
一個好的第一步是執行
go mod tidy
。這有可能可以解決問題,但它也有助於讓你的go.mod
檔案與.go
原始碼保持一致狀態,這將有助於讓任何後續調查更容易。(如果go mod tidy
本身以你意想不到的方式變更依賴項的版本,請先閱讀 關於「go mod tidy」的這個常見問題解答。如果這沒有解釋清楚,你可以嘗試重設你的go.mod
,然後執行go list -mod=readonly all
,這可能會提供更具體的訊息,說明需要變更其版本的任何內容)。 -
第二個步驟通常應該是檢查
go list -m all
以查看為你的建置選定的實際版本清單。go list -m all
會顯示你選定的最終版本,包括間接依賴項,以及在為任何共用依賴項解析版本後。它也會顯示任何replace
和exclude
指令的結果。 -
一個好的下一步是檢查
go mod graph
或go 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 模組範例」逐步操作中看到更詳細的命令和範例,用於查詢您的模組。
發生意外版本的原因之一可能是有人建立了無效或意外的 go.mod
檔案,這並非預期,或相關錯誤(例如:模組的 v2.0.1
版本可能在 go.mod
中錯誤宣告自己是 module foo
,而沒有必要的 /v2
;.go
程式碼中用於匯入 v3 模組的匯入陳述式可能遺漏必要的 /v3
;go.mod
中用於 v4 模組的 require
陳述式可能遺漏必要的 /v4
)。因此,如果你看到的特定問題原因不明顯,可以先重新閱讀上方 「go.mod」 和 「語意匯入版本管理」 區段的資料(因為其中包含模組必須遵循的重要規則),然後花幾分鐘時間檢查最相關的 go.mod
檔案和匯入陳述式。
為什麼我會收到「找不到提供套件 foo 的模組」錯誤訊息?
這是可能會發生於多個不同根本原因的通用錯誤訊息。
在某些情況下,此錯誤只是因為路徑輸入錯誤,因此第一步應該是根據錯誤訊息中列出的詳細資料,仔細檢查不正確的路徑。
如果你尚未執行,通常下一步是嘗試 go get -v foo
或 go get -v -x foo
- 一般來說,
go get
通常會提供比go build
更詳細的錯誤訊息。 - 請參閱本區段中第一個疑難排解常見問答 上方,以取得更多詳細資料。
其他一些可能原因
-
如果你已發出
go build
或go build .
,但目前的目錄中沒有任何.go
原始檔,你可能會看到錯誤找不到提供 foo 套件的模組
。如果你遇到這種情況,解決方法可能是使用其他呼叫,例如go build ./...
(其中./...
會擴充為符合目前模組中的所有套件)。請參閱 #27122。 -
Go 1.11 中的模組快取可能會導致此錯誤,包括在遇到網路問題或多個
go
命令同時執行的情況下。這已在 Go 1.12 中解決。請參閱本節中的第一個疑難排解常見問題 以上,以取得更多詳細資料和可能的更正步驟。
為什麼「go mod init」會出現「無法確定來源目錄的模組路徑」錯誤訊息?
go mod init
在沒有任何引數的情況下,將嘗試根據不同的提示(例如 VCS 元資料)猜測適當的模組路徑。然而,預期 go mod init
並非總是能夠猜測出適當的模組路徑。
如果 go mod init
給你這個錯誤,表示那些啟發法無法猜測,你必須自己提供模組路徑(例如 go mod init github.com/you/hello
)。
我有一個複雜的相依性問題,它尚未選擇加入模組。我可以使用其目前相依性管理員的資訊嗎?
是的。這需要一些手動步驟,但在一些較複雜的情況下可能會有幫助。
當你初始化自己的模組時執行 go mod init
,它會自動從先前的相依性管理員轉換,方法是將設定檔(例如 Gopkg.lock
、glide.lock
或 vendor.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,這是一個針對此使用案例的社群工具。此外,您會想要將 require github.com/some/nonmodule v1.2.3
新增至實際 go.mod
,以符合您手動複製的版本。
在這個 #28489 留言 中,有一個具體範例說明如何針對 Docker 使用此技術,其中說明如何取得一組一致的 Docker 依賴項版本,以避免 github.com/sirupsen/logrus
與 github.com/Sirupsen/logrus
之間的大小寫敏感問題。
我該如何解決「剖析 go.mod:意外的模組路徑」和「載入模組需求時發生錯誤」,這些錯誤是由於匯入路徑與宣告的模組身分不符所造成?
為什麼會發生此錯誤?
一般來說,模組會透過 go.mod
中的 module
指示宣告其身分,例如 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/logrus
變更為 github.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
的存放庫(從錯誤的右側),您可以查看 go.mod
檔案的最新版本或 master
,看看它是否在 go.mod
的第一行宣告自己是 module example.com/some/NEW/name
。如果是,那表示您看到的是「舊模組名稱」與「新模組名稱」的問題。
本節的其餘部分著重於解決此錯誤的「舊名稱」與「新名稱」形式,方法是依序執行下列步驟
-
檢查您自己的程式碼,看看您是否使用
example.com/some/OLD/name
進行匯入。如果是,請更新您的程式碼,使用example.com/some/NEW/name
進行匯入。 -
如果您在升級期間收到此錯誤,您應該嘗試使用 Go 的提示版本進行升級,它有更明確的升級邏輯 (#26902),通常可以避開這個問題,而且通常也有更好的錯誤訊息。請注意,提示 / 1.13 中的
go get
引數與 1.12 中的不同。取得提示並使用它來升級您的相依項的範例
go get golang.org/dl/gotip && gotip download
gotip get -u all
gotip mod tidy
由於有問題的舊匯入通常在間接相依關係中,使用提示升級,然後執行 go mod tidy
可以經常讓您升級到問題版本,然後再從 go.mod
中移除問題版本,因為不再需要,當您回到使用 Go 1.12 或 1.11 進行日常使用時,這會讓您進入運作狀態。例如,請看此方法運作 在此處,以升級超過 github.com/golang/lint
與 golang.org/x/lint
問題。
-
如果您在執行
go get -u foo
或go get -u foo@latest
時收到此錯誤,請嘗試移除-u
。這會提供foo@latest
使用的相依關係集合,而不會將foo
的相依關係升級到foo
作者在發布foo
時驗證為運作的版本。這在過渡期間特別重要,因為foo
的某些直接和間接相依關係可能尚未採用 semver 或模組。(一個常見的錯誤是認為go get -u foo
僅取得foo
的最新版本。實際上,go get -u foo
或go get -u foo@latest
中的-u
表示也取得foo
的所有直接和間接相依關係的最新版本;這可能是您想要的,但如果它因深層間接相依關係而失敗,則可能不是您想要的)。 -
如果上述步驟無法解決錯誤,則下一種方法稍微複雜一些,但通常應可解決此錯誤的「舊名稱」與「新名稱」形式。這僅使用錯誤訊息本身的資訊,以及簡要查看一些 VCS 歷史記錄。
4.1. 前往
example.com/some/NEW/name
儲存庫4.2. 確定
go.mod
檔案在那裡引入的時間(例如,透過查看go.mod
的責備或歷史記錄檢視)。4.3. 選擇
go.mod
檔案引入前的版本或提交。4.4. 在
go.mod
檔案中,在replace
陳述式的兩側加入replace
陳述式,使用舊名稱:replace example.com/some/OLD/name => example.com/some/OLD/name <go.mod 前的版本>
使用我們先前的範例,其中github.com/Quasilyte/go-consistent
是舊名稱,而github.com/quasilyte/go-consistent
是新名稱,我們可以看到go.mod
最初是在提交 00c5b0cf371a 中引入的。該儲存庫未使用 semver 標籤,因此我們將採用緊接在前的提交 00dd7fb039e,並將其新增至replace
,在replace
的兩側都使用舊的大寫 Quasilyte 名稱
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 中有追蹤)。
- 如果上述步驟沒有解決問題,可能是因為有問題的舊匯入路徑仍然被一個或多個依賴項的最新版本使用中。在這種情況下,找出仍然在使用有問題的舊匯入路徑的人,並找到或開啟一個問題,要求有問題的匯入者變更為使用現在正規的匯入路徑。在步驟 2 中使用
gotip
可能可以找出有問題的匯入者,但並非在所有情況下都能做到,特別是對於升級(#30661)。如果不明確是誰使用有問題的舊匯入路徑進行匯入,你通常可以透過建立一個乾淨的模組快取,執行觸發錯誤的操作或作業,然後在模組快取中搜尋舊的、有問題的匯入路徑來找出。例如
export GOPATH=$(mktemp -d)
go get -u foo # peform operation that generates the error of interest
cd $GOPATH/pkg/mod
grep -R --include="*.go" github.com/Quasilyte/go-consistent
- 如果這些步驟不足以解決問題,或者您是專案的維護者,由於循環參考而無法移除對舊有問題匯入路徑的參考,請參閱單獨的 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,其中包括
在模組中,子目錄終於有名稱了。如果父目錄表示「模組 m」,則子目錄會匯入為「m/subdir」,不再是「./subdir」。
某些必要的檔案可能不存在於已填入的供應商目錄中
沒有 .go
檔案的目錄不會由 go mod vendor
複製到 vendor
目錄中。這是設計使然。
簡而言之,撇開任何特定供應商行為,go 建置的整體模型是,建置套件所需檔案應與 .go
檔案放在同一個目錄中。
使用 cgo 的範例,修改其他目錄中的 C 原始碼不會觸發重新建置,而建置會使用過期的快取項目。cgo 文件現在 包含
請注意,其他目錄中的檔案變更不會導致套件重新編譯,因此套件的所有非 Go 原始碼都應儲存在套件目錄中,而不是子目錄中。
社群工具 https://github.com/goware/modvendor 讓您可以輕鬆地將一組完整的 .c、.h、.s、.proto 或其他檔案從模組複製到 vendor
目錄。儘管這可能有所幫助,但如果您有建置套件所需的檔案位於 .go
檔案目錄之外,則必須小心確保一般而言您的 go 建置已妥善處理(無論是否供應)。
請參閱 #26366 中的額外討論。
傳統供應的另一種方法是檢查模組快取。它最終可能與傳統供應具有相似的優點,並且在某些方面會得到更高保真度的副本。此方法被解釋為「範例 Go 模組」逐步解說。
此內容是 Go Wiki 的一部分。