Go Wiki: 解決模組路徑更改導致的問題
意外的模組路徑
使用者在使用他們的專案 my-go-project
時,可能會在 go get -u
過程中遇到類似以下的錯誤:
$ cd my-go-project
$ go get -u ./...
[...]
go: github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
[...]
Exit code 1
golang.org/x/lint
是一個模組,在遷移到 git 倉庫 golang.org/x/lint
並將其模組名稱重新命名為 golang.org/x/lint
之前,它的 git 倉庫和模組名稱是 github.com/golang/lint
。Go 工具目前在嘗試理解新 git 倉庫中的舊模組名稱時遇到困難:golang/go#30831。
my-go-project
遇到這個問題是因為 my-go-project
本身或其某個間接依賴在模組圖中存在指向舊的 github.com/golang/lint
模組名稱的路徑。
例如,如果 my-go-project
本身依賴於舊的 github.com/golang/lint
模組名稱
$ GO111MODULE=on go mod graph
my-go-project github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
或者,my-go-project
可能依賴於一箇舊版本的 google.golang.org/grpc
,而該版本又依賴於舊的 github.com/golang/lint
模組名稱
$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
最後,可能 my-go-project
依賴於另一個需要舊版本 google.golang.org/grpc
的依賴項,而該依賴項又依賴於舊的 github.com/golang/lint
模組名稱
$ GO111MODULE=on go mod graph
my-go-project some/dep@v1.2.3
...
another/dep@v1.4.2 google.golang.org/grpc@v1.16.0
google.golang.org/grpc@v1.16.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
移除對該名稱的引用
在 Go 工具更新以理解模組路徑已更改的模組(跟蹤在 golang/go#30831)之前,解決方案是更新模組圖,使其不再存在指向舊模組名稱的路徑。
使用上面的示例,我們將探索如何更新模組圖,使其不再存在指向 github.com/golang/lint
的路徑。
修復第一個示例很簡單,唯一的連結來自使用者可以控制的 my-go-project
。在 go.mod
中將舊位置替換為新位置——將 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
替換為 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
—— 就可以從圖中移除該連結。
$ GO111MODULE=on go mod graph
my-go-project golang.org/x/lint@v0.0.0-20190301231843-5614ed5bae6f
修復第二個示例需要更多步驟,但本質上是相同的過程:google.golang.org/grpc@v1.16.0
提供了指向 github.com/golang/lint
的連結,因此 google.golang.org/grpc
應該更新其 go.mod
,從 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
更改為 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f
(幸運的是,這在 v1.17.0
中已經完成)。然後,my-go-project
應該更新其 go.mod
以包含新版本的 google.golang.org/grpc
,這樣我們現在就有了
$ GO111MODULE=on go mod graph
my-go-project google.golang.org/grpc@v1.17.0
google.golang.org/grpc@v1.17.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
修復第三個示例與第二個示例類似:更新到 another/dep
的新版本,該新版本帶來了新版本的 google.golang.org/grpc
,而該新版本不包含對 github.com/golang/lint
的引用。
太棒了!問題解決了——Go 工具不再有指向 github.com/golang/lint
的路徑需要考慮,因此在 go get -u
過程中不會再遇到這個問題。
更棘手的問題:清除歷史記錄
這一切都很好,應該能解決大多數使用者的問題。
然而,有一種情況會變得相當複雜:當模組依賴圖中存在迴圈時。考慮這個模組依賴圖
並且,假設 some/lib
過去依賴於 github.com/golang/lint
。
讓我們看看包含版本的模組依賴圖
$ go mod graph
my-go-lib some/lib@v1.7.0
some/lib@v1.7.0 some-other/lib@v2.5.3
some/lib@v1.7.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.3 some/lib@v1.6.0
some/lib@v1.6.0 some-other/lib@v2.5.0
some/lib@v1.6.0 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.0 some/lib@v1.3.1
some/lib@v1.3.1 some-other/lib@v2.4.8
some/lib@v1.3.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.4.8 some/lib@v1.3.0
some/lib@v1.3.0 some-other/lib@v2.4.7
some/lib@v1.3.0 github.com/golang/lint@v0.0.0-20180702182130-06c8688daad7
使用 golang.org/x/exp/cmd/modgraphviz 進行視覺化
這裡我們看到,即使 some/lib
的最後幾個版本正確地依賴於 golang.org/x/lint
,但由於 some/lib
和 some-other/lib
共享一個迴圈,因此很可能存在一條追溯到很早以前的路徑。
之所以出現這樣的路徑,是因為版本更新的過程通常是原子性的:當 some/lib
更新其 some-other/lib
的版本併發布自身的新版本時,some-other/lib
的最新版本仍然依賴於 some/lib
的前一個版本。也就是說,單獨更新這兩個庫中的任何一個都不足以消除歷史鏈。
要永久移除歷史鏈並清除圖中的舊 github.com/golang/lint
引用,兩個庫必須同時更新彼此的版本。
原子性地更新兩個庫的版本
解決 github.com/golang/lint
問題的方法是,首先確保 some/lib
不依賴於 github.com/golang/lint
,然後將 some/lib
和 some-other/lib
的版本都更新到彼此不存在的未來版本。我們想要這樣的圖:
my-go-lib some/lib@v1.7.1
some/lib@v1.7.1 some-other/lib@v2.5.4
some/lib@v1.7.1 golang.org/x/lint@v0.0.0-20181026193005-c67002cb31c3
some-other/lib@v2.5.4 some/lib@v1.7.1
由於 some/lib
和 some-other/lib
在同一版本下相互依賴,因此不存在回溯到提供 github.com/golang/lint
的時間點的路徑。
假設 some/lib
是 v1.7.0
,some-other/lib
是 v2.5.3
,以下是實現這種原子版本更新的步驟:
- 驗證錯誤確實存在
- 在
some/lib
和some-other/lib
中執行GO111MODULE=on go get -u ./...
。 - 在兩個倉庫中都應該會看到錯誤:
github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
。
- 在
- 驗證
some/lib
的最新版本確實依賴於golang.org/x/lint
而不是github.com/golang/lint
。如果移除了歷史記錄但仍然保留了對github.com/golang/lint
的錯誤依賴,那就太糟糕了! - 透過 alpha 標籤將兩個庫都更新到彼此不存在的未來版本(這更安全,因為 go modules 在評估模組的最新發布版本時不會考慮 alpha 版本)
some/lib
將其some-other/lib
依賴項從v2.5.3
更改為v2.5.4-alpha
。some/lib
標記提交v1.7.1-alpha
並推送提交和標籤。some-other/lib
將其some/lib
依賴項從v1.6.0
更改為v1.7.1-alpha
。some-other/lib
標記提交v2.5.4-alpha
並推送提交和標籤。
- 在仍處於 alpha 狀態時驗證結果
- 在
some/lib
中執行GO111MODULE=on go build ./...
&&go test ./...
。 - 在
some-other/lib
中執行GO111MODULE=on go build ./...
&&go test ./...
。 - 在兩個倉庫中執行
GO111MODULE=on go mod graph
並斷言沒有指向github.com/golang/lint
的路徑。 - 注意:
go get -u
仍然無法工作,因為——如上所述——在評估最新版本時不會考慮 alpha 版本。
- 在
- 如果一切看起來都很好,則繼續將彼此的版本再次更新到不存在的未來版本
some/lib
將其some-other/lib
依賴項從v2.5.4-alpha
更改為v2.5.4
。some/lib
標記提交v1.7.1
並推送提交和標籤。some-other/lib
將其some/lib
依賴項從v1.7.1-alpha
更改為v1.7.1
。some-other/lib
標記提交v2.5.4
並推送提交和標籤。
- 驗證錯誤不再存在
- 在
some/lib
和some-other/lib
中執行GO111MODULE=on go get -u ./...
。 - 不應出現
parsing go.mod: unexpected module path "golang.org/x/lint"
錯誤。
- 在
- 目前,
some/lib
和some-other/lib
的go.sum
s 不完整。這是因為我們依賴於未來不存在的模組版本,所以直到過程完成才能生成 go.sum 條目。因此,讓我們修復這個問題。- 在
some/lib
中執行GO111MODULE=on go mod tidy
。 - 提交、標記提交
v1.7.2
,並推送提交和標籤。 - 在
some-other/lib
中執行GO111MODULE=on go mod tidy
。 - 提交、標記提交
v2.5.5
,並推送提交和標籤。
- 在
- 最後,讓我們確保
my-go-project
依賴於這些新版本的some/lib
和some-other/lib
,它們沒有冗長的歷史記錄。- 將
my-go-project
的go.mod
條目從some/lib v1.7.0
更改為some/lib 1.7.2
。 - 透過在
my-go-project
中執行GO111MODULE=on go get -u ./...
進行測試。
- 將
請注意,在步驟 5.b 和 5.d 之間,使用者是受影響的:已釋出了一個版本的 some/lib
,該版本依賴於一個不存在的 some-other/lib
版本。因此,理想情況下,此過程應即時完成,使得步驟 5.d 在步驟 5.b 之後儘快完成,從而最大限度地減小中斷視窗。
更大的迴圈
這個例子解釋了在圖中存在兩個包的迴圈時如何清除歷史記錄,但如果涉及更多包的迴圈呢?例如,考慮以下圖:
這些圖中的每一個都涉及迴圈(後者示例)或互連的模組(前者示例),涉及四個模組,而不是我們之前看到的簡單兩個模組的示例。過程大致相同,但這次在步驟 3 和 5 中,我們將所有四個模組更新到彼此不存在的未來版本,同樣在步驟 4 和 6 中,我們將測試所有四個模組,並在步驟 7 中修復所有四個模組的 go.sum。
更普遍地說,上述過程適用於涉及任何 n 個模組的任何一組互連模組:每個主要步驟都涉及 n 個模組協同工作。
此內容是 Go Wiki 的一部分。