在前面轉載了系列文章:Golang 需要避免踩的 50 個坑,總得來說閱讀量都挺大。今天這篇文章,我們們一起聊聊Go的依賴包管理工具。
背景
每一門語言都有其依賴的生態,當我們使用Java語言的時候,使用Maven或者Gradle管理包依賴。早期的Go被很多開發者所詬病的一個問題就是依賴包的管理。Golang 1.5 release版本的釋出之前,只能通過設定多個GOPATH
的方式來解決這個問題,例如:我兩個工程都依賴了Beego,但A工程依賴的是Beego 1.1,B工程依賴的是Beego 1.7,我必須設定兩個GOPATH
來區分,並且在切換工程的時候GOPATH
也得切換,無比痛苦。在Golang 1.5 release 開始支援除了GOROOT
和GOPATH
之外的依賴管理:vender,官方 wiki 推薦了多種支援這種特性的包管理工具,如:Godep、gv、gvt、glide、govendor和官方的dep等。
環境準備
安裝Go
筆者是Mac系統,安裝Go有多種方式,通過brew、下載原始碼安裝go等方式可以安裝go。
在bash_profile中自定義GOPATH和GOBIN位置:
GOROOT=/usr/local/go
export GOPATH=/Users/user/aoho/go-workspace
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN:$GOROOT/bin
複製程式碼
安裝完成之後,檢視go的環境變數:go env
。
GOARCH="amd64"
GOBIN="/usr/local/go/bin/go"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/user/aoho/go-workspace"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/st/gkm45qzd2tv8mc32my38_n_00000gp/T/go-build646095787=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
複製程式碼
go的版本為:go version go1.9.3 darwin/amd64
。
GOPATH和GOROOT
GOROOT不是必須要設定的。預設go會安裝在/usr/local/go下,但也允許自定義安裝位置,GOROOT的目的就是告知go當前的安裝位置,編譯的時候從GOROOT去找SDK的system libariry。
如上面展示的結果,筆者使用的就是預設的安裝地址,也可以通過 export GOROOT=$HOME/go1.9.3
指定。
GOPATH必須要設定,但並不是固定不變的。GOPATH的目的是為了告知go,需要程式碼的時候,去哪裡查詢。注意這裡的程式碼,包括本專案和引用外部專案的程式碼。GOPATH可以隨著專案的不同而重新設定。
GOPATH下會有3個目錄:src、bin、pkg。
- src目錄:go編譯時查詢程式碼的地方;
- bin目錄:go get這種bin工具的時候,二進位制檔案下載的目的地;
- pkg目錄:編譯生成的lib檔案儲存的地方。
包管理
上面小節提到,依賴的程式碼去$GOPATH
指定的位置尋找,這部分程式碼可能是本專案或者外部引用的專案。下面依次介紹這兩種情況。
內部依賴管理
如筆者示例route_auth.go的引入:
import (
"gwp/Chapter_2_Go_ChitChat/chitchat/data"
"net/http"
)
複製程式碼
route_auth.go需要引用data/user.go,專案結構如下:
編譯時會去$GOPATH/src/
目錄去查詢需要的程式碼,因此只要上面data/user.go在$GOPATH/src/gwp/Chapter_2_Go_ChitChat/chitchat/data
裡面,go編譯的時候就能找到。
外部依賴管理
對於外部依賴的管理,在最開始go沒有像java使用maven來管理依賴包、包版本,而是直接使用GOPATH來管理外部依賴。
GOPATH來管理外部依賴
go允許import不同程式碼庫的程式碼,例如github.com, k8s.io, golang.org等等;對於需要import的程式碼,可以使用 go get 命令取下來放到GOPATH對應的目錄中去。例如go get github.com/globalsign/mgo
,會下載到$GOPATH/src/github.com/globalsign/mgo
中去,當其他專案在import github.com/globalsign/mgo
的時候也就能找到對應的程式碼了。
看到這裡也就明白了,對於go來說,其實並不在意你的程式碼是內部還是外部的,總之都在GOPATH裡,任何import包的路徑都是從GOPATH開始的;唯一的區別,就是內部依賴的包是開發者自己寫的,外部依賴的包是go get下來的。Go 語言原生包管理的缺陷:
- 能拉取原始碼的平臺很有限,絕大多數依賴的是 github.com
- 不能區分版本,以至於令開發者以最後一項包名作為版本劃分
- 依賴 列表/關係 無法持久化到本地,需要找出所有依賴包然後一個個 go get
- 只能依賴本地全域性倉庫(GOPATH/GOROOT),無法將庫放置於區域性倉庫($PROJECT_HOME/vendor)
vendor
依賴GOPATH來解決go import存在的問題在上面小節已經列舉。為了解決這個問題,go在1.5版本引入了vendor屬性(預設關閉,需要設定go環境變數GO15VENDOREXPERIMENT=1),並在1.6版本中預設開啟了vendor屬性。
簡單來說,vendor屬性就是讓go編譯時,優先從專案原始碼樹根目錄下的vendor目錄查詢程式碼(可以理解為切了一次GOPATH),如果vendor中有,則不再去GOPATH中去查詢。
以kube-keepalived-vip為例。該專案會呼叫k8s.io/kubernetes的庫(Client),但如果你用1.5版本的kubernetes程式碼來編譯keepalived,會編譯不過。1.5版本中程式碼有變化,已經沒有這個Client了。這就是前面說的依賴GOPATH來解決go import所帶來的問題,程式碼不對上了。
kube-keepalived-vip專案用vendor目錄解決了這個問題:該專案把所有依賴的包都拷貝到了vendor目錄下,對於需要編譯該專案的人來說,只要把程式碼從github上clone到$GOPATH/src
以後,就可以進去go build了(注意,必須將kube-keepalived-vip專案拷貝到$GOPATH/src
目錄中,否則go會無視vendor目錄,仍然去$GOPATH/src
中去找依賴包)。
通過如上vendor解決了部分問題,然而又引起了新的問題:
- vendor目錄中依賴包沒有版本資訊。這樣依賴包脫離了版本管理,對於升級、問題追溯,會有點困難。
- 如何方便的得到本專案依賴了哪些包,並方便的將其拷貝到vendor目錄下?依靠人工實在不現實。
為了解決這些問題,開源社群在vendor基礎上開發了多個管理工具,比較常用的有godep、govendor glide等,go官方釋出了dep。
godep
godep是解決包依賴的管理工具,原理是掃描記錄版本控制的資訊,並在go命令前加殼來做到依賴管理。godep早期版本並不依賴vendor,所以對go的版本要求很鬆,go 1.5之前的版本也可以用,只是行為上有所不同。在vendor推出以後,godep也改為使用vendor了。godep 建議在 golang 1.6 以後使用,且godep 依賴 vendor 。
godep的使用者眾多,如docker,kubernetes, coreos等go專案很多都是使用godep來管理其依賴,當然原因可能是早期也沒的工具可選。
go get -u -v github.com/tools/godep
複製程式碼
通過如上的命令安裝,成功安裝後,在$GOPATH
的bin目錄下會有一個godep可執行的二進位制檔案,後面執行的命令都是用這個,建議這個目錄加入到全域性環境變數中。
編譯執行
因為go命令是直接到GOPATH目錄下去找第三方庫,且在1.6以後支援vendor方式編譯,而使用godep下載的依賴庫放到Godeps/workspace目錄下的,但是不影響繼續使用依賴GOPATH目錄,所以與三方工具本身不衝突。因此使用:
godep go build main.go
複製程式碼
godep中的go命令,就是將原先的go命令加了一層殼,執行godep go
的時候,會將當前專案的workspace目錄加入GOPATH變數中。
檢出依賴
如果要增加新的依賴包:
- 執行
go get github.com/globalsign/mgo
- 程式碼中
import github.com/globalsign/mgo
專案編寫好了,使用GOPATH
的依賴包測試ok了的時候,執行:
godep save
複製程式碼
如上的命令將會自動掃描當前目錄所屬包中import的所有外部依賴庫(非系統庫),並將所有的依賴庫下來下來到當前工程中,產生檔案 Godeps/Godeps.json
檔案。
godep save
時godep把所有依賴包程式碼從GOPATH路徑拷貝到Godeps目錄下,並去除程式碼管理目錄。這個用處主要是為了支撐godep go tool的一系列操作,尤其是git clone了程式碼庫下來後,通常直接用godep go install xxx即可完成編譯,一定程度上能夠緩解golang比較嚴格的程式碼路徑和包管理帶來的煩惱。在沒有 Godeps 檔案的情況下,生成模組依賴目錄vendor資料夾。如果是開發依賴使用三方庫,需要固定使用某個版本,請完全提交Godeps和vendor資料夾。
依賴包會有更新,如何更新依賴包?可以通過如下的命令實現。
- 執行
go get -u github.com/globalsign/mgo
- 執行
godep update github.com/globalsign/mgo
拉取依賴 restore
通過命令 godep restore
同步依賴庫,如果下載的專案中只有Godeps.json檔案,而沒有包含第三庫則可以使用godep restore這個命令將所有的依賴庫下來到$GOPATH\src
中用於開發。
godep restore執行時,godep會按照Godeps/Godeps.json內列表,依次執行go get -d -v來下載對應依賴包到GOPATH路徑下。
govendor
govendor是在vendor之後出來的,功能相對godep多一點,不過就核心問題的解決來說基本是一樣的。該工具將專案依賴的外部包拷貝到專案下的 vendor 目錄下,並通過 vendor.json 檔案來記錄依賴包的版本,方便使用者使用相對穩定的依賴。
go get -u github.com/kardianos/govendor
複製程式碼
如上的命令即可安裝govendor,govendor生成vendor目錄的時候需要2條命令:
- govendor init生成vendor/vendor.json,此時檔案中只有本專案的資訊
- govendor add +external更新vendor/vendor.json,並拷貝GOPATH下的程式碼到vendor目錄中。 govendor還可以直接指定依賴包版本來獲取包。
govendor的依賴包主要有以下多種型別:
使用步驟
進入專案的根目錄。
# 建立 vendor 資料夾和 vendor.json 檔案
govendor init
# 從 $GOPATH 中新增依賴包,會加到 vendor.json
govendor add +external
# 列出已經存在的依賴包
govendor list
# 找出使用的對應包
govendor list -v fmt
# 拉取指定版本的包
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
govendor fetch golang.org/x/net/context@v1 # Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@=v1 # Get the tag or branch named "v1".
複製程式碼
相對上面的工具來說,govendor功能更加豐富。
總結
本文主要介紹了幾種go依賴包管理工具,首先介紹了go的環境安裝,配置對應的環境變數;其次講到包管理的兩種型別:內部依賴和外部依賴的管理。內部依賴包的管理很簡單,go原生的外部依賴包管理存在很多缺陷,隨後介紹了開源社群推出的godep和govendor,在vendor基礎上進行了功能的完善。還有目前常用的包依賴管理工具glide和官方的dep,將會在後面的文章介紹,盡請期待。