初窥Go module
目錄
一. 建立試驗環境
二. 傳統Go構建以及包依賴管理的回顧
三. go modules定義、experiment開關以及“依賴管理”的工作模式
四. go modules的依賴版本選擇
五. go modules與vendor
六. 小結
自2007年“三巨頭(Robert Griesemer,?Rob Pike,?Ken Thompson)”提出設計和實現Go語言以來,Go語言已經發展和演化了十余年了。這十余年來,Go取得了巨大的成就,先后在2009年和2016年當選TIOBE年度最佳編程語言,并在全世界范圍內擁有數量龐大的擁躉。不過和其他主流編程語言一樣,Go語言也不是完美的,不能滿足所有開發者的“口味”。這些年來Go在“包依賴管理”和“缺少泛型”兩個方面飽受詬病,它們也是Go粉們最希望Go核心Team重點完善的兩個方面。
今年(2018)年初,Go核心Team的技術leader,也是Go Team最早期成員之一的Russ Cox在個人博客上連續發表了七篇文章,系統闡述了Go team解決“包依賴管理”的技術方案:?vgo。vgo的主要思路包括:Semantic Import Versioning、Minimal Version Selection、引入Go module等。這七篇文章的發布引發了Go社區激烈地爭論,尤其是MVS(最小版本選擇)與目前主流的依賴版本選擇方法的相悖讓很多傳統Go包管理工具的維護者“不滿”,尤其是“準官方工具”:dep。vgo方案的提出也意味著dep項目的生命周期即將進入尾聲。
5月份,Russ Cox的Proposal “cmd/go: add package version support to Go toolchain”被accepted,這周五早些時候Russ Cox將vgo的代碼merge到Go主干,并將這套機制正式命名為“go module”。由于vgo項目本身就是一個實驗原型,merge到主干后,vgo這個術語以及vgo項目的使命也就此結束了。后續Go modules機制將直接在Go主干上繼續演化。
Go modules是go team在解決包依賴管理方面的一次勇敢嘗試,無論如何,對Go語言來說都是一個好事。在本篇文章中,我們就一起來看看這個新引入的go modules機制。
?
一. 建立試驗環境
由于加入go modules experiment機制的Go 1.11版本尚未正式發布,且go 1.11 beta1版本發布在go modules merge到主干之前,因此我們要進行go module試驗只能使用Go tip版本,即主干上的最新版本。我們需要通過編譯Go源碼包的方式獲得支持go module的go編譯器:
編譯Go項目源碼的前提是你已經安裝了一個發布版,比如Go 1.10.3。然后按照下面步驟執行即可:
$ git clone https://github.com/golang/go.git $ mv go go-tip $ cd go-tip $ ./all.bash Building Go cmd/dist using /root/.bin/go1.10.2. Building Go toolchain1 using /root/.bin/go1.10.2. Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1. Building Go toolchain2 using go_bootstrap and Go toolchain1. Building Go toolchain3 using go_bootstrap and Go toolchain2. Building packages and commands for linux/amd64. ##### Testing packages. ok archive/tar 0.026s ... ... ##### API checkALL TESTS PASSED --- Installed Go for linux/amd64 in /root/.bin/go-tip Installed commands in /root/.bin/go-tip/bin *** You need to add /root/.bin/go-tip/bin to your PATH.驗證源碼編譯方式的安裝結果:
# ./go version go version devel +a241922 Fri Jul 13 00:03:31 2018 +0000 linux/amd64查看有關go module的手冊:
$ ./go help mod usage: go mod [-v] [maintenance flags]Mod performs module maintenance operations as specified by the following flags, which may be combined.The -v flag enables additional output about operations performed.The first group of operations provide low-level editing operations for manipulating go.mod from the command line or in scripts or other tools. They read only go.mod itself; they do not look up any information about the modules involved.The -init flag initializes and writes a new go.mod to the current directory, in effect creating a new module rooted at the current directory. The file go.mod must not already exist. If possible, mod will guess the module path from import comments (see 'go help importpath') or from version control configuration. To override this guess, use the -module flag. (Without -init, mod applies to the current module.)The -module flag changes (or, with -init, sets) the module's path (the go.mod file's module line). ... ...無法通過編譯源碼的方式獲取go tip版的小伙伴們也不用著急,在后續即將發布的 go 1.11 beta2 版本中將會包含對go modules的支持,到時候按常規方式安裝beta2即可體驗go modules。
?
二. 傳統Go構建以及包依賴管理的回顧
Go在構建設計方面深受Google內部開發實踐的影響,比如go get的設計就深受Google內部單一代碼倉庫(single monorepo)和基于主干(trunk/mainline based)的開發模型的影響:只獲取Trunk/mainline代碼和版本無感知。
Google內部基于主干的開發模型:
– 所有開發人員基于主干trunk/mainline開發:提交到trunk或從trunk獲取最新的代碼(同步到本地workspace)
– 版本發布時,建立Release branch,release branch實質上就是某一個時刻主干代碼的快照;
– 必須同步到release branch上的bug fix和增強改進代碼也通常是先在主干上提交(commit),然后再cherry-pick到release branch上
我們知道go get獲取的代碼會放在$GOPATH/src下面,而go build會在$GOROOT/src和$GOPATH/src下面按照import path去搜索package,由于go get 獲取的都是各個package repo的trunk/mainline的代碼,因此,Go 1.5?之前的Go compiler都是基于目標Go程序依賴包的trunk/mainline代碼去編譯的。這樣的機制帶來的問題是顯而易見的,至少包括:
- 因依賴包的trunk的變化,導致不同人獲取和編譯你的包/程序時得到的結果實質是不同的,即不能實現reproduceable build
- 因依賴包的trunk的變化,引入不兼容的實現,導致你的包/程序無法通過編譯
- 因依賴包演進而無法通過編譯,導致你的包/程序無法通過編譯
為了實現reproduceable build,Go 1.5 引入了Vendor機制,Go編譯器會優先在vendor下搜索依賴的第三方包,這樣如果開發者將特定版本的依賴包存放在vendor下面并提交到code repo,那么所有人理論上都會得到同樣的編譯結果,從而實現reproduceable build。
在Go 1.5發布后的若干年,gopher們把注意力都集中在如何利用vendor解決包依賴問題,從手工添加依賴到vendor、手工更新依賴,到一眾包依賴管理工具的誕生:比如:?govendor、glide以及號稱準官方工具的dep,努力地嘗試著按照當今主流思路解決著諸如:“鉆石型依賴”等難題。
正當gopher認為dep將“順理成章”地升級為go toolchain一部分的時候,vgo橫空出世,并通過對“Semantic Import Versioning”和”Minimal Version Selected”的設定,在原Go tools上簡單快速地實現了Go原生的包依賴管理方案 。vgo就是go module的前身。
?
三. go modules定義、experiment開關以及“依賴管理”的工作模式
通常我們會在一個repo(倉庫)中創建一組Go package,repo的路徑比如:github.com/bigwhite/gocmpp會作為go package的導入路徑(import path),Go 1.11給這樣的一組在同一repo下面的packages賦予了一個新的抽象概念: module,并啟用一個新的文件go.mod記錄module的元信息。
不過一個repo對應一個module這種說法其實并不精確也不正確,一個repo當然可以擁有多個module,很多公司或組織是喜歡用monorepo的,這樣勢必有在單一的monorepo建立多個module的需求,顯然go modules也是支持這種情況的。
圖:single repo,single module
圖:single monorepo,multiple modules
是時候上代碼了!
我們在~/test下建立hello目錄(注意:$GOPATH=~/go,顯然hello目錄并不在GOPATH下面)。hello.go的代碼如下:
// hello.go package mainimport "bitbucket.org/bigwhite/c"func main() {c.CallC() }我們構建一下hello.go這個源碼文件:
# go build hello.go hello.go:3:8: cannot find package "bitbucket.org/bigwhite/c" in any of:/root/.bin/go-tip/src/bitbucket.org/bigwhite/c (from $GOROOT)/root/go/src/bitbucket.org/bigwhite/c (from $GOPATH)構建錯誤!錯誤原因很明了:在本地的GOPATH下并沒有找到bitbucket.org/bigwhite/c路徑的package c。傳統fix這個問題的方法是手工將package c通過go get下載到本地(并且go get會自動下載package c所依賴的package d):
# go get bitbucket.org/bigwhite/c # go run hello.go call C: master branch--> call D:call D: master branch--> call D end這種我們最熟悉的Go compiler從$GOPATH下(以及vendor目錄下)搜索目標程序的依賴包的模式稱為:“GOPATH mode”。
GOPATH是Go最初設計的產物,在Go語言快速發展的今天,人們日益發現GOPATH似乎不那么重要了,尤其是在引入vendor以及諸多包管理工具后。并且GOPATH的設置還會讓Go語言新手感到些許困惑,提高了入門的門檻。Go core team也一直在尋求“去GOPATH”的方案,當然這一過程是循序漸進的。Go 1.8版本中,如果開發者沒有顯式設置GOPATH,Go會賦予GOPATH一個默認值(在linux上為$HOME/go)。雖說不用再設置GOPATH,但GOPATH還是事實存在的,它在go toolchain中依舊發揮著至關重要的作用。
Go module的引入在Go 1.8版本上更進了一步,它引入了一種新的依賴管理mode:“module-aware mode”。在該mode下,某源碼樹(通常是一個repo)的頂層目錄下會放置一個go.mod文件,每個go.mod文件定義了一個module,而放置go.mod文件的目錄被稱為module root目錄(通常對應一個repo的root目錄,但不是必須的)。module root目錄以及其子目錄下的所有Go package均歸屬于該module,除了那些自身包含go.mod文件的子目錄。
在“module-aware mode”下,go編譯器將不再在GOPATH下面以及vendor下面搜索目標程序依賴的第三方Go packages。我們來看一下在“module-aware mode”下hello.go的構建過程:
我們首先在~/test/hello下創建go.mod:
// go.mod module hello然后構建hello.go
# go build hello.go go: finding bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02 go: finding bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b go: downloading bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b go: downloading bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02# ./hello call C: master branch--> call D:call D: master branch--> call D end我們看到go compiler并沒有去使用之前已經下載到GOPATH下的bitbucket.org/bigwhite/c和bitbucket.org/bigwhite/d,而是主動下載了這兩個包并成功編譯。我們看看執行go build后go.mod文件的內容:
# cat go.mod module hellorequire (bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24bbitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a02 // indirect )我們看到go compiler分析出了hello module的依賴,將其放入go.mod的require區域。由于c、d兩個package均沒有版本發布(打tag),因此go compiler使用了c、d的當前最新版,并以Pseudo-versions的形式記錄之。并且我們看到:hello module并沒有直接依賴d package,因此在d的記錄后面通過注釋形式標記了indirect,即非直接依賴,也就是傳遞依賴。
在“module-aware mode”下,go compiler將下載的依賴包緩存在$GOPATH/src/mod下面:
// $GOPATH/src/mod # tree -L 3 . ├── bitbucket.org │ └── bigwhite │ ├── c@v0.0.0-20180714063616-861b08fcd24b │ └── d@v0.0.0-20180714005150-3e3f9af80a02 ├── cache │ ├── download │ │ ├── bitbucket.org │ │ ├── golang.org │ │ └── rsc.io │ └── vcs │ ├── 064503657de46d4574a6ab937a7a3b88fee03aec15729f7493a3dc8e35cc6d80 │ ├── 064503657de46d4574a6ab937a7a3b88fee03aec15729f7493a3dc8e35cc6d80.info │ ├── 0c8659d2f971b567bc9bd6644073413a1534735b75ea8a6f1d4ee4121f78fa5b ... ...我們看到c、d兩個package也是按照“版本”進行緩存的,便于后續在“module-aware mode”下進行包構建使用。
Go modules機制在go 1.11中是experiment feature,按照Go的慣例,在新的experiment feature首次加入時,都會有一個特性開關,go modules也不例外,GO111MODULE這個臨時的環境變量就是go module特性的experiment開關。GO111MODULE有三個值:auto、on和off,默認值為auto。GO111MODULE的值會直接影響Go compiler的“依賴管理”模式的選擇(是GOPATH mode還是module-aware mode),我們詳細來看一下:
-
當GO111MODULE的值為off時,go modules experiment feature關閉,go compiler顯然會始終使用GOPATH mode,即無論要構建的源碼目錄是否在GOPATH路徑下,go compiler都會在傳統的GOPATH和vendor目錄(僅支持在gopath目錄下的package)下搜索目標程序依賴的go package;
-
當GO111MODULE的值為on時(export GO111MODULE=on),go modules experiment feature始終開啟,與off相反,go compiler會始終使用module-aware mode,即無論要構建的源碼目錄是否在GOPATH路徑下,go compiler都不會在傳統的GOPATH和vendor目錄下搜索目標程序依賴的go package,而是在go mod命令的緩存目錄($GOPATH/src/mod)下搜索對應版本的依賴package;
-
當GO111MODULE的值為auto時(不顯式設置即為auto),也就是我們在上面的例子中所展現的那樣:使用GOPATH mode還是module-aware mode,取決于要構建的源碼目錄所在位置以及是否包含go.mod文件。如果要構建的源碼目錄不在以GOPATH/src為根的目錄體系下,且包含go.mod文件(兩個條件缺一不可),那么使用module-aware mode;否則使用傳統的GOPATH mode。
?
四. go modules的依賴版本選擇
1. build list和main module
go.mod文件一旦創建后,它的內容將會被go toolchain全面掌控。go toolchain會在各類命令執行時,比如go get、go build、go mod等修改和維護go.mod文件。
之前的例子中,hello module依賴的c、d(indirect)兩個包均沒有顯式的版本信息(比如: v1.x.x),因此go mod使用Pseudo-versions機制來生成和記錄c, d的“版本”,我們可以通過下面命令查看到這些信息:
# go list -m -json all {"Path": "hello","Main": true,"Dir": "/root/test/hello" } {"Path": "bitbucket.org/bigwhite/c","Version": "v0.0.0-20180714063616-861b08fcd24b","Time": "2018-07-14T06:36:16Z","Dir": "/root/go/src/mod/bitbucket.org/bigwhite/c@v0.0.0-20180714063616-861b08fcd24b" } {"Path": "bitbucket.org/bigwhite/d","Version": "v0.0.0-20180714005150-3e3f9af80a02","Time": "2018-07-14T00:51:50Z","Indirect": true,"Dir": "/root/go/src/mod/bitbucket.org/bigwhite/d@v0.0.0-20180714005150-3e3f9af80a02" }go list -m輸出的信息被稱為build list,也就是構建當前module所要構建的所有相關package(及版本)的列表。在輸出信息中我們看到 “Main”: true這一信息,標識當前的module為“main module”。所謂main module,即是go build命令執行時所在當前目錄所歸屬的那個module,go命令會在當前目錄、當前目錄的父目錄、父目錄的父目錄…等下面尋找go.mod文件,所找到的第一個go.mod文件對應的module即為main module。如果沒有找到go.mod,go命令會提示下面錯誤信息:
# go build test/hello/hello.go go: cannot find main module root; see 'go help modules'當然我們也可以使用下面命令簡略輸出build list:
# go list -m all hello bitbucket.org/bigwhite/c v0.0.0-20180714063616-861b08fcd24b bitbucket.org/bigwhite/d v0.0.0-20180714005150-3e3f9af80a022. module requirement
現在我們給c、d兩個package打上版本信息:
package c: v1.0.0 v1.1.0 v1.2.0package d: v1.0.0 v1.1.0 v1.2.0 v1.3.0然后清除掉$GOPATH/src/mod目錄,并將hello.mod重新置為初始狀態(只包含module字段)。接下來,我們再來構建一次hello.go:
// ~/test/hello目錄下# go build hello.go go: finding bitbucket.org/bigwhite/c v1.2.0 go: downloading bitbucket.org/bigwhite/c v1.2.0 go: finding bitbucket.org/bigwhite/d v1.3.0 go: downloading bitbucket.org/bigwhite/d v1.3.0# ./hello call C: v1.2.0--> call D:call D: v1.3.0--> call D end# cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.2.0 // indirect (c package被標記為indirect,這似乎是當前版本的一個bug)bitbucket.org/bigwhite/d v1.3.0 // indirect )我們看到,再一次初始構建hello module時,Go compiler不再用最新的commit revision所對應的Pseudo-version,而是使用了c、d兩個package的最新發布版(c:v1.2.0,d: v1.3.0)。
如果我們對使用的c、d版本有特殊約束,比如:我們使用package c的v1.0.0,package d的v1.1.0版本,我們可以通過go mod -require來操作go.mod文件,更新go.mod文件中的require段的信息:
# go mod -require=bitbucket.org/bigwhite/c@v1.0.0 # go mod -require=bitbucket.org/bigwhite/d@v1.1.0# cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.0.0 // indirectbitbucket.org/bigwhite/d v1.1.0 // indirect )# go build hello.go go: finding bitbucket.org/bigwhite/d v1.1.0 go: finding bitbucket.org/bigwhite/c v1.0.0 go: downloading bitbucket.org/bigwhite/c v1.0.0 go: downloading bitbucket.org/bigwhite/d v1.1.0# ./hello call C: v1.0.0--> call D:call D: v1.1.0--> call D end我們看到由于我們顯式地修改了對package c、d兩個包的版本依賴約束,go build構建時會去下載package c的v1.0.0和package d的v1.1.0版本并完成構建。
3. module query
除了通過傳入package@version給go mod -requirement來精確“指示”module依賴之外,go mod還支持query表達式,比如:
# go mod -require='bitbucket.org/bigwhite/c@>=v1.1.0'go mod會對query表達式做求值,得出build list使用的package c的版本:
# cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.1.0bitbucket.org/bigwhite/d v1.1.0 // indirect )# go build hello.go go: downloading bitbucket.org/bigwhite/c v1.1.0 # ./hello call C: v1.1.0--> call D:call D: v1.1.0--> call D endgo mod對module query進行求值的算法是“選擇最接近于比較目標的版本(tagged version)”。以上面例子為例:
query text: >=v1.1.0 比較的目標版本為v1.1.0 比較形式:>=因此,滿足這一query的最接近于比較目標的版本(tagged version)就是v1.1.0。
如果我們給package d增加一個約束“小于v1.3.0”,我們再來看看go mod的選擇:
# go mod -require='bitbucket.org/bigwhite/d@<v1.3.0' # cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.1.0 // indirectbitbucket.org/bigwhite/d <v1.3.0 )# go build hello.go go: finding bitbucket.org/bigwhite/d v1.2.0 go: downloading bitbucket.org/bigwhite/d v1.2.0# ./hello call C: v1.1.0--> call D:call D: v1.2.0--> call D end我們看到go mod選擇了package d的v1.2.0版本,根據module query的求值算法,v1.2.0恰是最接近于“小于v1.3.0”的tagged version。
用下面這幅示意圖來呈現這一算法更為直觀一些:
4. minimal version selection(mvs)
到目前為止,我們所使用的example都是最最簡單的,hello module所依賴的package c和package d并沒有自己的go.mod,也沒有定義自己的requirements。對于復雜的包依賴場景,Russ Cox在“Minimal Version Selection”一文中給過形象的算法解釋(注意:這個算法僅是便于人類理解,但是性能低下,真正的實現并非按照這個算法實現):
例子情景
算法的形象解釋
MVS以build list為中心,從一個空的build list集合開始,先加入main module(A1),然后遞歸計算main module的build list,我們看到在這個過程中,先得到C 1.2的build list,然后是B 1.2的build list,去重合并后形成A1的rough build list,選擇集合中每個module的最新version,最終形成A1的build list。
我們改造一下我們的例子,讓它變得復雜些!
首先,我們為package c添加go.mod文件,并為其打一個新版本:v1.3.0:
//bitbucket.org/bigwhite/c/go.mod module bitbucket.org/bigwhite/crequire (bitbucket.org/bigwhite/d v1.2.0 )在module bitbucket.org/bigwhite/c的module文件中,我們為其添加一個requirment: bitbucket.org/bigwhite/d@v1.2.0。
接下來,我們將hello module重置為初始狀態,并刪除$GOPATH/src/mod目錄。我們修改一下hello module的hello.go如下:
package mainimport "bitbucket.org/bigwhite/c" import "bitbucket.org/bigwhite/d"func main() {c.CallC()d.CallD() }我們讓hello module也直接調用package d,并且我們在初始情況下,給hello module添加一個requirement:
module hellorequire (bitbucket.org/bigwhite/d v1.3.0 )好了,這次我們再來構建一下hello module:
# go build hello.go go: finding bitbucket.org/bigwhite/d v1.3.0 go: downloading bitbucket.org/bigwhite/d v1.3.0 go: finding bitbucket.org/bigwhite/c v1.3.0 go: downloading bitbucket.org/bigwhite/c v1.3.0 go: finding bitbucket.org/bigwhite/d v1.2.0 # cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.3.0 // indirectbitbucket.org/bigwhite/d v1.3.0 // indirect )# ./hello call C: v1.3.0--> call D:call D: v1.3.0--> call D end call D: v1.3.0我們看到經過mvs算法后,go compiler最終選擇了d v1.3.0版本。這里也模仿Russ Cox的圖解給出hello module的mvs解析示意圖(不過我這個例子還是比較simple):
5. 使用package d的v2版本
按照語義化版本規范,當出現不兼容性的變化時,需要升級版本中的major值,而go modules允許在import path中出現v2這樣的帶有major版本號的路徑,表示所用的package為v2版本下的實現。我們甚至可以同時使用一個package的v0/v1和v2兩個版本的實現。我們依舊使用上面的例子來實操一下如何在hello module中使用package d的兩個版本的代碼。
我們首先需要為package d建立module文件:go.mod,并標識出當前的module為:bitbucket.org/bigwhite/d/v2(為了保持與v0/v1各自獨立演進,可通過branch的方式來實現),然后基于該版本打v2.0.0 tag。
// bitbucket.org/bigwhite/d #cat go.mod module bitbucket.org/bigwhite/d/v2改造一下hello module,import d的v2版本:
// hello.go package mainimport "bitbucket.org/bigwhite/c" import "bitbucket.org/bigwhite/d/v2"func main() {c.CallC()d.CallD() }清理hello module的go.mod,僅保留對package c的requirement:
module hellorequire (bitbucket.org/bigwhite/c v1.3.0 )清理$GOPATH/src/mod目錄,然后重新構建hello module:
# go build hello.go go: finding bitbucket.org/bigwhite/c v1.3.0 go: finding bitbucket.org/bigwhite/d v1.2.0 go: downloading bitbucket.org/bigwhite/c v1.3.0 go: downloading bitbucket.org/bigwhite/d v1.2.0 go: finding bitbucket.org/bigwhite/d/v2 v2.0.0 go: downloading bitbucket.org/bigwhite/d/v2 v2.0.0# cat go.mod module hellorequire (bitbucket.org/bigwhite/c v1.3.0 // indirectbitbucket.org/bigwhite/d/v2 v2.0.0 // indirect )# ./hello call C: v1.3.0--> call D:call D: v1.2.0--> call D end call D: v2.0.0我們看到c package依然使用的是d的v1.2.0版本,而main中使用的package d已經是v2.0.0版本了。
?
五. go modules與vendor
在最初的設計中,Russ Cox是想徹底廢除掉vendor的,但在社區的反饋下,vendor得以保留,這也是為了兼容Go 1.11之前的版本。
Go modules支持通過下面命令將某個module的所有依賴保存一份copy到root module dir的vendor下:
# go mod -vendor # ls go.mod go.sum hello.go vendor/ # cd vendor # ls bitbucket.org/ modules.txt # cat modules.txt # bitbucket.org/bigwhite/c v1.3.0 bitbucket.org/bigwhite/c # bitbucket.org/bigwhite/d v1.2.0 bitbucket.org/bigwhite/d # bitbucket.org/bigwhite/d/v2 v2.0.0 bitbucket.org/bigwhite/d/v2# tree . . ├── bitbucket.org │ └── bigwhite │ ├── c │ │ ├── c.go │ │ ├── go.mod │ │ └── README.md │ └── d │ ├── d.go │ ├── README.md │ └── v2 │ ├── d.go │ ├── go.mod │ └── README.md └── modules.txt5 directories, 9 files這樣即便在go modules的module-aware mode模式下,我們依然可以只用vendor下的package來構建hello module。比如:我們先刪除掉$GOPATH/src/mod目錄,然后執行:
# go build -getmode=vendor hello.go # ./hello call C: v1.3.0--> call D:call D: v1.2.0--> call D end call D: v2.0.0當然生成的vendor目錄還可以兼容go 1.11之前的go compiler。不過由于go 1.11之前的go compiler不支持在GOPATH之外使用vendor機制,因此我們需要將hello目錄copy到$GOPATH/src下面,再用go 1.10.2版本的compiler編譯它:
# go version go version go1.10.2 linux/amd64 ~/test/hello# go build hello.go hello.go:3:8: cannot find package "bitbucket.org/bigwhite/c" in any of:/root/.bin/go1.10.2/src/bitbucket.org/bigwhite/c (from $GOROOT)/root/go/src/bitbucket.org/bigwhite/c (from $GOPATH) hello.go:4:8: cannot find package "bitbucket.org/bigwhite/d/v2" in any of:/root/.bin/go1.10.2/src/bitbucket.org/bigwhite/d/v2 (from $GOROOT)/root/go/src/bitbucket.org/bigwhite/d/v2 (from $GOPATH)# cp -r hello ~/go/src # cd ~/go/src/hello # go build hello.go # ./hello call C: v1.3.0--> call D:call D: v1.2.0--> call D end call D: v2.0.0編譯輸出和程序的執行結果均符合預期。
?
六. 小結
go modules剛剛merge到go trunk中,問題還會有很多。merge后很多gopher也提出了諸多問題,可以在這里查到。當然哪位朋友如果也遇到了go modules的問題,也可以在go官方issue上提出來,幫助go team盡快更好地完善go 1.11的go modules機制。
go module的加入應該算是go 1.11版本最大的變化,go module的內容很多,短時間內我的理解也可能存在偏差和錯誤,歡迎廣大gopher們交流指正。
參考資料:
- go modules have landed?需科學上網訪問
- Go & Versioning
- go help mod
總結
以上是生活随笔為你收集整理的初窥Go module的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 理解Go 1.5 vendor
- 下一篇: Spring Boot 最核心的 3 个