03-01-05-Go包管理詳解


Go包管理

一 包管理歷史

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

二 modules 的使用

2.1 准備

Golang 版本:1.12.3。在 1.12 版本之前,使用 Go modules 之前需要環境變量 GO111MODULE:

  • GO111MODULE=off: 不使用 modules 功能,查找vendor和GOPATH目錄
  • GO111MODULE=on: 使用 modules 功能,不會去 GOPATH 下面查找依賴包。
  • GO111MODULE=auto: Golang 自己檢測是不是使用 modules 功能,如果當前目錄不在$GOPATH 並且 當前目錄(或者父目錄)下有go.mod文件,則使用 GO111MODULE, 否則仍舊使用 GOPATH mode
set GO111MODULE=on    //windows
export GO111MODULE=on //linux

在 GOPATH 之外創建一個項目 mod-demo,包含一個 main.go 文件,內容如下:

package main

import "fmt"
import "github.com/astaxie/beego"
func main() {
	fmt.Println("hello")
	beego.Run()
}

2.2 初始化go modules

在項目根目錄執行命令 go mod init test-demo ,然后會生成一個 go.mod

image-20191120210941001

這里比較關鍵的就是這個 go.mod 文件,這個文件中標識了我們的項目的依賴的 package 的版本。執行 init 暫時還沒有將所有的依賴管理起來。我們需要將程序 run 起來(比如執行 go run/test),或者 build(執行命令 go build)的時候,才會觸發依賴的解析。

比如使用 go run 即可觸發 modules 工作。

go run test.go

這個時候我們再查看 go.mod 文件:

module test-demo

go 1.13

require (
	github.com/astaxie/beego v1.12.0 // indirect
	github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

注意:有indirect注釋的代表間接依賴,沒有的代表直接依賴,這里是版本號+時間戳+hash

注意:在goland中可以用alt+enter/option+enter來下載依賴包

同時我們發現項目目錄下多了一個 go.sum 用來記錄每個 package 的版本和哈希值。go.mod 文件正常情況會包含 module 和 require 模塊,除此之外還可以包含 replace 和 exclude 模塊。

這些 package 並不是直接存儲到 GOPATH/src,而是存儲到 GOPATH/pkg/mod 下面,不同版本並存的方式。

image-20191120211440359

2.3 依賴的升級和降級

可以使用如下命令來查看當前項目依賴的所有的包

go list -m -u all
//如果我想要升級(降級)某個 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 中的項目的版本也相應改變了。

go get github.com/astaxie/beego@v1.11.1

在 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 版本

2.4 vender

我們知道 Go 1.5 推出了 vendor 機制,go mod 也可以支持 vendor 機制,將依賴包拷貝到 vendor 目錄。但是像一些 test case 里面的依賴包並不會拷貝的 vendor 目錄中。

go mod vendor

三 modules 高級特性

3.1 代理服務器GoProxy

proxy 顧名思義,代理服務器。眾所周知,有些 Golang 的 package 在國內是無法直接 go get 的。在之前,我們解決這個問題,一般都是通過設置 http_proxy/https_proxy 來解決。GoProxy 相當於官方提供了一種 proxy 的方式讓用戶來進行包下載。要使用 GoProxy 只需要設置環境變量 GOPROXY 即可。目前公開的 GOPROXY 有:

  • goproxy.io 在goland-->Go-->Go Modules(ego)中配置這個地址
  • goproxy.cn: 由七牛雲提供,參考 github repo

image-20191120231215167

當然你也可以實現自己的 GoProxy 服務,比如項目中的依賴包含外部依賴和內部依賴的時候,那么只需要實現 module proxy protocal 協議即可。

值得注意的是,在最新 release 的 Go 1.13 版本中默認將 GOPROXY 設置為 https://proxy.golang.org,這個對於國內的開發者是無法直接使用的。所以如果升級了 Go 1.13 版本一定要把 GOPROXY 手動改掉。

3.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

3.3 SubCommand

modules 支持的 subcommand 如下

即輸入go mod 后面可以跟以下命令

  • download: 下載 modules 到本地緩存
  • edit: 提供一種命令行交互修改 go.mod 的方式
  • graph: 將 module 的依賴圖在命令行打印出來,其實並不是很直觀
  • init: 初始化 modules,會生成一個 go.mod 文件
  • tidy: 清理 go.mod 中的依賴,會添加缺失的依賴,同時移除沒有用到的依賴
  • vendor: 將依賴包打包拷貝到項目的 vendor 目錄下,值得注意的是並不會將 test code 中的依賴包打包到 vendor 中。這種設計在社區也引起過幾次爭論,但是並沒有達成一致。
  • verify: verify 用來檢測依賴包自下載之后是否被改動過。
  • why: 解釋為什么 package 或者 module 是需要,但是看上去解釋的理由並不是非常的直觀。

四 Go 1.13 對 modules 的改動

上面在討論 GoProxy 的時候提到了 Go 1.13 默認設置環境變量 GOPROXY 的值,除此之外 Go 1.13 對 modules 還有哪些值得注意的改動呢?

4.1 默認開啟

modules 在 Go 1.13 的版本下是默認開啟的。

4.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 都是私有的。

4.3 GOSUMDB

GOSUMDB 的全稱為 Go CheckSum Database,用來下載的包的安全性校驗問題。包的安全性在使用 GoProxy 之后更容易出現,比如我們引用了一個不安全的 GoProxy 之后然后下載了一個不安全的包,這個時候就出現了安全性問題。對於這種情況,可以通過 GOSUMDB 來對包的哈希值進行校驗。當然如果想要關閉哈希校驗,可以將 GOSUMDB 設置為 off;如果要對部分包關閉哈希校驗,則可以將包的前綴設置到環境變量中 GONOSUMDB 中,設置規則類似 GOPRIVATE。

關於 GOSUMDB 的配置格式為:<db_name>+ +

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

五 自己寫的包如何導入使用

直接在go.mod中寫包名即可

module test-demo

go 1.13

require (
	github.com/astaxie/beego v1.12.0
	github.com/gin-gonic/gin v1.4.0 // indirect
	github.com/go-redis/redis v6.15.6+incompatible
	github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
    redisPackage v0.0.1  //自己加的v0.1.1 必須為三個數(也可以 不加)
)

五 總結

Golang 包管理歷經多個版本,目前來看並沒有一個完全讓開發者滿意的方案,比如類似 Java 的 maven 的包管理方式。很多開發者也表示 modules 的管理方式也不是很直觀。其實 Golang 的 package 使用 git 的管理方式來管理其實是一個很好的管理方式,我們最需要的其實如何能讓普通開發者獲取到任何 package 的心智負擔降到最低。值得欣慰的是我們確實有看到大家在這個方向上的努力,比如 modules 的 GoProxy。

放眼未來,希望會有一種令大部分普通開發者滿意的包管理方式吧。

最后常用命令

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,下載沒download的package
go mod vendor:生成vendor目錄
go mod verify:驗證依賴是否正確
go mod why:查找依賴

go test    執行一下,自動導包

go list -m  主模塊的打印路徑
go list -m -f={{.Dir}}  print主模塊的根目錄
go list -m all  查看當前的依賴和版本信息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM