如果您還在使用vendor機制管理依賴包,那麼說明您肯定是處於下面兩種情況之一!
-
還工作在傳統的GOPATH模式下(使用Go 1.10及之前版本;或Go 1.11及之後版本,但GO111MODULE=off),利用vendor管理目標包的特定依賴;
-
工作在go module模式下,但仍然利用vendor管理目標module的特定依賴並使用go build -mod=vendor來構建。
那麼我們是否應該將專案中儲存依賴包的vendor目錄提交到原始碼倉庫進行管理呢?如果讓筆者給出答案,那就是:應該。
https://www.cnblogs.com/zhangmingcheng/p/15396817.html
(轉)Go專案的vendor目錄是否需要提交?看這一篇就知道了
使用 go mod vendor 將mod 中已經下載的包,生成vendor目錄,然後在使用 go build -mod=vendor 就可以脫離 部分 mod中的包,使用本地的vendor來編譯。上傳到倉庫中去。
==================================================================================================
Go Modules基本使用
在初步瞭解了 Go modules 的前世今生後,我們正式進入到 Go modules 的使用,首先我們將從頭開始建立一個 Go modules 的專案(原則上所建立的目錄應該不要放在 GOPATH 之中)。
所提供的命令
在 Go modules 中,我們能夠使用如下命令進行操作:
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 檔案 |
go mod download | 下載 go.mod 檔案中指明的所有依賴 |
go mod tidy | 整理現有的依賴 |
go mod graph | 檢視現有的依賴結構 |
go mod edit | 編輯 go.mod 檔案 |
go mod vendor | 匯出專案所有的依賴到vendor目錄 |
go mod verify | 校驗一個模組是否被篡改過 |
go mod why | 檢視為什麼需要依賴某模組 |
所提供的環境變數
在 Go modules 中有如下常用環境變數,我們可以透過 go env
命令來進行檢視,如下
==================================================================================================
Go modules 是 Go 語言中正式官宣的專案依賴管理工具,Go modules(前身為vgo)於 Go1.11 正式釋出,在 Go1.14 已經準備好,並且可以用在生產上(ready for production)了,鼓勵所有使用者從其他依賴項管理工具遷移到 Go modules。
什麼是Go Modules
Go modules 是 Go 語言的依賴解決方案,釋出於 Go1.11,成長於 Go1.12,豐富於 Go1.13,正式於 Go1.14 推薦在生產上使用。
Go moudles 目前整合在 Go 的工具鏈中,只要安裝了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出現也解決了在 Go1.11 前的幾個常見爭議問題:
- Go 語言長久以來的依賴管理問題。
- “淘汰”現有的 GOPATH 的使用模式。
- 統一社群中的其它的依賴管理工具(提供遷移功能)。
GOPATH的那些點點滴滴
我們有提到 Go modules 的解決的問題之一就是“淘汰”掉 GOPATH,但是 GOPATH 又是什麼呢,為什麼在 Go1.11 前就使用 GOPATH,而 Go1.11 後就開始逐步建議使用 Go modules,不再推薦 GOPATH 的模式了呢?
GOPATH是什麼
我們先看看第一個問題,GOPATH 是什麼,我們可以輸入如下命令檢視:
$ go env
GOPATH="/Users/eddycjy/go"
...
我們輸入go env
命令列後可以檢視到 GOPATH 變數的結果,我們進入到該目錄下進行檢視,如下:
go
├── bin
├── pkg
└── src
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
....
GOPATH目錄下一共包含了三個子目錄,分別是:
- bin:儲存所編譯生成的二進位制檔案。
- pkg:儲存預編譯的目標檔案,以加快程式的後續編譯速度。
- src:儲存所有
.go
檔案或原始碼。在編寫 Go 應用程式,程式包和庫時,一般會以$GOPATH/src/github.com/foo/bar
的路徑進行存放。
因此在使用 GOPATH 模式下,我們需要將應用程式碼存放在固定的$GOPATH/src
目錄下,並且如果執行go get
來拉取外部依賴會自動下載並安裝到$GOPATH
目錄下。
為什麼棄用GOPATH模式
在 GOPATH 的 $GOPATH/src
下進行 .go
檔案或原始碼的儲存,我們可以稱其為 GOPATH 的模式,這個模式,看起來好像沒有什麼問題,那麼為什麼我們要棄用呢,參見如下原因:
- GOPATH 模式下沒有版本控制的概念,具有致命的缺陷,至少會造成以下問題:
- 在執行
go get
的時候,你無法傳達任何的版本資訊的期望,也就是說你也無法知道自己當前更新的是哪一個版本,也無法透過指定來拉取自己所期望的具體版本。 - 在執行Go應用程式的時候,你無法保證其它人與你所期望依賴的第三方庫是相同的版本,也就是說在專案依賴庫的管理上,你無法保證所有人的依賴版本都一致。
- 你沒辦法處理 v1、v2、v3 等等不同版本的引用問題,因為 GOPATH 模式下的匯入路徑都是一樣的,都是
github.com/foo/bar
。
- 在執行
- Go 語言官方從 Go1.11 起開始推進 Go modules(前身vgo),Go1.13 起不再推薦使用 GOPATH 的使用模式,Go modules 也漸趨穩定,因此新專案也沒有必要繼續使用GOPATH模式。
在GOPATH模式下的產物
Go1 在 2012 年 03 月 28 日釋出,而 Go1.11 是在 2018 年 08 月 25 日才正式釋出(資料來源:Github Tag),在這個空檔的時間內,並沒有 Go modules 這一個東西,最早期可能還好說,因為剛釋出,用的人不多,所以沒有明顯暴露,但是後期 Go 語言使用的人越來越多了,那怎麼辦?
這時候社群中逐漸的湧現出了大量的依賴解決方案,百花齊放,讓人難以挑選,其中包括我們所熟知的 vendor 目錄的模式,以及曾經一度被認為是“官宣”的 dep 的這類依賴管理工具。
但為什麼 dep 沒有正在成為官宣呢,其實是因為隨著 Russ Cox 與 Go 團隊中的其他成員不斷深入地討論,發現dep 的一些細節似乎越來越不適合 Go,因此官方採取了另起 proposal 的方式來推進,其方案的結果一開始先是釋出 vgo(Go modules的前身,知道即可,不需要深入瞭解),最終演變為我們現在所見到的 Go modules,也在 Go1.11 正式進入了 Go 的工具鏈。
因此與其說是 “在GOPATH模式下的產物”,不如說是歷史為當前提供了重要的教訓,因此出現了 Go modules。
==================================================================================================
https://eddycjy.com/posts/go/go-moduels/2020-02-28-go-modules/
GOPROXY
GOSUMDB
GOPROXY
GONOPROXY/GONOSUMDB/GOPRIVATE
這個環境變數主要是用於設定 Go 模組代理(Go module proxy),其作用是用於使 Go 在後續拉取模組版本時能夠脫離傳統的 VCS 方式,直接透過映象站點來快速拉取。
GOPROXY 的預設值是:https://proxy.golang.org,direct
,這有一個很嚴重的問題,就是 proxy.golang.org
在國內是無法訪問的,因此這會直接卡住你的第一步,所以你必須在開啟 Go modules 的時,同時設定國內的 Go 模組代理,執行如下命令:
$ go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY的值是一個以英文逗號 “,” 分割的 Go 模組代理列表,允許設定多個模組代理,假設你不想使用,也可以將其設定為 “off” ,這將會禁止 Go 在後續操作中使用任何 Go 模組代理。
direct是什麼
而在剛剛設定的值中,我們可以發現值列表中有 “direct” 標識,它又有什麼作用呢?
實際上 “direct” 是一個特殊指示符,用於指示 Go 回源到模組版本的源地址去抓取(比如 GitHub 等),場景如下:當值列表中上一個 Go 模組代理返回 404 或 410 錯誤時,Go 自動嘗試列表中的下一個,遇見 “direct” 時回源,也就是回到源地址去抓取,而遇見 EOF 時終止並丟擲類似 “invalid version: unknown revision…” 的錯誤。
GOSUMDB
它的值是一個 Go checksum database,用於在拉取模組版本時(無論是從源站拉取還是透過 Go module proxy 拉取)保證拉取到的模組版本資料未經過篡改,若發現不一致,也就是可能存在篡改,將會立即中止。
GOSUMDB的預設值為:sum.golang.org
,在國內也是無法訪問的,但是 GOSUMDB 可以被 Go 模組代理所代理(詳見:Proxying a Checksum Database)。
因此我們可以透過設定 GOPROXY 來解決,而先前我們所設定的模組代理 goproxy.cn
就能支援代理 sum.golang.org
,所以這一個問題在設定 GOPROXY 後,你可以不需要過度關心。
另外若對 GOSUMDB 的值有自定義需求,其支援如下格式:
- 格式 1:
<SUMDB_NAME>+<PUBLIC_KEY>
。 - 格式 2:
<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
。
也可以將其設定為“off”,也就是禁止 Go 在後續操作中校驗模組版本。
GONOPROXY/GONOSUMDB/GOPRIVATE
這三個環境變數都是用在當前專案依賴了私有模組,例如像是你公司的私有 git 倉庫,又或是 github 中的私有庫,都是屬於私有模組,都是要進行設定的,否則會拉取失敗。
更細緻來講,就是依賴了由 GOPROXY 指定的 Go 模組代理或由 GOSUMDB 指定 Go checksum database 都無法訪問到的模組時的場景。
而一般建議直接設定 GOPRIVATE,它的值將作為 GONOPROXY 和 GONOSUMDB 的預設值,所以建議的最佳姿勢是直接使用 GOPRIVATE。
並且它們的值都是一個以英文逗號 “,” 分割的模組路徑字首,也就是可以設定多個,例如:
$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
設定後,字首為 git.xxx.com 和 github.com/eddycjy/mquote 的模組都會被認為是私有模組。
如果不想每次都重新設定,我們也可以利用萬用字元,例如:
$ go env -w GOPRIVATE="*.example.com"
這樣子設定的話,所有模組路徑為 example.com 的子域名(例如:git.example.com)都將不經過 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
開啟Go Modules
目前Go modules並不是預設開啟,因此Go語言提供了GO111MODULE這個環境變數來作為Go modules的開關,其允許設定以下引數:
- auto:只要專案包含了go.mod檔案的話啟用 Go modules,目前在Go1.11至Go1.14中仍然是預設值。
- on:啟用 Go modules,推薦設定,將會是未來版本中的預設值。
- off:禁用 Go modules,不推薦設定。
如果你不確定你當前的值是什麼,可以執行go env
命令,檢視結果:
$ go env
GO111MODULE="off"
...
如果需要對GO111MODULE的值進行變更,推薦透過go env
命令進行設定:
$ go env -w GO111MODULE=on
但是需要注意的是如果對應的系統環境變數有值了(進行過設定),會出現如下警告資訊:warning: go env -w GO111MODULE=... does not override conflicting OS environment variable
。
又或是可以透過直接設定系統環境變數(寫入對應的.bash_profile檔案亦可)來實現這個目的:
$ export GO111MODULE=on
初始化專案
在完成 Go modules 的開啟後,我們需要建立一個示例專案來進行演示,執行如下命令:
$ mkdir -p $HOME/eddycjy/module-repo
$ cd $HOME/eddycjy/module-repo
然後進行Go modules的初始化,如下:
$ go mod init github.com/eddycjy/module-repo
go: creating new go.mod: module github.com/eddycjy/module-repo
在執行 go mod init
命令時,我們指定了模組匯入路徑為 github.com/eddycjy/module-repo
。接下來我們在該專案根目錄下建立 main.go 檔案,如下:
package main
import (
"fmt"
"github.com/eddycjy/mquote"
)
func main() {
fmt.Println(mquote.GetHello())
}
然後在專案根目錄執行 go get github.com/eddycjy/mquote
命令,如下:
$ go get github.com/eddycjy/mquote
go: finding github.com/eddycjy/mquote latest
go: downloading github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
go: extracting github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
檢視go.mod 檔案
在初始化專案時,會生成一個 go.mod 檔案,是啟用了 Go modules 專案所必須的最重要的標識,同時也是GO111MODULE 值為 auto 時的識別標識,它描述了當前專案(也就是當前模組)的元資訊,每一行都以一個動詞開頭。
在我們剛剛進行了初始化和簡單拉取後,我們再次檢視go.mod檔案,基本內容如下:
module github.com/eddycjy/module-repo
go 1.13
require (
github.com/eddycjy/mquote v0.0.0-20200220041913-e066a990ce6f
)
為了更進一步的講解,我們模擬引用如下:
module github.com/eddycjy/module-repo
go 1.13
require (
example.com/apple v0.1.2
example.com/banana v1.2.3
example.com/banana/v2 v2.3.4
example.com/pear // indirect
example.com/strawberry // incompatible
)
exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/fried v0.1.0
replace example.com/banana => example.com/fish
- module:用於定義當前專案的模組路徑。
- go:用於標識當前模組的 Go 語言版本,值為初始化模組時的版本,目前來看還只是個標識作用。
- require:用於設定一個特定的模組版本。
- exclude:用於從使用中排除一個特定的模組版本。
- replace:用於將一個模組版本替換為另外一個模組版本。
另外你會發現 example.com/pear
的後面會有一個 indirect 標識,indirect 標識表示該模組為間接依賴,也就是在當前應用程式中的 import 語句中,並沒有發現這個模組的明確引用,有可能是你先手動 go get
拉取下來的,也有可能是你所依賴的模組所依賴的,情況有好幾種。
檢視go.sum檔案
在第一次拉取模組依賴後,會發現多出了一個 go.sum 檔案,其詳細羅列了當前專案直接或間接依賴的所有模組版本,並寫明瞭那些模組版本的 SHA-256 雜湊值以備 Go 在今後的操作中保證專案所依賴的那些模組版本不會被篡改。
github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=
github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=
...
我們可以看到一個模組路徑可能有如下兩種:
github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
h1 hash 是 Go modules 將目標模組版本的 zip 檔案開包後,針對所有包內檔案依次進行 hash,然後再把它們的 hash 結果按照固定格式和演算法組成總的 hash 值。
而 h1 hash 和 go.mod hash 兩者,要不就是同時存在,要不就是隻存在 go.mod hash。那什麼情況下會不存在 h1 hash 呢,就是當 Go 認為肯定用不到某個模組版本的時候就會省略它的 h1 hash,就會出現不存在 h1 hash,只存在 go.mod hash 的情況。
檢視全域性快取
我們剛剛成功的將 github.com/eddycjy/mquote
模組拉取了下來,其拉取的結果快取在 $GOPATH/pkg/mod
和 $GOPATH/pkg/sumdb
目錄下,而在mod
目錄下會以 github.com/foo/bar
的格式進行存放,如下:
mod
├── cache
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
...
需要注意的是同一個模組版本的資料只快取一份,所有其它模組共享使用。如果你希望清理所有已快取的模組版本資料,可以執行 go clean -modcache
命令。
Go Modules下的go get行為
在拉取專案依賴時,你會發現拉取的過程總共分為了三大步,分別是 finding(發現)、downloading(下載)以及 extracting(提取), 並且在拉取資訊上一共分為了三段內容
==================================================================================================
http://liupzmin.com/2019/09/16/golang/go-modules-md/
兩個模式
對於 modules 這種模式官網有一個稱呼是 Module-aware
,我不知道如何去翻譯這個組合詞,與之相對的,就是在Module-aware mode
之前我們使用的包管理方式稱為GOPATH mode
,他們的區別如下:
-
GOPATH mode: go command從
vendor
和GOPATH
下尋找依賴,依賴會被下載至GOPATH/src
-
Module-aware mode: go command不再考慮
GOPATH
,僅僅使用GOPATH/pkg/mod
儲存下載的依賴,並且是多版本並存
注意:Module-aware開啟和關閉的情況下,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) ,
在 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 版本