人的一切痛苦,本質上都是對本身的無能的憤怒。 -- 王小波
Go Module
已經來了,默認Go Module
模式將會在1.13版本發佈。也就是說半年後,就會全面鋪開。鑑於官方提供掃盲文檔中的樣例過於簡單,提供一個更加貼近實際開發過程的例子也許是有必要的。git
官方文檔參考:Go Module Wiki。github
按照官方的說明,Go Module
是在 Go 的 1.11
版本開始引入,可是默認該選項是關閉的,直到1.13
版本將會默認開啓。預計1.13
將會在2019年8月份發佈。因此在這以前,是必須手動開啓Go Module
支持。golang
必要條件:算法
GO111MODULE=on
在開啓Go Module
功能後,官方還提供了環境變量GOPROXY
用於設置包鏡像服務。此處暫不詳細介紹了。緩存
Go Module
帶來的改變GOPATH
做用的改變 引入Go Module
後,環境變量GOPATH
仍是存在的。開啓Go Module
功能開關後,環境變量GOPATH
的做用也發生了改變。函數
When using modules, GOPATH is no longer used for resolving imports. However, it is still used to store downloaded source code (in GOPATH/pkg/mod) and compiled commands (in GOPATH/bin).
翻譯出來就是:工具
GOPATH
再也不用於解析imports
包路徑,即原有的GOPATH/src/
下的包,經過import
是找不到了。Go Module
功能開啓後,下載的包將存放與$GOPATH/pkg/mod
路徑$GOPATH/bin
路徑的功能依舊保持。go.mod
文件配置 開始Go Module
開發以前,首先是初始化一個正確的Go Module
定義,即go.mod
文件。
何爲正確的Go Module
定義。就是說mod
包必須符合。學習
方法一:測試
- 在
$GOPATH/src
的目錄下,建立合理的路徑github.com/liujianping/foo
路徑。- 進入
$GOPATH/src/github.com/liujianping/foo
路徑,執行go mod init
便可。
或者ui
方法二:
- 建立foo路徑,位置任意
- 進入foo目錄,執行
go mod init github.com/liujianping/foo
便可。
生成了go.mod
文件後,就該文件的語法簡單的學習一下。
官方提供了一個簡單全面的例子:
module my/thing go 1.12 require other/thing v1.0.2 require new/thing/v2 v2.3.4 exclude old/thing v1.2.3 replace bad/thing v1.4.5 => good/thing v1.4.5
go get
流程改變 引入Go Module
以後,go get
官方又從新實現了一套。具體實現代碼能夠參考:
Go Module
功能,go get
代碼實現$GOROOT/src/cmd/go/internal/get/get.go
Go Module
功能,go get
代碼實現$GOROOT/src/cmd/go/internal/modget/get.go
簡單說明一下主要區別,更詳細的go get
取包原理放到下篇講解。最直接的區別是:
- 老的
go get
取包過程相似:git clone
+go install
, 開啓Go Module
功能後go get
就只有git clone
或者download
過程了。- 新老實現還有一個不一樣是,二者存包的位置不一樣。前者,存放在
$GOPATH/src
目錄下;後者,存放在$GOPATH/pkg/mod
目錄下。- 老的
go get
取完主包後,會對其repo
下的submodule
進行循環拉取。新的go get
再也不支持submodule
子模塊拉取。
官方的版本因爲過於簡單,連一個基礎的本地第三方包的引入都沒有,僅僅經過引入一個公開的第三方開源包,缺乏了常規本地開發說明。因此,筆者特地提供一個完整的例子,分別從:
三個維度闡釋Go Module
的在實際開發中的具體應用。
本例子的目錄結構以下:
$GOPATH/src ├── github.com └── liujianping ├── demo │ └── go.mod └── foo └── go.mod
建立兩個mod
模塊:demo
與 foo
, 其中 foo
做爲一個依賴包,提供簡單的 Greet
函數供 demo
項目調用。
本地倉庫的意思,就是例子中的兩個包: github.com/liujianping/demo
與 github.com/liujianping/foo
暫時僅僅存在於本地。沒法經過 go get
直接從github.com
上獲取。
經過如下命令,簡單的建立項目代碼:
$: mkdir -p $GOPATH/src/github.com/liujianping/foo $: cd $GOPATH/src/github.com/liujianping/foo $: go mod init $: cat <<EOF > foo.go package foo import "fmt" func Greet(name string) string { return fmt.Sprintf("%s, 你好!", name) } EOF $: mkdir -p $GOPATH/src/github.com/liujianping/demo $: cd $GOPATH/src/github.com/liujianping/demo $: go mod init $: cat <<EOF > main.go package main import ( "fmt" "github.com/liujianping/foo" ) func main(){ fmt.Println(foo.Greet("GoModule")) } EOF
執行完以上命令之後,mod demo
與 foo
的代碼部分就完成了。如今來執行如下:
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go build github.com/liujianping/demo: cannot find module for path github.com/liujianping/foo
從輸出能夠看出,在demo
中調用 foo
的依賴包,在編譯過程就失敗了。demo
沒法找到github.com/liujianping/foo
。爲何這樣?
按照傳統的$GOPATH
引入包原則,只要在$GOPATH/src
存在相應路徑的包,就能夠完成編譯了。從如今的情形就能夠解釋$GOPATH
在Go Module
功能開啓後,對原有引入包的規則發生的改變。
既然,$GOPATH/src
路徑再也不支持。那麼如何解決這個沒法找到包依賴的問題呢?方法有二:
該小節提供本地路徑
方法。
$: cat $GOPATH/src/github.com/liujianping/demo/go.mod module github.com/liujianping/demo
目前demo
項目的go.mod
僅僅一句話,由於沒法找github.com/liujianping/foo
,因此在go build
過程當中也不會修改go.mod
,增長對包github.com/liujianping/foo
的依賴關係。因此,只能是手動處理了。修改go.mod
文件以下:
module github.com/liujianping/demo require github.com/liujianping/foo v0.0.0 replace github.com/liujianping/foo => ../foo
再次執行demo程序:
$: go run main.go go: finding github.com/liujianping/foo v0.0.0 GoModule, 你好!
對於項目中直接引用本地依賴包的官方文檔中有段注意事項:
Note: for direct dependencies, a require directive is needed even when doing a replace. For example, if foo is a direct dependency, you cannot do replace foo => ../foo without a corresponding require for foo. (If you are not sure what version to use in the require directive, you can often use v0.0.0 such as require foo v0.0.0; see #26241).
意思就是,即便是本地依賴包,明確的require
仍然是須要的。至於版本號,其實只要符合SemVer規範就能夠。能夠是v0.0.0
,也能夠是v0.1.2
Go Module
最主要是引入了依賴包的版本控制。因此,咱們不妨就本地版本測試一下。
對本地版本foo
進行相應的git本地版本控制,增長几個版本,代碼中相應的增長版本信息。
package foo import "fmt" func Greet(name string) string { return fmt.Sprintf("%s, 你好! Version 1.0.0", name) }
增長了如下三個版本tag。
$: git tag v0.1.0 v0.2.0 v1.0.0
在demo
項目中,設置foo
版本, go.mod
修改以下:
module github.com/liujianping/demo require github.com/liujianping/foo v0.1.0 replace github.com/liujianping/foo => ../foo
執行demo
程序,輸出以下:
go run main.go go: finding github.com/liujianping/foo v0.1.0 GoModule, 你好! Version 1.0.0
不可貴出結論:go get
是不會從本地倉庫獲取版本信息的,查看go get
在module模式下工具鏈實現代碼也可得出這個結論。
從上節能夠大體瞭解Go Module
的原理。如今咱們將foo
依賴包上傳到github.com
上,包括相應的版本tag。首先github.com
建立相應的項目foo
.再將本地倉庫上傳到遠程倉庫中。
$: git remote add origin git@github.com:liujianping/foo.git $: git push -u origin master
上傳版本tag信息:
$: git push origin --tags
如今完成了github.com/liujianping/foo
依賴包的遠程部署。看看具體實操demo
項目,首先去掉本地的直接依賴。demo
項目的go.mod
以下
$: cat $GOPATH/src/github.com/liujianping/demo/go.mod module github.com/liujianping/demo
從新執行demo
項目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go go: finding github.com/liujianping/foo v1.0.0 go: downloading github.com/liujianping/foo v1.0.0 GoModule, 你好! Version 1.0.0
查看變動後的go.mod
,以下
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.0 // indirect
同時demo
根目錄下,增長了go.sum
文件。
cat go.sum github.com/liujianping/foo v1.0.0 h1:yYoUzvOwC1g+4mXgSEloF187GmEpjKAHEmkApDwvOVQ= github.com/liujianping/foo v1.0.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic=
修改foo版本號到 v0.2.0
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v0.2.0 // indirect
從新執行demo
項目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go go: finding github.com/liujianping/foo v0.2.0 go: downloading github.com/liujianping/foo v0.2.0 GoModule, 你好! Version 0.2.0
再看看go.sum
文件發生的變化:
cat go.sum github.com/liujianping/foo v0.2.0 h1:2JCV7mfUyneSksnWokX0kZoBbtWPoyL8s8iW80WHl/A= github.com/liujianping/foo v0.2.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic= github.com/liujianping/foo v1.0.0 h1:yYoUzvOwC1g+4mXgSEloF187GmEpjKAHEmkApDwvOVQ= github.com/liujianping/foo v1.0.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic=
經過以上步驟,粗略能夠了解針對Go Module
對於遠程倉庫的版本選擇。簡單解釋版本的選擇過程下:
- 檢查遠程倉庫最新的tag版本,有就取得該版本
- 遠程倉庫沒有tag版本時,直接獲取master分支的HEAD版本
- 若是在
go.mod
文件中指定了具體版本,go get
直接獲取該指定版本go.mod
中除了能夠指定具體版本號之外,還支持分支名
繼續對遠程版本foo
增長新的版本v1.0.1
。提交相應代碼並推送版本標籤v1.0.1
到遠端。並從新設置demo
項目中的go.mod
中的依賴版本爲v1.0.0
.以下:
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.0 // indirect
從新執行demo
項目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go GoModule, 你好! Version 1.0.0
此次執行沒有輸出go自己的提示信息,而是直接輸出告終果。由於github.com/liujianping/foo v1.0.0
已經存在於本地的緩存中了,不妨查看一下。
$: ls $GOPATH/pkg/mod/github.com/liujianping/foo@v1.0.0
雖然就demo
項目而言,依賴項目foo
有兩個v1.0.0
與v1.0.1
兩個版本可用。按照GoModule
版本選擇最小版本的算法,demo
項目依舊選擇v1.0.0
版本。
如何更新依賴包版本
更新依賴包的版本,最簡單的方式,直接手動編輯go.mod
設置依賴包版本便可。
另一種方式就是經過go get -u
的方式進行自動更新。具體操做步驟以下:
查看依賴包版本更新信息
$: go list -u -m all go: finding github.com/liujianping/foo v1.0.1 github.com/liujianping/demo github.com/liujianping/foo v1.0.0 [v1.0.1]
更新依賴包版本
$: go get -u go: downloading github.com/liujianping/foo v1.0.1
或者,制定更新patch版本
$: go get -u=patch github.com/liujianping/foo go: downloading github.com/liujianping/foo v1.0.1
此時,go.mod
文件即被更新
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.1
從新執行程序
$: go run main.go GoModule, 你好! Version 1.0.1
基於分支
GoModule
除了支持基於標籤tag
的版本控制,能夠直接利用遠程分支名稱進行開發。
因此本節,筆者就模塊foo
建立一個新的遠程分支develop
.具體代碼,請直接參考github.com/liujianping/foo
項目。
修改demo
項目的go.mod
文件:
module github.com/liujianping/demo require github.com/liujianping/foo develop
再次執行demo
, 結果以下:
$: go run main.go go: finding github.com/liujianping/foo develop go: downloading github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446 GoModule, 你好! Branch develop
查看,go.mod
文件,發生以下變動:
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446
按官方文檔的說明,使用分支名,能夠直接拉取該分支的最後一次提交。從實驗來看, Go Module
一旦發生編譯就會針對分支名的依賴進行版本號固定。
對於私有倉庫而言,其原理與1.3.2中的遠程倉庫是相似的。惟一不一樣之處是,go get
取包的過程可能存在種種障礙,致使沒法經過go get
取到私有倉庫包。主要緣由多是:
致使按照正常的go get
過程取包失敗。若是瞭解了go get
取包原理,以上問題也就迎刃而解了。
更多文章可直接訪問我的BLOG:GitDiG.com