go依賴包管理工具對比

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
  • GOPATH和GOROOT
    • GOROOT並不是必須要設定的。
    • GOPATH必須要設定,但並不是固定不變的
  • 本項目內部的依賴
  • 管理外部的依賴包
    • 使用GOPATH來管理外部依賴
  • vendor
    • godep
    • glide
    • govendor
    • golang官方dep
    • 各依賴管理工具對比
    • gvt
  • vendor的問題

當開始真正用go去做項目的時候,不可避免的就會遇到依賴包的問題。go的依賴包管理與java+maven的依賴管理不太一樣,我們從GOPATH開始逐漸理解其思想,然後再對比下目前常用的依賴包管理工具。

GOPATH和GOROOT

初學者很容易會被這兩個環境變數給搞暈。

GOROOT並不是必須要設定的。

GOROOT不是必須要設定的。參見Installing to a custom location,預設go會安裝在/usr/local/go下,但也允許自訂安裝位置,GOROOT的目的就是告知go當前的安裝位置,編譯的時候從GOROOT去找SDK的system libariry。

例如我用的是ubuntu 16.04,預設go的版本是1.6,如果我想升級為更新的版本,就需要自訂安裝,所以我設定了GOROOT:export GOROOT=$HOME/go1.7.4

GOPATH必須要設定,但並不是固定不變的

GOPATH的目的是為了告知go,需要代碼的時候,去哪裡尋找。注意這裡的代碼,包括本項目和引用外部項目的代碼。GOPATH可以隨著項目的不同而重新設定。

GOPATH下會有3個目錄:src, bin, pkg。

  • src目錄:go編譯時間尋找代碼的地方
  • bin目錄:go get godep這種bin工具的時候,二進位檔案下載的目的地
  • pkg目錄:編譯產生的lib檔案儲存體的地方。

本項目內部的依賴

以kubernetes為例。kubernetes/cmd/kubectl/kubectl.go中引用了app包中的kubectl.go,代碼是這樣寫的:

import ("os""k8s.io/kubernetes/cmd/kubectl/app")

那麼go在編譯的時候怎麼尋找這個包呢?

這就是GOPATH發揮作用的時候了。go編譯時間會去$GOPATH/src/目錄去尋找需要的代碼,因此只要上面app/kubectl.go在$GOPATH/src/k8s.io/kubernetes/cmd/kubectl/裡面,go編譯的時候就能找到,那麼自然的,kubernetes/cmd/kubectl/kubectl.go也需要放到$GOPATH/src/k8s.io/裡去。最終$GOPATH裡的代碼結構是這樣的:

├── src│   ├── k8s.io│   │   └── kubernetes│   │       ├── cmd│   │       │   ├── kubectl│   │       │   │   ├── app│   │       │   │   │   ├── BUILD│   │       │   │   │   └── kubectl.go│   │       │   │   ├── BUILD│   │       │   │   ├── kubectl.go│   │       │   │   └── OWNERS

管理外部的依賴包

不可避免的我們會使用外部的依賴包包。go在這方面做的非常飄逸。go沒有像java使用maven來管理依賴包、包版本,而是直接使用GOPATH來管理外部依賴。

使用GOPATH來管理外部依賴

go允許import不同程式碼程式庫的代碼,例如github.com, k8s.io, golang.org等等;對於需要import的代碼,可以使用 go get 命令取下來放到GOPATH對應的目錄中去。例如go get github.com/silenceshell/hcache,會下載到$GOPATH/src/github.com/silenceshell/hcache中去,當其他項目在import github.com/silenceshell/hcache的時候也就能找到對應的代碼了。

看到這裡也就明白了,對於go來說,其實並不care你的代碼是內部還是外部的,總之都在GOPATH裡,任何import包的路徑都是從GOPATH開始的;唯一的區別,就是內部依賴的包是開發人員自己寫的,外部依賴的包是go get下來的。

vendor

依賴GOPATH來解決go import有個很嚴重的問題:如果項目依賴的包做了修改,或者乾脆刪掉了,會影響我的項目。因此在1.5版本以前,為了規避這個問題,通常會將當前使用的依賴包拷貝出來。

為了能讓項目繼續使用這些依賴包,有這麼幾個辦法:

  • 將依賴包拷貝到項目源碼樹中,然後修改import
  • 將依賴包拷貝到項目源碼樹中,然後修改GOPATH
  • 在某個檔案中記錄依賴包的版本,然後將GOPATH中的依賴封裝更新到對應的版本(因為依賴包實際是個git庫,可以切換版本)

go作為一個現代化的語言,居然要用這麼複雜不直觀而又不標準的方法來管理依賴,難怪在早期會有很多人非常不看好go的前景。

為瞭解決這個問題,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,會編譯不過:

./controller.go:107: undefined: "k8s.io/kubernetes/pkg/client/unversioned".Client

查下代碼會發現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目錄下? Manual is fxxk.

社區為瞭解決這些(工程)問題,在vendor基礎上開發了多個管理工具,比較常用的有godep, govendor, glide。go官方也在開發官方dep,目前還是Alpha狀態。

下面來看看使用的比較多的gode, glide和 govendor。

godep

godep的使用者眾多,如docker,kubernetes, coreos等go項目很多都是使用godep來管理其依賴,當然原因可能是早期也沒的工具可選。

godep早期版本並不依賴vendor,所以對go的版本要求很松,go 1.5之前的版本也可以用,只是行為上有所不同。在vendor推出以後,godep也改為使用vendor了。

godep使用很簡單:當你的項目編寫好了,使用GOPATH的依賴包測試ok了的時候,執行:

$ godep save

以hcache為例,執行go save,會做2件事:

  • 掃描本項目的代碼,將hcache項目依賴的包及該包的版本號碼(即git commit)記錄到Godeps/Godeps.json檔案中
  • 將依賴的代碼從GOPATH/src中copy到vendor目錄(忽略原始代碼的.git目錄)。對於不支援vendor的早期版本,則會拷貝到Godeps/_workspace/裡

一個Godeps.json的例子。

{    "ImportPath": "github.com/silenceshell/hcache",    "GoVersion": "go1.7",    "GodepVersion": "v79",    "Deps": [        {               "ImportPath": "github.com/tobert/pcstat",            "Rev": "91a7346e5b462a61e876c0574cb1ba331a6a5ac5"        },          {               "ImportPath": "golang.org/x/sys/unix",            "Rev": "0b25a408a50076fbbcae6b7ac0ea5fbb0b085e79"        }       ]   }

如果要增加新的依賴包:

  1. Run go get foo/bar
  2. Edit your code to import foo/bar.
  3. Run godep save (or godep save ./…).

如果要更新依賴包:

  1. Run go get -u foo/bar
  2. Run godep update foo/bar. (You can use the … wildcard, for example godep update foo/…).

godep還支援godep restore,可以將vendor下的代碼反向拷貝到$GOPATH下。不過我沒想到這個功能在什麼情況下可以用到。

glide

glide也是在vendor之後出來的。glide的依賴包資訊在glide.yaml和glide.lock中,前者記錄了所有依賴的包,後者記錄了依賴包的版本資訊(合成一個多好)。

glide使用也不麻煩:

glide create  # 建立glide工程,產生glide.yamlglide install # 產生glide.lock,並拷貝依賴包work, work, workglide update  # 更新依賴包資訊,更新glide.lock

glide install會根據glide.lock來更新包的資訊,如果沒有則會走一把glide update產生glide.lock

最終一個使用glide管理依賴的的工程會是這樣:

──$GOPATH/src/myProject (Your project)  ├─ glide.yaml  ├─ glide.lock  ├─ main.go (Your main go code can live here)  ├─ mySubpackage (You can create your own subpackages, too)  |    ├─ foo.go  ├─ vendor       ├─ github.com            ├─ Masterminds                  ├─ ... etc.

glide的功能更豐富一些。

  • glide tree可以很直觀的看到vendor中的依賴包(以後會被移除掉,感覺沒啥用)
  • glide list可以列出vendor下所有包
  • glide支援的Version Control Systems更多,除了支援git,還支援 SVN, Mercurial (Hg), Bzr
  • 最重要的,glide.yaml可以指定更多資訊,例如依賴包的tag、repo、本package的os, arch。允許指定repo可以解決package名不變,但使用的是fork出來的的工程。

govendor

govendor是在vendor之後出來的,功能相對godep多一點,不過就核心問題的解決來說基本是一樣的。govendor產生vendor目錄的時候需要2條命令:

  • govendor init產生vendor/vendor.json,此時檔案中只有本項目的資訊
  • govendor add +external更新vendor/vendor.json,並拷貝GOPATH下的代碼到vendor目錄中。

govendor還可以直接指定依賴包版本來擷取包,這也有了點版本管理的影子了。

# Setup your project.cd "my project in GOPATH"govendor init# Add existing GOPATH files to vendor.govendor add +external# View your work.govendor list# Look at what is using a packagegovendor list -v fmt# Specify a specific version or revision to fetchgovendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55govendor 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".

相比godep來說,govendor略繁瑣一點(比如govendor init有啥用),功能上略豐富一些。

golang官方dep

雖說golang的dep還是alpha狀態,但也可以用了。

$ dep init$ dep ensure -update$ dep ensure github.com/pkg/errors@^0.8.0

沒啥特殊要求的話,一條dep init就夠用了;如果要升級,或者指定某個tag版本,可以用dep ensure。dep還有個很好用的功能,dep prune,可以刪除沒有用到的package。

各依賴管理工具對比

go官方wiki給了一個比較全面的對比。

godep更直觀,使用者也多一些,一些個人的小項目可以用;glide功能更豐富,更接近maven(例如glide.lock跟maven的pom.xml比較類似,可以指定擷取某一個版本),新的項目可以考慮使用glide。

當然了,還是期待golang的dep能夠更好用,解決目前依賴包管理工具片段化過多的問題。

gvt

glide/godep/govendor都只會拉import的依賴包,對於依賴包的依賴包則不會管。這種情況可以用gvt把所有的依賴全部拉到vendor目錄下。是不是很酸爽,但對於解決golang.org庫無法訪問的問題還是很有協助的,並且也可以拉平團隊使用的所有依賴包。

vendor的問題

總的來說glide比較完善了。不過還是有些不太愉快的地方,如代碼copy的泛濫:某個包在不同的項目中各有一份copy,而且其版本可能不一樣;當依賴的包比較多的時候,vendor目錄也會非常龐大。這是vendor的鍋(或者說GOPATH的原罪,不能像maven一樣對同一個包在本地有多個版本,本地只能有一份代碼),似乎沒辦法避免。

當然了,也別指望go會改善這一點。

“Through the design of the standard library, great effort was spent on controlling dependencies. It can be better to copy a little code than to pull in a big library for one function. Dependency hygiene trumps code reuse.” - Go at Google

拷貝點代碼怎麼了,對吧。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.