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 發行說明 以取得詳細資訊。

Go 1.15

請參閱 Go 1.15 發行說明 以取得詳細資訊。

Go 1.14

請參閱 Go 1.14 發行說明 以取得詳細資訊。

Go 1.13

有關詳細資訊,請參閱 Go 1.13 發行說明

目錄

「快速入門」和「新概念」部分對於開始使用模組的人來說特別重要。「如何…」部分涵蓋更多關於機制的詳細資訊。此頁面中數量最多的內容是常見問題解答,回答更具體的問題;至少瀏覽這裡列出的常見問題解答一覽表是值得的。

快速入門

範例

本頁面其餘部分將涵蓋詳細資訊,但以下是一個從頭開始建立模組的簡單範例。

在 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.2semver 標籤

$ 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 檔案的隱式修改。

您典型的日常工作流程可以是

簡要說明其他您可能使用的常見功能

在閱讀完接下來關於「新概念」的四個章節後,您將擁有足夠的資訊,可以開始使用模組來執行大部分專案。檢閱上方〈目錄〉(包括其中的常見問題解答)也很有幫助,以便熟悉更詳細主題的清單。

新概念

這些章節提供關於主要新概念的高階簡介。如需更多詳細資訊和原理,請參閱這段由 Russ Cox 說明設計背後哲學的 40 分鐘簡介〈影片〉、〈官方提案文件〉,或更詳細的初始〈vgo 部落格系列文章〉。

模組

模組 是相關 Go 套件的集合,這些套件會作為單一單元一起進行版本控制。

模組會記錄精確的相依性需求,並建立可重複的建置。

最常見的情況是,版本控制存放庫在存放庫根目錄中定義一個模組。(單一存放庫中支援多個模組,但通常會比每個存放庫一個模組產生更多持續性的工作)。

總結存放庫、模組和套件之間的關係

模組必須根據 semver 進行語意化版本控制,通常格式為 v(主版本).(次要版本).(修補版本),例如 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
)

模組透過 go.mod 中的 module 指令宣告其身分,此指令提供 *模組路徑*。模組中所有套件的匯入路徑都將模組路徑作為共同字首。模組路徑和從 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」)會自動查詢適當的模組,並將該新直接相依項的最高版本新增到您的模組的 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 模組的程式碼必須遵守這些規則

一般來說,具有不同匯入路徑的套件是不同的套件。例如,math/rand 是與 crypto/rand 不同的套件。如果不同的匯入路徑是因為匯入路徑中出現不同的主要版本,也是如此。因此 example.com/my/mod/mypkg 是與 example.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 或更高版本)」 部分。

如何使用模組

如何安裝和啟用模組支援

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

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

如何定義模組

為現有專案建立 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
    

    此步驟會從任何現有的 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 ./... 或類似指令為止,這是本區段中顯示的順序)。

  3. 建置模組。當從模組的根目錄執行時,./... 模式會比對目前模組中的所有套件。go build 會自動新增遺失或未轉換的相依性,以滿足此特定建置呼叫的匯入需求

    $ go build ./...
    
  4. 測試模組的設定,以確保它能與所選版本搭配使用

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

    $ 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 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 時的常見起點是執行 go get foogo get foo@latest,而不用 -u(在一切正常後,可以考慮 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」工具 自動化。

標記發行前要考慮的一些當前建議最佳實務

釋出模組 (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 版本的範例,這兩個選項為

  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.*.* commit 放置在個別的 v3 分支上。
    • 注意:建立新分支並非必要。如果您之前已在 master 上發布,且偏好於在 master 上標記 v3.0.0,這是一個可行的選項。(不過,請注意在 master 中引入不相容的 API 變更可能會對非模組使用者造成問題,因為在 Go 1.11 之前,go 工具並未察覺 semver,或者在 Go 1.11+ 中未啟用 模組模式 時)。
    • 現有的相依性管理解決方案,例如 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 和非模組使用者

更新現有安裝說明

避免中斷現有的匯入路徑

模組透過 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+ 模組允許在單一建置中有多個主要版本

使用非模組程式碼的模組

使用模組的非模組程式碼

現有 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,那麼您可以立即執行此操作)。

其他資源

文件和提案

簡介材料

其他素材

自 Vgo 初始提案以來的變更

在提案、原型和 beta 程序中,整體社群已建立超過 400 個問題。請持續提供意見回饋。

以下是部分較大的變更和改善事項清單,幾乎所有變更和改善事項都是主要根據社群的意見回饋

GitHub 問題

常見問題

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

require 指令允許任何模組宣告它應該使用相依模組 D 的版本 >= x.y.z 來建置(這可能是因為與模組 D 的版本 < x.y.z 不相容而指定的)。經驗數據顯示 這是用於 depcargo 的主要約束形式。此外,建置中的頂層模組可以 exclude 相依模組的特定版本,或使用不同的程式碼 replace 其他模組。請參閱完整提案以 取得更多詳細資料和依據

版本化模組提案的主要目標之一,是為工具和開發人員新增一個圍繞 Go 程式碼版本的共用詞彙和語意。這為未來宣告其他形式的不相容性奠定基礎,例如可能

什麼時候會取得舊行為與新的基於模組的行為?

一般來說,模組是 Go 1.11 的選用功能,因此舊行為在預設情況下會被保留。

在取得舊的 1.10 現狀行為與新的選用式模組行為時,進行摘要

為什麼透過 go get 安裝工具會失敗,並出現錯誤 無法找到主模組

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

最簡單的解決方案是讓 GO111MODULE 未設定(或等效地明確設定為 GO111MODULE=auto),這可以避免此錯誤。

回想一下模組存在的主要原因之一是記錄精確的依賴資訊。此依賴資訊會寫入您目前的 go.mod。如果您不在具有 go.mod 的檔案樹中,但已透過設定 GO111MODULE=on 告訴 go get 指令在模組模式中執行,則執行 go get 會導致錯誤 無法找到主模組,因為沒有可用的 go.mod 來記錄依賴資訊。

解決方案替代方案包括

  1. GO111MODULE 未設定(預設值,或明確設定 GO111MODULE=auto),這會產生更友善的行為。當您在模組之外時,這將提供 Go 1.10 行為,因此會避免 go get 回報 無法找到主模組

  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 檔案,然後捨棄。這已由 一個簡單的 shell 指令碼 自動化,作者為 @rogpeppe。此指令碼允許透過 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 指令整合必須支援這一點。

此常見問題解答討論追蹤全域安裝的工具。

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

如何追蹤模組的工具相依性?

如果您

那麼目前建議的方法之一是將 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 中普及。

例如

其他工具,例如 goimports、guru、gorename 和類似工具的狀態會在概括議題 #24661 中追蹤。請參閱該概括議題以取得最新狀態。

特定工具的一些追蹤議題包括

一般而言,即使您的編輯器、IDE 或其他工具尚未支援模組,如果您在 GOPATH 內使用模組並執行 go mod vendor,它們的大部分功能都應該可以使用模組(因為這樣會透過 GOPATH 選取適當的相依性)。

完整的修正方法是將載入套件的程式從 go/build 移到 golang.org/x/tools/go/packages,它了解如何以支援模組的方式找出套件。這最終可能會變成 go/packages

常見問題 — 其他控制

有哪些社群工具可供使用模組?

社群開始在模組上建置工具。例如

我應該在什麼時候使用 replace 指令?

如上方的 「go.mod」概念區段 所述,replace 指令在頂層 go.mod 中提供額外的控制,以實際用來滿足在 Go 原始碼或 go.mod 檔案中找到的相依性,而主模組以外的模組中的 replace 指令在建置主模組時會被忽略。

replace 指令讓您可以提供另一個匯入路徑,該路徑可能是位於 VCS (GitHub 或其他位置) 中的另一個模組,或位於您的本機檔案系統中,並具有相對或絕對檔案路徑。來自 replace 指令的新匯入路徑會在不需要更新實際原始碼中的匯入路徑的情況下使用。

replace 讓頂層模組可以控制相依性所使用的確切版本,例如

替換 也允許使用分岔的相依性,例如

您也可以參考分支,例如

一個範例使用案例是如果您需要修復或調查相依性中的某個項目,您可以在本機建立分岔並在頂層 go.mod 中新增類似以下的項目

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

注意:如果 替換 指令的右側是一個檔案系統路徑,則目標必須在該位置有一個 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 buildgo 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 部落格文章確實提議完全捨棄供應,但 來自社群的意見回饋 導致保留了對供應的支持。

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

1.10 等較舊版本的 Go 了解如何使用 go mod vendor 建立的 vendor 目錄,當 模組模式 已停用時,Go 1.11 和 1.12+ 也是如此。因此,供應是一種模組提供相依性給不完全了解模組的較舊版本 Go,以及尚未自行啟用模組的使用者的方法。

如果您考慮使用供應,建議閱讀提示文件中的 「模組和供應」「建立相依性的供應副本」 部分。

是否有「始終開啟」的模組存放庫和企業代理伺服器?

公開託管的「持續開啟」不可變模組儲存庫和選擇性的私人託管代理和儲存庫正變得可用。

例如

請注意,您不需要執行代理程式。相反地,1.11 中的 go 工具已透過 GOPROXY 新增了選用的代理程式支援,以啟用更多企業使用案例(例如更大的控制權),並能更好地處理「GitHub 已關閉」或人們刪除 GitHub 儲存庫等情況。

我可以控制 go.mod 何時更新,以及 go 工具何時使用網路來滿足相依性嗎?

預設情況下,像 go build 的指令會視需要連線網路,以滿足匯入。

有些團隊會希望禁止 go 工具在特定時間點連線網路,或希望在 go 工具更新 go.mod、取得相依性,以及如何使用供應商時,能有更大的控制權。

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

這些選項的詳細資料散布在官方文件當中。社群嘗試將與這些行為相關的旋鈕進行彙整的概觀,請見 此處,其中包含連結至官方文件,以取得更多資訊。

如何將模組與 CI 系統(例如 Travis 或 CircleCI)搭配使用?

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

不過,在 CI 中執行測試時,最好在啟用和停用模組的情況下執行 Go 1.11,因為有些使用者尚未選擇加入模組。供應商也是需要考慮的主題。

以下兩篇部落格文章更具體地說明這些主題

如何下載建置特定套件或測試所需的模組?

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 提示文件)。

go mod tidy 會更新您目前的 go.mod,以納入模組中測試所需的相依性,如果測試失敗,我們必須知道使用哪些相依性才能重現失敗。

go mod tidy 也會確保您目前的 go.mod 反映所有作業系統、架構和建置標籤的可能組合的相依性需求(如 此處 所述)。相反地,其他命令,例如 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)以及提示文件中的 「模組下載和驗證」 部分。

此外,模組的 go.sum 會記錄建置中使用的所有直接和間接相依性的檢查碼(因此您的 go.sum 通常會列出比 go.mod 更多的模組)。

我是否應提交「go.sum」檔案以及「go.mod」檔案?

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

如果我沒有任何依賴項,我是否仍應新增「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 會生效

go 工具在模組模式中時,它會假設非模組 v2+ 套件不了解語意化匯入版本控制,並將其視為套件的 v1 版本系列(不相容)延伸(而 +incompatible 字尾表示 go 工具正在這麼做)。

範例

假設

在這種情況下,例如從模組 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.1oldpackage 也不會被授予 語意匯入版本管理 的權利和責任(例如在匯入路徑中使用 /vN),因為 oldpackage 尚未表示希望這麼做。

+incompatible 字尾表示 oldpackagev3.0.1 版本尚未主動選擇加入模組,因此假設 oldpackagev3.0.1 版本了解語意匯入版本管理或如何在匯入路徑中使用主要版本。因此,在 模組模式 中執行時,go 工具會將非模組的 oldpackagev3.0.1 版本視為 oldpackage 的 v1 版本系列的(不兼容)延伸,並假設 oldpackagev3.0.1 版本不了解語意匯入版本管理,而 +incompatible 字尾表示 go 工具正在這麼做。

根據語意匯入版本管理,oldpackagev3.0.1 版本被視為 v1 發行系列的一部分,這表示例如版本 v1.0.0v2.0.0v3.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+ 模組」 區段中更詳細說明的「主要子目錄」方法,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 版本不支援完整模組,因此這些版本實際上總是停用完整模組模式)。

「最低模組相容性」的主要目標是

  1. 讓較舊的 Go 版本 1.9.7+ 和 1.10.3+ 能夠更輕鬆地編譯使用語意化匯入版本控管的模組,並在 Go 1.11 中停用 模組模式 時提供相同的行為。

  2. 讓舊程式碼能夠使用 v2+ 模組,而不需要舊的使用者程式碼在使用 v2+ 模組時立即變更為使用新的 /vN 匯入路徑。

  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 中的「最小模組相容性」機制

如果我建立 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 主要版本。

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

模組可以依賴其自身不同主要版本:大致上,這類似於依賴不同模組。這可能出於不同原因,包括允許模組的主要版本實作為不同主要版本周圍的 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

Fig. A top-level module&rsquo;s path is a prefix of another module&rsquo;s path.

圖。頂層模組的路徑是另一個模組路徑的前置詞。

此儲存庫包含兩個模組。但是,模組「my-repo」是模組「my-repo/mig」路徑的前置詞。

我是否應在單一儲存庫中擁有多個模組?

在這樣的組態中新增模組、移除模組和模組版本控制需要相當的謹慎和深思熟慮,因此管理單一模組儲存庫幾乎總是比在現有儲存庫中管理多個模組更簡單。

Russ Cox 在 #26664 中評論

對於所有非進階使用者,您可能希望採用通常的慣例,即一個儲存庫等於一個模組。對於程式碼儲存選項的長期演進而言,一個儲存庫可以包含多個模組非常重要,但這幾乎肯定不是您預設想做的事。

多模組如何造成更多工作量的兩個範例

但是,除了這兩個範例之外,還有其他細微差別。如果您考慮在單一儲存庫中有多個模組,請仔細閱讀這個 小節 中的常見問題。

在儲存庫中有多個 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」會在編譯時造成「模稜兩可的匯入」錯誤。

解決此問題的方法是讓新增加的模組依賴於它被「分割出來」的模組,版本必須在分割出來之後。

讓我們使用上述儲存庫來逐步執行此操作,假設「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 指令會嘗試從模組快取中擷取每個依賴模組。因此,我們需要使用取代規則,讓 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 推送 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 檔案,請先仔細檢閱 此多模組儲存庫區段 中的其他常見問題。

常見問題集 — 最小版本選擇

最小版本選擇是否會讓開發人員無法取得重要的更新?

請參閱先前 官方提案討論的常見問題 中的「最低版本選取是否會讓開發人員無法獲得重要的更新?」問題。

常見問題集 — 可能的問題

如果我遇到問題,有哪些一般事項可以進行重點檢查?

你目前正在檢查的錯誤可能是因為你的建置中沒有特定模組或套件的預期版本所導致的次要問題。因此,如果特定錯誤的原因不明顯,根據下一個常見問題解答中所述,檢查你的版本會很有幫助。

如果我未看到預期的依賴項版本,我可以檢查哪些事項?

  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 模組範例」逐步操作中看到更詳細的命令和範例,用於查詢您的模組。

發生意外版本的原因之一可能是有人建立了無效或意外的 go.mod 檔案,這並非預期,或相關錯誤(例如:模組的 v2.0.1 版本可能在 go.mod 中錯誤宣告自己是 module foo,而沒有必要的 /v2.go 程式碼中用於匯入 v3 模組的匯入陳述式可能遺漏必要的 /v3go.mod 中用於 v4 模組的 require 陳述式可能遺漏必要的 /v4)。因此,如果你看到的特定問題原因不明顯,可以先重新閱讀上方 「go.mod」「語意匯入版本管理」 區段的資料(因為其中包含模組必須遵循的重要規則),然後花幾分鐘時間檢查最相關的 go.mod 檔案和匯入陳述式。

為什麼我會收到「找不到提供套件 foo 的模組」錯誤訊息?

這是可能會發生於多個不同根本原因的通用錯誤訊息。

在某些情況下,此錯誤只是因為路徑輸入錯誤,因此第一步應該是根據錯誤訊息中列出的詳細資料,仔細檢查不正確的路徑。

如果你尚未執行,通常下一步是嘗試 go get -v foogo get -v -x foo

其他一些可能原因

為什麼「go mod init」會出現「無法確定來源目錄的模組路徑」錯誤訊息?

go mod init 在沒有任何引數的情況下,將嘗試根據不同的提示(例如 VCS 元資料)猜測適當的模組路徑。然而,預期 go mod init 並非總是能夠猜測出適當的模組路徑。

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

我有一個複雜的相依性問題,它尚未選擇加入模組。我可以使用其目前相依性管理員的資訊嗎?

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

當你初始化自己的模組時執行 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,這是一個針對此使用案例的社群工具。此外,您會想要將 require github.com/some/nonmodule v1.2.3 新增至實際 go.mod,以符合您手動複製的版本。

在這個 #28489 留言 中,有一個具體範例說明如何針對 Docker 使用此技術,其中說明如何取得一組一致的 Docker 依賴項版本,以避免 github.com/sirupsen/logrusgithub.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 中宣告其正規名稱,這可能會導致問題。在此情況下,錯誤可能會在升級時觸發,因為發現升級版本的模組宣告的正規模組路徑不再與舊匯入路徑相符。

範例問題情境

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。如果是,那表示您看到的是「舊模組名稱」與「新模組名稱」的問題。

本節的其餘部分著重於解決此錯誤的「舊名稱」與「新名稱」形式,方法是依序執行下列步驟

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

  2. 如果您在升級期間收到此錯誤,您應該嘗試使用 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/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 的責備或歷史記錄檢視)。

    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 中有追蹤)。

  1. 如果上述步驟沒有解決問題,可能是因為有問題的舊匯入路徑仍然被一個或多個依賴項的最新版本使用中。在這種情況下,找出仍然在使用有問題的舊匯入路徑的人,並找到或開啟一個問題,要求有問題的匯入者變更為使用現在正規的匯入路徑。在步驟 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
  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,其中包括

在模組中,子目錄終於有名稱了。如果父目錄表示「模組 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 的一部分。