Go 包管理歷史以及 Go mod 使用

rickiyang發表於2020-10-25

之前也寫過 Go 管理依賴工具 godep 的使用,當時看 godep 使用起來還是挺方便,其原因主要在於有總比沒有強。關於依賴管理工具其實還是想從頭聊聊這個需求以及大家做這個功能的各種出發點。

GOPATH 和 GOROOT

GOROOT 這個變數的作用就是為了告訴當前執行的 Go 程式當前 Go 安裝在哪裡,當你想要執行的時候去哪裡找 Go SDK相關的類。

GOPATH 這個設定其實從語言層面上來說就有點反設計模式。主要原因在於 Go 剛出生的時候沒有自帶包管理功能,預設所有的專案和引用的第三方包都下載到 src 目錄下,第三方包對於 Go 而言也是一個可用的Go專案,這一點跟 Java 區別還是挺大。

那 GOPATH 是否可以設定多個呢?當讓可以,並且人家就是這麼讓你用的。

比如你有專案A,設定的 GOPATH 是:/src/projA/,那麼A專案所有的依賴都會下載在這個目錄。

比如你有專案B,設定的 GOPATH 是:/src/projB/,那麼B專案所有的依賴都會下載在這個目錄。

而如果你使用同一個 GOPATH,那麼你所有的專案 down 下來的依賴都在 src 目錄下,這時候就有版本的問題,如果 A 專案想用 依賴 C 的 1.0.0 版本,B 專案想用依賴 C 的 1.0.1 版本,這時候該怎麼指定呢?

正確的 GOPATH 就是一個專案一個。

包管理歷史上的“恩怨情仇”

所以問題來了,一個專案一個 GOPATH,如果我有三個專案是個超級無敵大系統,可能會 down 下來整個 github 上的開源庫,那這儲存都是問題啊。

針對 Go 官方對包管理的亂象無動於衷,社群的同學們實在是忍無可忍,相繼開源了各種包管理工具。

13 年的時候 Godep 誕生,原理很簡單,跟 maven 一樣,指定一個統一的包管理目錄,將這個目錄作為唯一的 GOPATH。也是在這一年 Docker 開始火起來了, Go 作為微服務開發“非官方指定語言”被廣泛使用,同時矮子裡面拔高子, dep 也綻放異彩。

15年 Go 官方被社群倒逼,1.5 版本同步帶上了一個實驗性質的功能 vendor 機制。由於預設情況下是關閉態,所以 normal 使用者無感知,super 使用者才會去體驗。1.6 版本的時候該功能才開啟為正常使用狀態。

那麼什麼是 vendor 機制呢?簡單說就是在你的專案中包含一個 vendor 的資料夾,它代替了通用的 GOPATH 目錄,你專案中所有的依賴都會在這裡存在。有了 vendor 好處顯而易見,你再也不用手工修改 GOPATH 了,每個人執行環境的包也都統一了起來。

但是問題也隨之而來,如果你依賴的一個專案也使用 vendor 管理依賴,那不就形成了巢狀依賴了嗎?這種情況怎麼處理。

當然還有別的各種問題,總之這也不是一個能拿得出手的成熟方案。

後面又出了 glide 等等社群的方案,但是到這裡為止官方還是沒有出一個能一統天下的方案。

當時 Google 官方的 Go 負責人 Russ 其實是有和 dep 的開發者 Sam 溝通讓他修改一些設計以便將 dep 繼承到 go 命令中去,但是由於一些觀念上的原因 Sam 並沒有接受,所以也因此錯過一段良緣。

後面官方由於社群的壓力在Go 1.11 版本的時候整合了新特性 Go modules。這是首次以官方名義開發的包管理工具。Go 命令直接支援 modules 相關用法。

Go modules

Go modules 是官方推出的依賴管理工具,Go modules 提供了3個重要的功能:

  1. go.mod 檔案,它與 package.jsonPipfile 檔案的功能類似。
  2. 機器生成的傳遞依賴項描述檔案 : go.sum
  3. 不再有 GOPATH 限制。模組可以位於任何路徑中。

要想使用 Go modules 請升級到 1.11 及其以上版本,1.13 版本已經預設開啟 Go modules,如果想體驗這個功能建議將 Go 版本升級到 1.13。

另外,這個功能預設並不是開啟的,需要手動設定環境變數開開啟:

go env -w GO111MODULE=on

go env -w 是Go 1.13 新增的命令,用於寫入環境變數。寫入的地方是os.UserCOnfigDir所在的目錄。

GO111MUDULE 變數是 Go modules 的開關。 有以下幾個引數:

  • auto:如果專案包含了 go.mod 檔案,則啟用 Go modules 功能。在Go 1.13 中是預設值。
  • on:始終開啟 Go modules。
  • off:禁用 Go modules。
使用

首先我們在 GOPATH外新建一個目錄,比如 usr/local/mod-demo,進入目錄下,生成 go.mod檔案:

Go mod init mod-demo

開啟我們剛生成的 go.mod 檔案可以看到:

module mod-demo
go 1.13

go.mod 檔案是開啟 modules 的必備配置檔案。它記錄了當前專案引用的包資料資訊。go.mod 檔案中定義了以下關鍵詞:

  • module:用於定義當前專案的模組路徑
  • go:用於設定Go 版本資訊
  • require:用於設定一個特定的模組版本
  • exclude:用於從使用中排除一個特定的模組版本
  • replace:用於將一個模組版本替換為另一個模組版本

接下來新增專案外的依賴:

package main

import (
	"github.com/gin-gonic/gin"

)

func main() {
	d := gin.Default()
	d.GET("/index", func(c *gin.Context) {
		c.JSON(200, gin.H{"message":"hello world","data":""})
	})
	d.Run("127.0.0.1:8080")
}

上面我們新增了 gin 的依賴包,然後看 go.mod 的內容:

module mod-demo

go 1.15

require github.com/gin-gonic/gin v1.6.3

可以看到已經新增了依賴。

以下版本格式都是合法的:

gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest

Go module 安裝 package 的原則是先拉最新的 release tag,若無tag則拉最新的commit,詳見 Modules官方介紹。 Go 會自動生成一個 go.sum 檔案來記錄 dependency tree:

github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
......
......
......

這裡會記錄當前以來的依賴包所依賴的依賴。

另外,之前我們在 golang.org 上下載不下來的包,直接使用 github 上的映象,使用 module 之後可以使用 replace 關鍵字來個替換:

比如你之前引用是這樣的:

require (
	golang.org/x/text v0.3.2
)

那麼使用 replace 之後就是這樣的:

require (
	golang.org/x/text v0.3.2
)
replace golang.org/x/text v0.3.2 => github.com/golang.org/text v0.3.2

執行命令go list -m all 也可以檢視當前所有依賴項。

Go modules 有如下常用命令:

download    download modules to local cache (下載依賴的module到本地cache))
edit        edit go.mod from tools or scripts (編輯go.mod檔案)
graph       print module requirement graph (列印模組依賴圖))
init        initialize new module in current directory (再當前資料夾下初始化一個新的module, 建立go.mod檔案))
tidy        add missing and remove unused modules (增加丟失的module,去掉未用的module)
vendor      make vendored copy of dependencies (將依賴複製到vendor下)
verify      verify dependencies have expected content (校驗依賴)
why         explain why packages or modules are needed (解釋為什麼需要依賴)

GOPROXY加速

Go 很多包都會被 GFW 牆掉,現在有了Go modules 就可以解決了。

Go modules 支援配置映象源,從而更快更穩定的完成 go get 操作,大家只需要 export GOPROXY 配置一下即可,推薦的源地址:https://goproxy.io/。

相關文章