go.mod版本管理

redrobot發表於2024-10-10

寫在前面

現在大部分 go 專案使用 go.mod 做版本控制,雖然能做到依賴的多版本共存,但是也會碰到一些不太好理解的地方,這裡對此進行一些記錄,方便查閱。

go module 版本格式

go.mod使用的版本號協議是 semver (Semantic Versioning),定義的版本號格式為:

vMAJOR.MINOR.PATCH
MAJOR 主版本號,如果有大的版本更新,導致 API 和之前版本不相容。我們遇到的就是這個問題。
MINOR 次版本號,當你做了向下相容的新 feature。
PATCH 修訂版本號,當你做了向下相容的修復 bug fix。
v 所有版本號都是 v 開頭。

在 go.mod 檔案中見到的所有的依賴包均是上述格式。如果依賴的第三方包中打的 tag 不符合上面的標準或者根本沒有打 tag,那麼 go.mod 中會自動生成滿足上述格式的偽版本號,這個時候可以檢視偽版本號中的最後一個數字,最後一個數字是倉庫中的 commitId,commitId 是正確的就是沒問題的。

標準樣式

類似下面格式,表明在 sarama 的庫中打了 v1.26.4 的 tag。注意,必須是打 v1.26.4 這樣的 tag,如果打的 tag 是 1.26.4、v1.26.4.2 等樣式均不符合 go.mod 的標準,go.mod 中均會生成偽版本號,所以儘量用標準形式打 tag。

github.com/Shopify/sarama v1.26.4

偽版本號

tag 不符合標準或者沒有 tag 就會生成偽版本號,類似下面這樣

github.com/faceair/sarama v1.27.3-0.20201026102015-6f053e317d37

生成偽版本號的時候主要就看最後一個數字,最後一個數字代表提交到倉庫的 commitId,可以根據這個 commitId 確定當前在使用那個版本的程式碼。
偽版本號具體的含義如下:v0.0.0-yyyymmddhhmmss-abcdefabcdef
yyyymmddhhmmss表示提交到倉庫的時間,abcdefabcdef就表示 commitId。如果打的 tag 為 v1.27.2.3(多用了一個小數點),生產的偽版本號中就會變成示例中的樣子。

間接依賴

在使用 Go module 過程中,隨著引入的依賴增多,也許你會發現go.mod檔案中部分依賴包後面會出現一個// indirect的標識。這個標識總是出現在require指令中,其中//與程式碼的行註釋一樣表示註釋的開始,indirect表示間接的依賴。

比如開源軟體 Kubernetes(v1.17.0版本)的 go.mod 檔案中就有數十個依賴包被標記為indirect:

require (
    github.com/Rican7/retry v0.1.0 // indirect
    github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 // indirect
    github.com/boltdb/bolt v1.3.1 // indirect
    github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b // indirect
    github.com/codegangsta/negroni v1.0.0 // indirect
    ...
)

在執行命令go mod tidy時,Go module 會自動整理go.mod 檔案,如果有必要會在部分依賴包的後面增加// indirect註釋。一般而言,被新增註釋的包肯定是間接依賴的包,而沒有新增// indirect註釋的包則是直接依賴的包,即明確的出現在某個import語句中。

然而,這裡需要著重強調的是:並不是所有的間接依賴都會出現在 go.mod檔案中。

間接依賴出現在go.mod檔案的情況,可能符合下面所列場景的一種或多種:

  • 直接依賴未啟用 Go module
  • 直接依賴go.mod 檔案中缺失部分依賴

不相容標識+incompatible

+incompatible 標識專案中引入了一個不相容的包。產生的原因是專案中引用的第三方依賴的版本已經升級到 v2 甚至更高,但是第三方包的 module name 沒有顯式加上 v2 標識。正確做法應該是讓第三方包的 module name 顯式加上 v2 標識,然後重新打一個 v2 版本的 tag,然後在自己的專案中使用新的 tag。

產生了不相容標識對第三方依賴包會有所影響,因為他們本身沒有按照規範打 tag,這種標識會讓使用方知道他們正在使用的包發生了不相容的變更,不利於第三方包的推廣。

replace 語句

go.mod 中的 replace 語句只會對當前專案產生影響,不會對其他引用該專案的程式碼產生影響。
專案中使用 replace 主要有以下幾個用途:

  1. 替換成映象包,某些包無法直接下載,用 replace 替換成映象包後就可以下載成功,使用時仍然使用原來的 replace 前的 module name
  2. 想要隱藏專案中使用的包的版本,require 中將包的版本定義成v0.0.0,然後在 replace 中替換成其他版本。主要用於某些專案不想讓其他專案引用的情況,k8s 的庫便是如此
  3. 將某些包替換成其他的包。可以這樣做,但是不如直接使用 replace 之後的包。
  4. 使用本地的其他專案,本地其他專案必須用 replace,可以用相對路徑和絕對路徑引用到另一個專案的根路徑中即可。

上面說的四種用途其實都是在說一件事,使用 replace 之後專案中實際使用的程式碼其實是 replace 之後的包的程式碼。

用到的一些 go 命令

go mod tidy

比較常用的一個命令,該命令會自動整理依賴,可以新增依賴,刪除不需要的依賴,同時還會對 go.sum 檔案進行修改。手動修改go.mod 中某個包的版本,然後執行 go mod tidy 相當於執行 go get 命令

go get

用於下載或更新包,下載時不指定包的版本預設會下載最新包,需要指定版本時包名和版本號之間用『@』符號相連(中間沒有空格)。指定版本時可以使用倉庫中打的 tag、commitId 或者 branchName,如果 tag 不符合標準,在下載時要先使用不標準的 tag,下載完成會後自動變成偽版本號。

go mod download

在 go.mod 手動修改版本號,然後可以執行 go mod download 下載,該命令不會對包進行整理,go.sum 也不會修改,執行完後應該再執行一下 go mod tidy

go clean -modcache

如果有時候下載包一直不成功,有可能是快取中的包衝突導致,可以使用該命令將快取刪除,然後執行 go mod tidy 重新下載所有的包。

總結

還是熟能生巧,要多踩些坑,才能更快解決碰到的問題。

相關文章