今天天色剛剛亮起,起床看到golang 1.11正式發版了,有著兩個重要的特性:modules和WebAssembly。
本博文只要說的是modules,congJava轉golang的同學肯定是對golang的包管理充滿了無奈之情,我也曾在部落格中介紹過glide,也介紹過dep,現在我們再一次升級介紹modules。
什麼是modules
現在都在說modules,那麼它是什嗎?
到文檔看看 Modules, module versions, and more:
A module is a collection of related Go packages. Modules are the unit of source code interchange and versioning. The go command has direct support for working with modules, including recording and resolving dependencies on other modules. Modules replace the old GOPATH-based approach to specifying which source files are used in a given build.
翻譯一下:
模組是相關Go包的集合。modules是原始碼交換和版本控制的單元。 go命令直接支援使用modules,包括記錄和解析對其他模組的依賴性。modules替換舊的基於GOPATH的方法來指定在給定構建中使用哪些源檔案。
可以得到兩個重要訊息:
- Go命令列支援modules操作
- modules用來替換GOPATH的
大家不需要太擔心了,golang 1.11版本僅僅是指對modules的初步支援,之前老的GOPATH還是可以繼續使用的,有人說是在golang 1.12去除,但是我覺得有點早了,畢竟人的慣性不是這麼容易改變的。
如何使用modules
modules是一個新的特性,那麼就需要新的Golang版本進行支援了,可以到官網下載,一定要是go 1.11及以上的版本(寫博文的時候go 1.11剛剛出來)。這麼部署就在這裡說了,相信初學者也是知道怎麼做的。
還有人記得vendor剛剛出來時候golang提供的環境變數GO15VENDOREXPERIMENT
嗎?現在modules出來,按照慣例也提供了一個環境變數GO111MODULE
,這個變數的三個1太有魔性了。
GO111MODULE
GO111MODULE
可以設定為三個字串值之一:off,on或auto(預設值)。
- off,則go命令從不使用新模組支援。它尋找vendor 目錄和GOPATH以尋找依賴關係;也就是繼續使用“GOPATH模式”。
- on,則go命令需要使用模組,go 會忽略 GOPATH 和 vendor 檔案夾,只根據 go.mod下載依賴。
- auto或未設定,則go命令根據目前的目錄啟用或禁用模組支援。僅噹噹前目錄位於GOPATH/src之外並且其本身包含go.mod檔案或位於包含go.mod檔案的目錄下時,才啟用模組支援。
Defining a module
開始的時候誰也不知道怎麼使用?不過go已經給我提供了工具了,可以在控制台輸入:
go help modules
看到一大串的文檔輸出,看著都頭疼了,一會兒我們再簡要說明重點,現在先進行操作。
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ export GO111MODULE=on #開啟modulesqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod init gitlab.luojilab.com/zeroteam/ddkafka # 建立go.modgo: creating new go.mod: module gitlab.luojilab.com/zeroteam/ddkafkaqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ ls # 真的建立了,google大法好呀README.md go.mod models.go mq_interface.go sarama segmentioqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ cat go.mod # 看看裡面什麼東西module gitlab.luojilab.com/zeroteam/ddkafkaqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ cd segmentio/qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ go test # 執行一下看看go: finding github.com/segmentio/kafka-go latestgo: finding github.com/golang/glog latestgo: downloading github.com/golang/glog v0.0.0-20160126235308-23def4e6c14bgo: downloading github.com/segmentio/kafka-go v0.0.0-20180716203113-48c37f796910qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ go list -mgitlab.luojilab.com/zeroteam/ddkafka
細心的同學一定可以發現,執行go mod init [module]
使用go.mod
只有一行資訊module gitlab.luojilab.com/zeroteam/ddkafka
,在執行 go build、 go test、 go list命令時會根據需要的依賴自動產生 require語句。
現在來說說如何定義一個modules,modules是由Go源檔案目錄結構定義的,如果目錄下含有go.mod檔案,該目錄稱為模組根目錄(module root)。模組根目錄及其子目錄所有的Go包都是屬於該modules的,但是如果子目錄包含有了自己的go.mod檔案就隸屬於該modules。舉一個例子:
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ tree.|-- README.md|-- go.mod|-- go.sum|-- models.go|-- mq_interface.go|-- sarama| |-- sarama_consumer.go| |-- sarama_consumer_test.go| |-- sarama_producer.go| `-- sarama_producter_test.go`-- segmentio |-- segmention_Consumer.go |-- segmention_consumer_test.go |-- segmention_producer.go `-- segmention_producter_test.go
gitlab.luojilab.com/zeroteam/ddkafka
目錄下含有了go.mod檔案,所以其子目錄sarama
和segmentio
都屬於gitlab.luojilab.com/zeroteam/ddkafka
模組,但是如果在segmentio
目錄中加入了go.mod,那麼segmentio就不再隸屬於gitlab.luojilab.com/zeroteam/ddkafka
模組。
那麼依賴被下載到哪裡了呢,你可以開啟的目錄$GPATH/pkg/mod
就可以看到了。
主模組和構建列表
The main module and the build list 暫且翻譯為主模組和構建列表。“主模組”是包含運行go命令的目錄的模組。 go命令通過尋找目前的目錄中的go.mod或者目前的目錄的父目錄,或者祖父目錄,依次遞迴尋找。
go.mod檔案可以通過require,replace和exclude語句使用的精確軟體包集。
- require語句指定的依賴項模組
- replace語句可以替換依賴項模組
- exclude語句可以忽略依賴項模組
go list
,可以查看當前的依賴和版本.
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ ls # 這是模組的子目錄segmention_Consumer.go segmention_consumer_test.go segmention_producer.go segmention_producter_test.goqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ go list -m #主模組的列印路徑gitlab.luojilab.com/zeroteam/ddkafkaqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ go list -m -f={{.Dir}} #print主模組的根目錄D:\code\gopath\src\gitlab.luojilab.com\zeroteam\ddkafkaqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/segmentio (module)$ go list -m all # 查看當前的依賴和版本資訊gitlab.luojilab.com/zeroteam/ddkafkagithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14bgithub.com/segmentio/kafka-go v0.0.0-20180716203113-48c37f796910
go mod 命令
go mod
命令之前可以使用過了go mod init
,下面我們把常用的go mod
命令羅列一下:
- go mod init:初始化modules
- go mod download:下載modules到本地cache
- go mod edit:編輯go.mod檔案,選項有-json、-require和-exclude,可以使用協助go help mod edit
- go mod graph:以文字模式列印模組需求圖
- go mod tidy:刪除錯誤或者不使用的modules
- go mod vendor:產生vendor目錄
- go mod verify:驗證依賴是否正確
- go mod why:尋找依賴
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod edit -json{ "Module": { "Path": "gitlab.luojilab.com/zeroteam/ddkafka" }, "Require": [ { "Path": "github.com/golang/glog", "Version": "v0.0.0-20160126235308-23def4e6c14b" }, { "Path": "github.com/segmentio/kafka-go", "Version": "v0.0.0-20180716203113-48c37f796910" } ], "Exclude": null, "Replace": null}qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ cat go.modmodule gitlab.luojilab.com/zeroteam/ddkafkarequire ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/segmentio/kafka-go v0.0.0-20180716203113-48c37f796910)qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod edit -require=github.com/Shopify/sarama@masterqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ cat go.modmodule gitlab.luojilab.com/zeroteam/ddkafkarequire ( github.com/Shopify/sarama master github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/segmentio/kafka-go v0.0.0-20180716203113-48c37f796910)qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod vendor # 啟動verdongo: downloading github.com/Shopify/sarama v1.17.1-0.20180820172058-647feef69a1ago: finding github.com/davecgh/go-spew/spew latestgo: finding github.com/eapache/queue v1.1.0go: finding github.com/eapache/go-xerial-snappy latestgo: finding github.com/eapache/go-resiliency/breaker latestgo: finding github.com/rcrowley/go-metrics latestgo: downloading github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165go: finding github.com/bsm/sarama-cluster v2.1.15+incompatiblego: downloading github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21go: downloading github.com/bsm/sarama-cluster v2.1.15+incompatiblego: downloading github.com/eapache/queue v1.1.0go: finding github.com/eapache/go-resiliency v1.1.0go: downloading github.com/eapache/go-resiliency v1.1.0go: finding github.com/davecgh/go-spew v1.1.1go: downloading github.com/davecgh/go-spew v1.1.1go: finding github.com/pierrec/lz4 v2.0.3+incompatiblego: downloading github.com/pierrec/lz4 v2.0.3+incompatiblego: finding github.com/golang/snappy latestgo: downloading github.com/golang/snappy v0.0.0-20180518054509-2e65f85255dbqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ lsREADME.md go.mod go.sum models.go mq_interface.go sarama segmentio vendorqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod verifyall modules verifiedqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka (module)$ go mod whygo: finding github.com/onsi/ginkgo/extensions/table latestgo: finding github.com/onsi/ginkgo v1.6.0go: finding github.com/Shopify/toxiproxy/client latestgo: finding github.com/onsi/gomega v1.4.1go: downloading github.com/onsi/gomega v1.4.1go: downloading github.com/onsi/ginkgo v1.6.0go: finding github.com/onsi/ginkgo/extensions latestgo: finding github.com/Shopify/toxiproxy v2.1.3+incompatiblego: downloading github.com/Shopify/toxiproxy v2.1.3+incompatiblego: finding github.com/hpcloud/tail v1.0.0go: finding github.com/golang/protobuf/proto latestgo: finding gopkg.in/yaml.v2 v2.2.1go: downloading github.com/hpcloud/tail v1.0.0go: downloading gopkg.in/yaml.v2 v2.2.1go: finding github.com/golang/protobuf v1.2.0go: downloading github.com/golang/protobuf v1.2.0go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405go: downloading gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405go: finding gopkg.in/tomb.v1 latestgo: finding gopkg.in/fsnotify.v1 v1.4.7go: downloading gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7go: downloading gopkg.in/fsnotify.v1 v1.4.7go: finding github.com/fsnotify/fsnotify v1.4.7go: downloading github.com/fsnotify/fsnotify v1.4.7# gitlab.luojilab.com/zeroteam/ddkafkagitlab.luojilab.com/zeroteam/ddkafka
go的 mod與get
go get這個命令大家應該不會陌生,這是下載go依賴包的根據,下載Go 1.11出來了,go get命令也與時俱進,支援了modules。go get 來更新 module:
- 運行 go get -u 將會升級到最新的次要版本或者修訂版本
- 運行 go get -u=patch 將會升級到最新的修訂版本(比如說,將會升級到 1.0.1 版本,但不會升級到 1.1.0 版本)
- 運行 go get package@version將會升級到指定的版本號碼
運行go get如果有版本的更改,那麼go.mod檔案也會更改。
如何處理被牆
我原本以為go 1.11版本modules不支援代理功能,但是經過驗證,是支援的。使用的命令就是之前提到過的replace
命令。文檔中有一段話:
The -replace=old[@v]=new[@v] and -dropreplace=old[@v] flagsadd and drop a replacement of the given module path and version pair.If the @v in old@v is omitted, the replacement applies to all versionswith the old module path. If the @v in new@v is omitted, the new pathshould be a local module root directory, not a module path.Note that -replace overrides any existing replacements for old[@v].
大概翻譯一下:
-replace=old[@v]=new[@v]和-dropreplace=old[@v]標誌添加和刪除給定模組路徑和版本對的替換。如果省略了old@v中的@v,則替換適用於所有使用舊模組路徑的版本。如果new@v中的@v被省略,新路徑應該是本地模組根目錄,而不是模組路徑。注意-replace會覆蓋任何現有的舊替換[@v]。
下面進行示範:建立一個main檔案代碼如下:
package mainimport ("errors""fmt""golang.org/x/net/netutil""net""time")var errFake = errors.New("fake error from errorListener")type errorListener struct {net.Listener}func main() {donec := make(chan bool, 1)go func() {const n = 2ll := netutil.LimitListener(errorListener{}, n)for i := 0; i < n+1; i++ {_, err := ll.Accept()if err != errFake {fmt.Errorf("Accept error = %v; want errFake", err)}}donec <- true}()select {case <-donec:case <-time.After(5 * time.Second):fmt.Errorf("timeout. deadlock?")}}
可以看到我們引入了golang.org/x/net/netuti
,很不幸,這是不能訪問到的類庫。
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go build # 試試是否可以下載build gitlab.luojilab.com/zeroteam/ddkafka/cmd: cannot find module for path golang.org/x/net/netutilqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go mod edit -require=golang.org/x/net@v1.2.3 # modules中加上依賴,版本是亂給的qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go build # 還是不行go: golang.org/x/net@v1.2.3: unrecognized import path "golang.org/x/net" (https fetch: Get https://golang.org/x/net?go-get=1: dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.)go: error loading module requirements
以失敗而告終!不過我們還有replace
方法,首先去下載
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/github.com/golang$ git clone https://github.com/golang/net.gitCloning into 'net'...remote: Counting objects: 7876, done.remote: Compressing objects: 100% (55/55), done.remote: Total 7876 (delta 37), reused 42 (delta 19), pack-reused 7802RReceiving objects: 100% (7876/7876), 6.35 MiB | 812.00 KiB/s, done.Resolving deltas: 100% (5443/5443), done.qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/github.com/golang/net (master)$ go mod init github.com/golang/net #建立modulesgo: creating new go.mod: module github.com/golang/netqiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/github.com/golang/net (master)$ cat go.modmodule github.com/golang/net
回到原來的項目:
qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go mod edit -require=golang.org/x/net@v1.2.3 # qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go mod edit -replace=golang.org/x/net@v1.2.3=/d/code/gopath/src/github.com/golang/net # 使用replace替換到指定的目錄了qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ go build # 這一次成功了qiang@DESKTOP-2A835P9 MINGW64 /d/code/gopath/src/gitlab.luojilab.com/zeroteam/ddkafka/cmd (module)$ lscmd.exe main.go
最後
最後說明一下最新出來的特性不建議立即使用到線上,最好再等等,等迭代一兩個版本之後,得帶最佳實務出來之後,畢竟現在支援modules模式的類庫還真不多。
附錄
- https://tip.golang.org/cmd/go/#hdr-Modules__module_versions__and_more
- https://roberto.selbach.ca/intro-to-go-modules