Go Modules 不完全教程 文章轉載自公眾號 Golang 成神之路 , 作者 L Go Modules 是 Golang 官方最近幾個版本推出的原生的包管理方式,在此之前,社區也不乏多種包管理方案。在討論 Go Modules 之前,我們先回顧一下 Golang 的包管理歷史的發展。然後討論 ...
Go Modules 不完全教程
文章轉載自公眾號Go Modules 是 Golang 官方最近幾個版本推出的原生的包管理方式,在此之前,社區也不乏多種包管理方案。在討論 Go Modules 之前,我們先回顧一下 Golang 的包管理歷史的發展。然後討論一下 Go Modules 的使用以及一些特性,篇幅有限,有些地方不方便展開,後面有時間再深入。行文倉促,不當之處,多多指教。
0. 包管理的歷史
Golang 的包管理一直被大眾所詬病的一個點,但是我們可以看到現在確實是在往好的方向進行發展。下麵是官方的包管理工具的發展歷史:
-
在 1.5 版本之前,所有的依賴包都是存放在 GOPATH 下,沒有版本控制。這個類似 Google 使用單一倉庫來管理代碼的方式。這種方式的最大的弊端就是無法實現包的多版本控制,比如項目 A 和項目 B 依賴於不同版本的 package,如果 package 沒有做到完全的向前相容,往往會導致一些問題。
-
1.5 版本推出了 vendor 機制。所謂 vendor 機制,就是每個項目的根目錄下可以有一個 vendor 目錄,裡面存放了該項目的依賴的 package。
go build
的時候會先去 vendor 目錄查找依賴,如果沒有找到會再去 GOPATH 目錄下查找。 -
1.9 版本推出了實驗性質的包管理工具 dep,這裡把 dep 歸結為 Golang 官方的包管理方式可能有一些不太準確。關於 dep 的爭議頗多,比如為什麼官方後來沒有直接使用 dep 而是弄了一個新的 modules,具體細節這裡不太方便展開。
-
1.11 版本推出 modules 機制,簡稱 mod,也就是本文要討論的重點。modules 的原型其實是 vgo,關於 vgo,可以參考文章末尾的參考鏈接。
除此之外,社區也一直在有幾個活躍的包管理工具,使用廣泛且具有代表性的主要有下麵幾個:
-
godep
-
glide
-
govendor
關於這幾種包管理工具的使用這裡就不再詳述了。
1. modules 簡單使用方式
下麵看一下 modules 的簡單使用方式。
1.1 準備工作
Golang 版本:1.12.3。在 1.12 版本之前,使用 Go modules 之前需要環境變數 GO111MODULE:
-
GO111MODULE=off: 不使用 modules 功能。
-
GO111MODULE=on: 使用 modules 功能,不會去 GOPATH 下麵查找依賴包。
-
GO111MODULE=auto: Golang 自己檢測是不是使用 modules 功能。
在 GOPATH 之外創建一個項目 mod-demo,包含一個 main.go 文件,內容如下:
packagemain
import(
"github.com/astaxie/beego"
)
funcmain() {
beego.Run()
}
1.2 初始化
初始化很簡單,在項目根目錄執行命令 go mod init mod-demo
,然後會生成一個 go.mod 文件如下。
➜ mod-demo $ gomod init mod-demo
go: creating new go.mod: module .
➜ mod-demo $ ls
go.mod main.go
➜ mod-demo $ catgo.mod
module .
go 1.12
這裡比較關鍵的就是這個 go.mod 文件,這個文件中標識了我們的項目的依賴的 package 的版本。執行 init 暫時還沒有將所有的依賴管理起來。我們需要將程式 run 起來(比如執行 go run/test),或者 build(執行命令 go build)的時候,才會觸發依賴的解析。
比如使用 go run 即可觸發 modules 工作。
➜ mod-demo $ gorun main.go
go: extracting github.com/astaxie/beego v1.12.0
2019/09/08 23:23:03.507 [I] http server Running on http://:8080
這個時候我們再查看 go.mod 文件:
➜ mod-demo $ catgo.mod
module mod-demo
go 1.12
require (
github.com/astaxie/beego v1.12.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)
同時我們發現項目目錄下多了一個 go.sum 用來記錄每個 package 的版本和哈希值。go.mod 文件正常情況會包含 module 和 require 模塊,除此之外還可以包含 replace 和 exclude 模塊。
這些 package 並不是直接存儲到 $GOPATH/src,而是存儲到 $GOPATH/pkg/mod 下麵,不同版本並存的方式。
➜ mod-demo $ ls$GOPATH/pkg/mod/github.com/astaxie
beego@v1.11.0 beego@v1.11.1 beego@v1.12.0
1.3 依賴升級(降級)
可以使用如下命令來查看當前項目依賴的所有的包。
➜ mod-demo $ golist -m-uall
go: finding github.com/beego/x2j latest
go: finding github.com/cloudflare/golz4 latest
go: finding github.com/siddontang/go latest
go: finding github.com/shiena/ansicolor latest
go: finding github.com/couchbase/go-couchbase latest
go: finding github.com/siddontang/rdb latest
go: finding gopkg.in/check.v1 latest
go: finding github.com/siddontang/ledisdb latest
go: finding github.com/ssdb/gossdb latest
go: finding github.com/couchbase/gomemcached latest
go: finding github.com/wendal/errors latest
go: finding github.com/couchbase/goutils latest
go: finding golang.org/x/net latest
go: finding github.com/cupcake/rdb latest
go: finding github.com/beego/goyaml2 latest
go: finding golang.org/x/crypto latest
go: finding github.com/bradfitz/gomemcache latest
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/OwnLocal/goes v1.0.0
github.com/astaxie/beego v1.12.0
...
如果我想要升級(降級)某個 package 則只需要 go get 即可,比如:
go get package@version
需要註意的是,在 modules 模式開啟和關閉的情況下,go get
的使用方式不是完全相同的。在 modules 模式開啟的情況下,可以通過在 package 後面添加 @version 來表明要升級(降級)到某個版本。如果沒有指明 version 的情況下,則預設先下載打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果沒有 release 版本,則下載最新的 pre release 版本,比如 v0.0.1-pre1。如果還沒有則下載最新的 commit。這個地方給我們的一個啟示是如果我們不按規範的方式來命名我們的 package 的 tag,則 modules 是無法管理的。version 的格式為 v(major).(minor).(patch)
,更多信息可以參考:https://semver.org/ 。
比如我們現在想將我們依賴中的 beego 項目的版本改為 v1.11.1,則可以像如下操作。我們發現執行完 go get
之後, go.mod 中的項目的版本也相應改變了。
➜ mod-demo $ gogetgithub.com/astaxie/beego@v1.11.1
go: finding github.com/astaxie/beego v1.11.1
go: downloading github.com/astaxie/beego v1.11.1
go: extracting github.com/astaxie/beego v1.11.1
➜ mod-demo $ catgo.mod
module .
go 1.12
require (
github.com/OwnLocal/goes v1.0.0 // indirect
github.com/astaxie/beego v1.11.1 // indirect
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)
在 modules 開啟的模式下,go get 還支持 version 模糊查詢,比如 > v1.0.0 表示大於 v1.0.0 的可使用版本;< v1.12.0 表示小於 v1.12.0 版本下最近可用的版本。version 的比較規則按照 version 的各個欄位來展開。
除了指定版本,我們還可以使用如下命名使用最近的可行的版本:
-
go get -u 使用最新的 minor 或者 patch 版本
-
go get -u=patch 使用最新的 patch 版本
1.4 vendor
我們知道 Go 1.5 推出了 vendor 機制,go mod 也可以支持 vendor 機制,將依賴包拷貝到 vendor 目錄。但是像一些 test case 裡面的依賴包並不會拷貝的 vendor 目錄中。
➜ mod-demo $ gohelp mod vendor
usage: go mod vendor [-v]
Vendor resets the main module's vendor directory to include all packages
needed to build and test all the main module's packages.
It does not include test code forvendored packages.
2. modules 特性
上面介紹了 go modules 的簡單使用方法,但是 modules 的一些更高級的特性沒有介紹,將在下麵進行展開。
2.1 GoProxy
proxy 顧名思義,代理伺服器。眾所周知,有些 Golang 的 package 在國內是無法直接 go get 的。在之前,我們解決這個問題,一般都是通過設置 http_proxy/https_proxy 來解決。GoProxy 相當於官方提供了一種 proxy 的方式讓用戶來進行包下載。要使用 GoProxy 只需要設置環境變數 GOPROXY
即可。目前公開的 GOPROXY 有:
-
goproxy.io
-
goproxy.cn: 由七牛雲提供,參考 github repo
當然你也可以實現自己的 GoProxy 服務,比如項目中的依賴包含外部依賴和內部依賴的時候,那麼只需要實現 module proxy protocal 協議即可。
值得註意的是,在最新 release 的 Go 1.13 版本中預設將 GOPROXY 設置為 https://proxy.golang.org,這個對於國內的開發者是無法直接使用的。所以如果升級了 Go 1.13 版本一定要把 GOPROXY 手動改掉。
2.2 Replace
replace 主要為瞭解決某些包發生改名的問題。
對於另外一種場景有的時候也是有用的,比如對於有些 golang.org/x/ 下麵的包由於某些原因在國內是下載不了的,但是對應的包在 github 上面是有一份拷貝的,這個時候我們就可以將 go.mod 中的包進行 replace 操作。
下麵是一個 Beego 項目的 go.mod 的 replace 的示例。
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85
replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
2.3 SubCommand
modules 支持的 subcommand 如下。
Go mod provides access to operations on modules.
Note that support formodules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be doneusing 'go get'.
See 'go help modules'foran overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module incurrent directory
tidy add missing and remove unused modules
vendor makevendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>"formore information about a command.
每個 subcommand 的含義如下:
-
download: 下載 modules 到本地緩存
-
edit: 提供一種命令行交互修改 go.mod 的方式
-
graph: 將 module 的依賴圖在命令行列印出來,其實並不是很直觀
-
init: 初始化 modules,會生成一個 go.mod 文件
-
tidy: 清理 go.mod 中的依賴,會添加缺失的依賴,同時移除沒有用到的依賴
-
vendor: 將依賴包打包拷貝到項目的 vendor 目錄下,值得註意的是並不會將 test code 中的依賴包打包到 vendor 中。這種設計在社區也引起過幾次爭論,但是並沒有達成一致。
-
verify: verify 用來檢測依賴包自下載之後是否被改動過。
-
why: 解釋為什麼 package 或者 module 是需要,但是看上去解釋的理由並不是非常的直觀。
➜ mod-demo $ gomod why github.com/astaxie/beego
# github.com/astaxie/beego
mod-demo
github.com/astaxie/beego
3. Go 1.13 對 modules 的改動
上面在討論 GoProxy 的時候提到了 Go 1.13 預設設置環境變數 GOPROXY 的值,除此之外 Go 1.13 對 modules 還有哪些值得註意的改動呢?
3.1 預設開啟
modules 在 Go 1.13 的版本下是預設開啟的。
3.2 GOPRIVATE
前面也說到對於一些內部的 package,GoProxy 並不能很好的處理,Go 1.13 推出了 GOPRIVATE 機制。只需要設置這個環境變數,然後標識出哪些 package 是 private 的,那麼對於這個 package 的處理將不會從 proxy 下載。GOPRIVATE 的值是一個以逗號分隔的列表,支持正則(正則語法遵守 Golang 的 包 path.Match)。下麵是一個 GOPRIVATE 的示例:
GOPRIVATE=*.corp.example.com,rsc.io/private
上面的 GOPRIVATE 表示以 *.corp.example.com 或者 rsc.io/private 開頭的 package 都是私有的。
3.3 GOSUMDB
GOSUMDB 的全稱為 Go CheckSum Database,用來下載的包的安全性校驗問題。包的安全性在使用 GoProxy 之後更容易出現,比如我們引用了一個不安全的 GoProxy 之後然後下載了一個不安全的包,這個時候就出現了安全性問題。對於這種情況,可以通過 GOSUMDB 來對包的哈希值進行校驗。當然如果想要關閉哈希校驗,可以將 GOSUMDB 設置為 off;如果要對部分包關閉哈希校驗,則可以將包的首碼設置到環境變數中 GONOSUMDB 中,設置規則類似 GOPRIVATE。
關於 GOSUMDB 的配置格式為:<db_name>+<publickey>+<url>。
GOSUMDB="sum.golang.org"
GOSUMDB="sum.golang.org+<publickey>"
GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"
上面三種配置都是合理的,因為對於 sum.golang.org,Go 自己知道其對應的 publickey 和 url,所以我們只要配置一個名字即可,對於另外一個 sum.golang.google.cn 也是一樣。除此之外的,都需要指明 publickey,url 預設是 https://<db_name>。
關於 GOSUMDB 更多的詳細信息可以參考:https://golang.org/cmd/go/#hdr-Module_authentication_failures
4. 總結
Golang 包管理歷經多個版本,目前來看並沒有一個完全讓開發者滿意的方案,比如類似 Java 的 maven 的包管理方式。很多開發者也表示 modules 的管理方式也不是很直觀。其實 Golang 的 package 使用 git 的管理方式來管理其實是一個很好的管理方式,我們最需要的其實如何能讓普通開發者獲取到任何 package 的心智負擔降到最低。值得欣慰的是我們確實有看到大家在這個方向上的努力,比如 modules 的 GoProxy。
放眼未來,希望會有一種令大部分普通開發者滿意的包管理方式吧。
5. Reference
-
https://research.swtch.com/vgo
-
https://github.com/golang/go/wiki/Modules
-
https://roberto.selbach.ca/intro-to-go-modules/
-
https://roberto.selbach.ca/playing-with-go-modules/
-
https://semver.org/
-
https://golang.org/cmd/go/#hdr-Module_authentication_failures
-
https://golang.org/doc/go1.13#modules
-
https://goproxy.io
-
https://github.com/goproxy/goproxy.cn
-
https://codeengineered.com/blog/2018/golang-godep-to-vgo/