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 過程中不會再遇到這個問題。

更棘手的問題:清除歷史記錄

這一切都很好,應該能解決大多數使用者的問題。

然而,有一種情況會變得相當複雜:當模組依賴圖中存在迴圈時。考慮這個模組依賴圖

Module Dependency Graph With A Cycle

並且,假設 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 進行視覺化

A Module Dependency Graph With Trailing History

這裡我們看到,即使 some/lib 的最後幾個版本正確地依賴於 golang.org/x/lint,但由於 some/libsome-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/libsome-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

A Module Dependency Graph Without Trailing History

由於 some/libsome-other/lib 在同一版本下相互依賴,因此不存在回溯到提供 github.com/golang/lint 的時間點的路徑。

假設 some/libv1.7.0some-other/libv2.5.3,以下是實現這種原子版本更新的步驟:

  1. 驗證錯誤確實存在
    1. some/libsome-other/lib 中執行 GO111MODULE=on go get -u ./...
    2. 在兩個倉庫中都應該會看到錯誤:github.com/golang/lint@v0.0.0-20190313153728-d0100b6bd8b3: parsing go.mod: unexpected module path "golang.org/x/lint"
  2. 驗證 some/lib 的最新版本確實依賴於 golang.org/x/lint 而不是 github.com/golang/lint。如果移除了歷史記錄但仍然保留了對 github.com/golang/lint 的錯誤依賴,那就太糟糕了!
  3. 透過 alpha 標籤將兩個庫都更新到彼此不存在的未來版本(這更安全,因為 go modules 在評估模組的最新發布版本時不會考慮 alpha 版本)
    1. some/lib 將其 some-other/lib 依賴項從 v2.5.3 更改為 v2.5.4-alpha
    2. some/lib 標記提交 v1.7.1-alpha 並推送提交和標籤。
    3. some-other/lib 將其 some/lib 依賴項從 v1.6.0 更改為 v1.7.1-alpha
    4. some-other/lib 標記提交 v2.5.4-alpha 並推送提交和標籤。
  4. 在仍處於 alpha 狀態時驗證結果
    1. some/lib 中執行 GO111MODULE=on go build ./... && go test ./...
    2. some-other/lib 中執行 GO111MODULE=on go build ./... && go test ./...
    3. 在兩個倉庫中執行 GO111MODULE=on go mod graph 並斷言沒有指向 github.com/golang/lint 的路徑。
    4. 注意:go get -u 仍然無法工作,因為——如上所述——在評估最新版本時不會考慮 alpha 版本。
  5. 如果一切看起來都很好,則繼續將彼此的版本再次更新到不存在的未來版本
    1. some/lib 將其 some-other/lib 依賴項從 v2.5.4-alpha 更改為 v2.5.4
    2. some/lib 標記提交 v1.7.1 並推送提交和標籤。
    3. some-other/lib 將其 some/lib 依賴項從 v1.7.1-alpha 更改為 v1.7.1
    4. some-other/lib 標記提交 v2.5.4 並推送提交和標籤。
  6. 驗證錯誤不再存在
    1. some/libsome-other/lib 中執行 GO111MODULE=on go get -u ./...
    2. 不應出現 parsing go.mod: unexpected module path "golang.org/x/lint" 錯誤。
  7. 目前,some/libsome-other/libgo.sums 不完整。這是因為我們依賴於未來不存在的模組版本,所以直到過程完成才能生成 go.sum 條目。因此,讓我們修復這個問題。
    1. some/lib 中執行 GO111MODULE=on go mod tidy
    2. 提交、標記提交 v1.7.2,並推送提交和標籤。
    3. some-other/lib 中執行 GO111MODULE=on go mod tidy
    4. 提交、標記提交 v2.5.5,並推送提交和標籤。
  8. 最後,讓我們確保 my-go-project 依賴於這些新版本的 some/libsome-other/lib,它們沒有冗長的歷史記錄。
    1. my-go-projectgo.mod 條目從 some/lib v1.7.0 更改為 some/lib 1.7.2
    2. 透過在 my-go-project 中執行 GO111MODULE=on go get -u ./... 進行測試。

請注意,在步驟 5.b 和 5.d 之間,使用者是受影響的:已釋出了一個版本的 some/lib,該版本依賴於一個不存在的 some-other/lib 版本。因此,理想情況下,此過程應即時完成,使得步驟 5.d 在步驟 5.b 之後儘快完成,從而最大限度地減小中斷視窗。

更大的迴圈

這個例子解釋了在圖中存在兩個包的迴圈時如何清除歷史記錄,但如果涉及更多包的迴圈呢?例如,考慮以下圖:

Module Dependency Graph With Four Related Cycles

Module Dependency Graph With One Four Vertex Cycle

這些圖中的每一個都涉及迴圈(後者示例)或互連的模組(前者示例),涉及四個模組,而不是我們之前看到的簡單兩個模組的示例。過程大致相同,但這次在步驟 3 和 5 中,我們將所有四個模組更新到彼此不存在的未來版本,同樣在步驟 4 和 6 中,我們將測試所有四個模組,並在步驟 7 中修復所有四個模組的 go.sum。

更普遍地說,上述過程適用於涉及任何 n 個模組的任何一組互連模組:每個主要步驟都涉及 n 個模組協同工作。


此內容是 Go Wiki 的一部分。