寫在前面
現在大部分 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 主要有以下幾個用途:
- 替換成映象包,某些包無法直接下載,用 replace 替換成映象包後就可以下載成功,使用時仍然使用原來的 replace 前的 module name
- 想要隱藏專案中使用的包的版本,require 中將包的版本定義成v0.0.0,然後在 replace 中替換成其他版本。主要用於某些專案不想讓其他專案引用的情況,k8s 的庫便是如此
- 將某些包替換成其他的包。可以這樣做,但是不如直接使用 replace 之後的包。
- 使用本地的其他專案,本地其他專案必須用 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 重新下載所有的包。
總結
還是熟能生巧,要多踩些坑,才能更快解決碰到的問題。