Go 語言包管理機制深入分析
隨著 Go 語言的深入使用,其依賴管理機制也一直是各位 Gopher 熱衷於探討的話題。Go 語言的原始碼依賴可通過
go get
命令來獲取,但自動化程度不高,於是官方提供了 Dep 這樣的自動化批量管理依賴的工具。雖然 Go 語言的依賴管理在很多方面還是不如人意,但整個體系正在日趨完善,本篇就將從最基本的依賴管理場景出發,一同探討 Go 語言依賴管理的一些最佳實踐。
Go 依賴管理的基本思路
在 Go 語言中,我們通過 go get
命令將 GitHub 或者 Google Code 上的程式碼下載到本地指定目錄,然後在開發程式碼中通過 import
的形式引用本地的程式碼。
import "github.com/spf13/cobra"
Go 語言可以通過直接分析程式碼中的 import 語句來查詢依賴關係。go get 命令在執行時,就會自動解析 import 來安裝所有的依賴。那麼下載的依賴在本地是如何儲存的呢?
這裡就涉及到 Go 語言的 WORKSPACE 概念,簡單來說就是通過 GOPATH 環境變數來設定 Go 程式碼的位置。一般來說,GOPATH 目錄下會包含 pkg、src 和 bin 三個子目錄,這三個目錄各有用處。
-
bin 目錄用來放置編譯好的可執行檔案,為了使得這裡的可執行檔案可以方便的執行,在 shell 中設定PATH變數。
-
src 目錄用來放置程式碼原始檔,在進行 import 時,是使用這個位置作為根目錄的。自己編寫的程式碼也應該放在這下面,不同的專案放在不同的目錄下進行管理。
-
pkg 用來放置安裝的包的連結物件(Object)的。這個概念有點類似於連結庫,Go 會將編譯出的可連線庫放在這裡,方便編譯時連結。不同的系統和處理器架構的物件會在 pkg 存放在不同的資料夾中。
當專案在 src 目錄下管理時,多個專案可能都會使用相同的依賴,如果每個專案都存一份依賴顯然會帶來大量的冗餘,這裡我們推薦一個設定 GOPATH 環境變數時的小技巧。
export GOPATH="/usr/local/share/go:$HOME/codes/go"
這樣第三方包就會預設放置在第一個路徑中,而你可以在第二個路徑下編寫自己的程式碼,多個專案共享一份依賴。
dep - 官方 Go 依賴管理工具
dep 是 Go 語言官方提供的依賴管理工具,跟其他依賴管理工具類似,都是通過一個檔案描述依賴的座標資訊,然後批量管理(下載、升級等)依賴包(原始碼)。dep 是一個開源專案, 大家可以在 https://github.com/golang/dep 瞭解詳細資訊,其安裝方式大家可以參考官方說明,這裡我們主要介紹其使用。
基本操作
通過 dep init
命令來初始化,會建立Gopkg.lock,Gopkg.toml檔案和一個空的vendor目錄。
我們在程式碼中通過 import
命令新增依賴後,通過 dep ensure
就可以下載依賴到本地 $GOPATH/src
目錄下。
main.go
...
import (
"github.com/golang/glog"
)
...
Gopkg.lock
[[projects]]
branch = "master"
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
name = "github.com/golang/glog"
packages = ["."]
pruneopts = "UT"
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/golang/glog"
]
solver-name = "gps-cdcl"
solver-version = 1
通過 dep status
我們可以檢視當前依賴引用的情況
$ dep status
PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED
github.com/golang/glog branch master branch master 23def4e 23def4e 1
另外有一個 dep check
命令來檢查是否存在依賴被引用,但是程式碼中並沒有使用的情況,Go 語言對於依賴的引用比較嚴格,不允許引用了但是沒使用的情況。從軟體安全的角度考慮,這是一個很好的實踐,避免引入一些安全風險。
$ dep check
# Gopkg.lock is out of sync:
github.com/golang/glog: in Gopkg.lock's input-imports, but neither imported nor required
當然,這種時候我們就需要移除本地依賴,最好不要手動刪除vendor中的內容,而是通過 dep ensure -update
命令來移除。
從 dep 的目錄結構,我們可以分析出 dep 的基本工作思路:
這裡面有兩個關鍵的步驟:
-
解析依賴
從當前專案的import
檔案中解析出整個工程的依賴情況,並結合 Gopkg.toml 定義的規則,然後將依賴關係輸出給 Gopkg.lock,注意這個 lock 檔案最好不要手動修改。 -
獲取依賴
通過 Gopkg.lock 瞭解整個依賴關係之後,將依賴的具體內容拉取下來放到 vendor 目錄中,然後執行 Go build 時從本地的 vendor 讀取依賴並完成構建。
這一些都是在 dep ensure
時完成的,其實在執行這個命令時還可以傳引數,最主要的是 -no-vendor
和 -vendor-only
這兩個引數。
-no-vendor
引數只會導致執行 resolve 函式,結果是建立一個新的Gopkg.lock 檔案,不會更新 vendor;而 -vendor-only
引數將跳過 resolve 並僅執行 vendoring 函式,導致 vendor/
從已存在的Gopkg.lock 重新更新。
總結
Go dep 目前是一款比較好用的依賴管理工具,很多比較大型的專案都在使用,從中可以學習到依賴管理的一些基本思路,對於理解其他語言,比如NPM的依賴管理模型也是比較有好處的。