在 Java 的項目中,有 Maven 和 Gradle 這些很好用的依賴版本管理工具,簡直不要太方便了,可是在 Golang 的項目中,以前的 Golang 官方並無提供版本管理工具,咱們之前用 go get 獲取依賴實際上是有潛在危險的,由於咱們不肯定最新版依賴是否會破壞掉咱們項目對依賴包的使用方式,即當前項目可能會出現不兼容最新依賴包的問題。以後官方出了一個 vendor 機制,將項目依賴的包都放在該目錄中,但這也並無很好地管理依賴的版本。以後官方出了一個準官方版本管理工具 go dep,這也算是 go modules 的前身了吧。隨着 Go1.11 的發佈,Golang 給咱們帶來了 module 全新特性,這是 Golang 新的一套依賴管理系統。如今 Go1.12 已經發布了,go modules 進一步穩定,但官方仍是沒有將其設爲默認機制,因此踩坑之路是必須的,本篇文章除了詳細說明 go modules 的特性以及使用以外,還總結了我在這個過程當中遇到的一些「坑」。git
•建立項目程序員
在默認狀況下,$GOPATH 默認狀況下是不支持 go mudules 的,咱們須要在項目目錄下手動執行如下命令:github
$ export GO111MODULE=on
這也代表了 go 要利用 modules 機制消滅 $GOPATH 的決心啊!
golang
爲了配合 go modules 機制,咱們 $GOPATH 之外的目錄建立一個 testmod 的包:spring
$ mkdir testmod$ cd testmod$ echo 'package testmod import "fmt" func SayHello(name string) string { return fmt.Sprintf("Hello, %s", name)}' >> testmod.go
•初始化 module:sql
$ go mod init github.com/objcoding/testmodgo: creating new go.mod: module github.com/objcoding/testmod
以上命令會在項目中建立一個 go.mod 文件,初始化內容以下:
mybatis
module github.com/objcoding/testmod
這時,咱們的項目已經成爲了一個 module 了。
架構
•推送到 github 倉庫app
$ git init$ git add *$ git commit -am "First commit"$ git push -u origin master
在這裏我也着重說下關於項目依賴包引用地址的問題,這個問題雖小,但也確實很困擾人,因此必須得說一下:
編輯器
go mudules 出現以前,在一個項目中有不少個包,在項目內,有些包須要依賴項目內其它包,假設項目有個包,相對於 gopath 的地址是 objcoding/mypackage,在項目內其它包引用這個包時,就能夠經過如下引用:
import myproject/mypackage
但你有沒有想過,當別的項目須要引用你的項目中的某些包,那麼就須要遠程下載依賴包了,這時就須要項目的倉庫地址引用,好比下面這樣:
import github.com/objcoding/myproject/mypackage
go modules 發佈以後,就徹底統一了包引用的地址,如上面咱們說的建立 go.mod 文件後,初始化內容的第一行就是咱們說的項目依賴路徑,一般來講該地址就是項目的倉庫地址,全部須要引用項目包的地址都填寫這個地址,不管是內部之間引用仍是外部引用,舉個例子,goim 的內部包引用:
go.mod module:
內部包引用:
也便是說,在項目 啓用了 go modules 以後,引用包必須跟 go mod 文件第一行包名同樣,
依賴的包都會保存在 ${GOPATH}/pkg/mod 文件夾中了,咱們也能夠在項目底部那裏查看依賴包:
但也有可能會出現依賴包地址正確但會報紅的狀況,這時極有多是你在 Goland 編輯器中沒有將項目設置爲 go modules 項目,具體設置以下:
勾選了該選項以後,就會在 External Libraries 中出現 Go Modules 目錄。
go modules 是一個版本化依賴管理系統,版本須要遵循一些規則,好比版本號須要遵循如下格式:
vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdefvX.0.0-yyyymmddhhmmss-abcdefabcdefvX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdefvX.Y.Z
vX.Y.Z 是咱們倉庫打的標籤版本,也就是 go modules 是根據倉庫標籤來肯定版本號的,所以咱們發佈版本時,須要給咱們的倉庫打上一個標籤版本號。
也就是版本號 + 時間戳 +hash,咱們本身指定版本時只須要制定版本號便可,沒有版本 tag 的則須要找到對應 commit 的時間和 hash 值。
還有一個重要的規則是,版本 0 和 1,最好須要有不一樣的依賴路徑,如:v1.0.0 和 v2.0.0 是有不一樣的依賴路徑,下面會詳細介紹一下這個版本規則。
瞭解了 go modules 的版本規則後,如今咱們發佈一下該項目的版本:
$ git tag v1.0.0$ git push --tags
這時咱們最好還須要建立一條 v1 分支,以便咱們在其它分支寫代碼不會影響到 v1.0.0 版本:
$ git checkout -b v1$ git push -u origin v1
如今咱們把剛剛作好的 module,拿過來用,新建一個 gomodules 項目:
package main
import ( "fmt" "github.com/objcoding/testmod")
func main() { fmt.Println(testmod.SayHello("張乘輝"))}
之前咱們能夠直接經過 go get 命令獲取依賴包,可是對於一個 module 項目來講,就遠遠比這個有趣多了,現將項目初始化成 module 項目:
$ go mod init
這時 go build 等命令就會下載依賴包,並把依賴信息添加到 go.mod 文件中,同時把依賴版本哈希信息存到 go.sum 文件中:
$ go build
go: finding github.com/objcoding/testmod v1.0.0go: downloading github.com/objcoding/testmod v1.0.0
這時,go.mod 文件內容以下:
module gomodulesrequire github.com/objcoding/testmod v1.0.0
go.sum 文件內容以下:
github.com/objcoding/testmod v1.0.0 h1:fGa15gBXoqkG0BVkQGP9i5Pg2nt8nayFpHFf+GLiX6A=github.com/objcoding/testmod v1.0.0/go.mod h1:LGpYEmOLZhLQC3JW88STU2Ja3rsfoGZmsidsHJhDNGU=
這裏還須要注意的是,有時候咱們引用 golang.org/x/ 的一些包,但發如今偉大的天朝這個地址是被qian了,可是咱們程序員也充分發揮了勤奮好學的優良傳統,在 go modules 中設置了 goproxy 機制,若是 go modules 設置了代理,會優先從代理中下載依賴包,在 /etc/profile 中加入如下內容:
export GOPROXY="https://goproxy.io"
goproxy.io 谷歌官方的代理地址,固然還有不少國內優秀的第三方代理。
你也能夠在 Goland編輯器中設置:
如今咱們來升級一下 testmod 項目:
$ cd gomodules$ echo 'package testmod import "fmt" func SayHello(name string) string { return fmt.Sprintf("你好, %s", name)}' >> testmod.go
我把「Hello」改爲「你好」,咱們把這個修改在 v1 分支中進行:
$ git commit -m "update testmod" testmod.go$ git tag v1.0.1$ git push --tags origin v1
如今咱們的 項目已經升級到 v1.0.1 版本了,咱們能夠有多種方式獲取這個版本依賴,go1.11 中,go get 擁有了不少新特性,咱們能夠直接經過如下命令獲取 v1.01 版本依賴:
$ go get github.com/objcoding/testmod@v1.0.1
也能夠經過 go mod 命令:
$ go mod edit -require="github.com/objcoding/testmod@v1.0.1"
$ go mod tidy
go mod edit -require 能夠主動修改 go.md 文件中依賴的版本號,而後經過 go mod tidy 對版本進行更新,這是一條神同樣的命令,它會自動清理掉不須要的依賴項,同時能夠將依賴項更新到當前版本。
上面版本規則說了,版本 0 和 1,即大版本更新,最好須要有不一樣的依賴路徑,如:v1.0.0 和 v2.0.0 是有不一樣的依賴路徑,那麼用 go modules 怎麼實現呢,咱們能夠經過修改 go.mod 文件第一行添加新路徑:
$ cd testmod$ echo 'module github.com/objcoding/testmod/v2' >> go.mod
而後咱們修改 testmod 函數 Hi():
$ cd testmod
$ echo 'package testmodimport ( "fmt")func SayHello(name, str string) string { return fmt.Sprintf("你好, %s, %s", name, str)}' >> testmod.go
這時,SayHello() 方法將不兼容 v1 版本,咱們須要新建一個 v2.0.0 版本,仍是老樣子,咱們最好在 v2.0.0 版本新建一條 v2 分分支,將 v2.0.0 版本的代碼寫到這條分支中(這只是一個規範,實際上你將代碼也寫到任何分支中都行,Go並無這個規範):
$ git add *$ git checkout -b v2$ git commit testmod.go -m "v2.0.0"$ git tag v2.0.0$ git push --tags origin v2
這時候 testmod 的版本已經更新到 v2.0.0 版本了,該版本並不兼容之前的版本,可是目前項目依然只能獲取到 v1.0.1 的依賴版本,由於咱們 testmod 的 module 路徑已經加上 v2 了,所以並不會出現衝突的問題,那麼若是咱們須要使用 v2.0.0 版本呢?咱們只須要在項目中更改一下 import 路徑:
package main
import ( "fmt" "github.com/objcoding/testmod/v2")func main() { fmt.Println(testmod.SayHello("張乘輝", "最近過得怎樣"))}
執行:
go mod tidy
這時咱們把 testmod 依賴版本號更新到了 v2.0.0 版本了,雖然是此時的 import 路徑是以 「v2」 結尾,可是 Go 很人性化,咱們依然可使用 testmod 來使用。
Go 團隊表示,在 Go 1.12 以前,這個特性都將會處於實驗性階段,Go 團隊會努力保持兼容性。一旦模塊穩定以後,對 GOPATH 的支持將會被移除掉。
近期熱文
Mybatis-spring源碼分析之註冊Mapper Bean
https://mp.weixin.qq.com/s/2v8kGm8T9BQFmpLfQE7-wg