Vendoring:Go 套件管理機制的大躍進

Go 的套件管理設計哲學

在介紹 Vendoring 機制前,先來聊聊Go 的套件管理機制,也因為 Go 與其他語言在這方面有非常大的不同,也因此有以下最為人所詬病的三點:

  1. 強制規定工作目錄 $GOPATH
  2. 建議採用絕對路徑來 import 而非相對路徑
  3. 極度依賴 Git 等 VCS 版本管理工具

規定工作目錄的好處就是一致性,這當然對於編譯完的 Go 二進制檔案要做分散式部署非常方便,但相對而來的壞處就是開發者要在自己習慣的目錄下開發就比較麻煩。Package 名稱跟 repo 名稱也因此變得特別重要,必須清楚地讓引用的人可以看出來這個套件的用途,也方便在 $GOPATH 中查找。

絕對路徑的好處是可讀性高、一致、相依度低,但是壞處一樣,是對於開發者的不方便,如果某個套件的改動尚未 Push 或者是你不在 $GOPATH 下開發,那很可能你編譯的程式碼不是最新的你都沒發現,這是新手容易踩到的坑。

依賴 VCS Repo 管理很好,但是當你人在中國,問題就大了,除了官方的套件肯定無法連上了,很可能有時候連 Github 也連不上的。最嚴重的是,import 路徑若是一個 Git Repo,我們卻無法指定 Commit, Tag, or Branch,期待最新提交的程式碼完全不會出錯是不可能的。即便假設最新程式都是正常的,套件版本不斷升級,還是很容易發生新舊版無法相容的問題,而這些問題在你沒有清除、或更新 $GOPATH 之前都是不容易發現的。

相對路徑 or 絕對路徑?

Relative imports? Do or don't?:簡單來說, 使用相對路徑會產生額外問題,例如當你在執行 go test 的時候,路徑可能就會因此錯亂,或者你用了 vim-go 等外掛輔助工具,可能也會因此無法解析變數、自動補全等功能失效。

套件相依問題的早期解法

在沒有官方支持以前,比較典型的做法有:

  1. Symbolic Link:將你正在編輯但是還沒推送的資料夾指到 $GOPATH 中。
  2. Multiple paths in $GOPATH:建立一個私有的資料夾,把與你專案相依的套件放在其中優先讓 Go 解析,細節可以參考Go in Production

Go 1.5,曙光乍現

當 Go 的開發社群越來越活躍,套件越來越多後,這個問題逐漸被獲得重視。Go 1.5 採用了 Russ Cox 在 2015/07 提出的 Vendoring 設計提案:只要專案目錄下有 vendor 資料夾,優先 import 其中的套件。

更好的消息是 GO15VENDOREXPERIMENT 所開啟的 Vendoring 功能在 Go 1.6 已經正式被採用了,Go 1.7 這個變數就會被移除,強制啟用 Vendoring。

安裝 Go 1.5+ 並且 export GO15VENDOREXPERIMENT=1 之後,我們就可以挑一套順手的 Go 套件管理工具來管理套件之間的相依關係。

Vendoring

在這麼多的選擇當中,glide 是最完整也是 Github 上最多星星數的套件管理工具。但是實際上使用後,我發現 glide 實在太肥大了,光是 glide up 更新相依的套件就要花上好幾分鐘。在我理想中的套件管理工具應該是只需要設定嚴格相依的套件版號,其他的永遠抓 repo 中最新的提交,切換版號的速度最好還能跟 git checkout 一樣快。trash 剛好完美地達到我的需求。

作者的設計哲學很簡單:

Come on, it's just going to be 300 (okay, it's ~600) lines of Go!

TL;DR 如何使用 trash

  1. 透過 go get github.com/rancher/trash 安裝
  2. 複製 github.com/rancher/trash/transh.yml 到專案的根目錄
  3. 於專案根目錄執行 trash --keep,相依套件就會出現在 ./vendor 底下

trash.yml 範例如下:

import:  
- package: github.com/Cepave/agent                  # package name
  version: origin/develop                           # tag, commit, or branch
  repo:    https://github.com/Cepave/agent.git      # (optional) git URL

未來 vendor 中的套件有任何修改時執行 trash --keep 即可。