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.modgithub.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/lib 位於 v1.7.0,而 some-other/lib 位於 v2.5.3

  1. 驗證錯誤確實存在
    1. some/libsome-other/lib 中執行 GO111MODULE=on go get -u ./...
    2. 在兩個儲存庫中,您應該會看到錯誤 go: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 模組不會將 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. 不應出現解析錯誤 go.mod: unexpected module path "golang.org/x/lint"
  7. 目前,some/libsome-other/libgo.sum 並不完整。這是因為我們依賴於模組的未來非存在版本,因此我們無法在程序完成之前產生 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-project go.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-other/lib 非存在版本的 some/lib 版本。因此,這個程序理想上應即時執行,以便在步驟 5.b 之後儘快完成步驟 5.d,以盡可能縮小中斷的時段。

較大的循環

這個範例說明了在圖形中有兩個套件涉及循環時移除歷史記錄的程序,但如果循環涉及更多套件呢?例如,考慮以下圖形

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 的一部分。