Go 部落格
遷移到 Go Modules
引言
本文是本系列文章的第二部分。
- 第 1 部分 — 使用 Go Modules
- 第 2 部分 — 遷移到 Go Modules (本文)
- 第 3 部分 — 釋出 Go Modules
- 第 4 部分 — Go Modules:v2 及更高版本
- 第 5 部分 — 保持模組相容
Go 專案使用多種多樣的依賴項管理策略。Vendoring 工具,例如 dep 和 glide,很受歡迎,但它們的行為差異很大,並非總能很好地協同工作。有些專案將其整個 GOPATH 目錄儲存在單個 Git 倉庫中。其他專案則完全依賴 go get
,並期望將相對較新版本的依賴項安裝在 GOPATH 中。
Go 1.11 中引入的 Go 模組系統提供了一種內置於 go
命令的官方依賴項管理解決方案。本文介紹了將專案轉換為模組的工具和技術。
請注意:如果您的專案已經標記為 v2.0.0 或更高版本,那麼在新增 go.mod
檔案時,您需要更新模組路徑。我們將在後續文章中解釋如何在不破壞使用者的情況下完成此操作,後續文章將重點介紹 v2 及更高版本。
在您的專案中遷移到 Go modules
專案在開始向 Go modules 過渡時可能處於以下三種狀態之一
- 全新的 Go 專案。
- 已有非模組依賴項管理器的 Go 專案。
- 沒有任何依賴項管理器的已有 Go 專案。
第一種情況在使用 Go Modules 中有所介紹;本文將討論後兩種情況。
使用依賴項管理器
要轉換已使用依賴項管理工具的專案,請執行以下命令
$ git clone https://github.com/my/project
[...]
$ cd project
$ cat Godeps/Godeps.json
{
"ImportPath": "github.com/my/project",
"GoVersion": "go1.12",
"GodepVersion": "v80",
"Deps": [
{
"ImportPath": "rsc.io/binaryregexp",
"Comment": "v0.2.0-1-g545cabd",
"Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
},
{
"ImportPath": "rsc.io/binaryregexp/syntax",
"Comment": "v0.2.0-1-g545cabd",
"Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
}
]
}
$ go mod init github.com/my/project
go: creating new go.mod: module github.com/my/project
go: copying requirements from Godeps/Godeps.json
$ cat go.mod
module github.com/my/project
go 1.12
require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$
go mod init
建立一個新的 go.mod 檔案,並自動從 Godeps.json
、Gopkg.lock
或許多其他支援的格式中匯入依賴項。go mod init
的引數是模組路徑,即模組可能找到的位置。
這是暫停並執行 go build ./...
和 go test ./...
的好時機,然後再繼續。後續步驟可能會修改您的 go.mod
檔案,因此如果您喜歡迭代方法,這是您的 go.mod
檔案最接近您遷移前依賴項規範的狀態。
$ go mod tidy
go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$ cat go.sum
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
$
go mod tidy
查詢模組中包傳遞匯入的所有包。它為任何已知模組未提供的包新增新的模組需求,並移除不提供任何匯入包的模組需求。如果某個模組提供的包僅被尚未遷移到模組的專案匯入,則該模組需求將標記為 // indirect
註釋。在將 go.mod
檔案提交到版本控制之前,執行 go mod tidy
始終是一種好習慣。
最後,確保程式碼能夠構建並且測試透過
$ go build ./...
$ go test ./...
[...]
$
請注意,其他依賴項管理器可能在單個包或整個倉庫級別(而非模組級別)指定依賴項,並且通常不識別依賴項的 go.mod
檔案中指定的需求。因此,您可能無法獲得與之前完全相同的每個包版本,並且存在升級後遇到破壞性更改的風險。因此,重要的是在執行上述命令後對生成的依賴項進行審計。為此,請執行
$ go list -m all
go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
github.com/my/project
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$
並將結果版本與您的舊依賴項管理檔案進行比較,以確保所選版本是合適的。如果您發現版本不符合預期,可以使用 go mod why -m
和/或 go mod graph
找出原因,並使用 go get
升級或降級到正確的版本。(如果您請求的版本比之前選擇的版本舊,go get
將根據需要降級其他依賴項以保持相容性。)例如,
$ go mod why -m rsc.io/binaryregexp
[...]
$ go mod graph | grep rsc.io/binaryregexp
[...]
$ go get rsc.io/binaryregexp@v0.2.0
$
沒有依賴管理器
沒有依賴項管理器
$ git clone https://go.googlesource.com/blog
[...]
$ cd blog
$ go mod init golang.org/x/blog
go: creating new go.mod: module golang.org/x/blog
$ cat go.mod
module golang.org/x/blog
go 1.12
$
對於沒有依賴項管理系統的 Go 專案,首先建立 go.mod
檔案
如果沒有來自先前依賴項管理器的配置檔案,go mod init
將建立一個僅包含 module
和 go
指令的 go.mod
檔案。在本例中,我們將模組路徑設定為 golang.org/x/blog
,因為這是它的自定義匯入路徑。使用者可以使用此路徑匯入包,我們必須小心不要更改它。
module
指令宣告模組路徑,而 go
指令宣告用於編譯模組內程式碼的 Go 語言的預期版本。
$ go mod tidy
go: finding golang.org/x/website latest
go: finding gopkg.in/tomb.v2 latest
go: finding golang.org/x/net latest
go: finding golang.org/x/tools latest
go: downloading github.com/gorilla/context v1.1.1
go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: extracting github.com/gorilla/context v1.1.1
go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
$ cat go.mod
module golang.org/x/blog
go 1.12
require (
github.com/gorilla/context v1.1.1
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
$ cat go.sum
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
[...]
$
接下來,執行 go mod tidy
新增模組的依賴項
$ go build ./...
$ go test ./...
ok golang.org/x/blog 0.335s
? golang.org/x/blog/content/appengine [no test files]
ok golang.org/x/blog/content/cover 0.040s
? golang.org/x/blog/content/h2push/server [no test files]
? golang.org/x/blog/content/survey2016 [no test files]
? golang.org/x/blog/content/survey2017 [no test files]
? golang.org/x/blog/support/racy [no test files]
$
go mod tidy
添加了模組中包傳遞匯入的所有包的模組需求,並生成了一個 go.sum
檔案,其中包含每個庫在特定版本時的校驗和。最後,確保程式碼仍然可以構建並且測試仍然透過
請注意,當 go mod tidy
新增需求時,它會新增模組的最新版本。如果您的 GOPATH
包含某個依賴項的舊版本,而該依賴項隨後釋出了破壞性更改,您可能會在 go mod tidy
、go build
或 go test
中看到錯誤。如果發生這種情況,嘗試使用 go get
降級到舊版本(例如,go get github.com/broken/module@v1.1.0
),或者花時間使您的模組與每個依賴項的最新版本相容。
模組模式下的測試
遷移到 Go modules 後,某些測試可能需要進行調整。
如果某個測試需要在包目錄中寫入檔案,當包目錄位於模組快取(只讀)中時,測試可能會失敗。特別是,這可能會導致 go test all
失敗。測試應將其需要寫入的檔案複製到臨時目錄,而不是直接寫入包目錄。
如果某個測試依賴相對路徑(../package-in-another-module
)來定位和讀取另一個包中的檔案,當該包位於另一個模組中時(該模組位於模組快取的版本化子目錄或 replace
指令中指定的路徑),測試將失敗。如果是這種情況,您可能需要將測試輸入複製到您的模組中,或者將測試輸入從原始檔案轉換為嵌入在 .go
原始檔中的資料。
如果某個測試期望測試中的 go
命令在 GOPATH 模式下執行,它可能會失敗。如果是這種情況,您可能需要在待測試的原始碼樹中新增 go.mod
檔案,或顯式設定 GO111MODULE=off
。
釋出版本
$ git tag v1.2.0
$ git push origin v1.2.0
最後,您應該為您的新模組標記併發佈一個釋出版本。如果您尚未釋出任何版本,這是可選的,但如果沒有官方釋出,下游使用者將依賴使用偽版本的特定提交,這可能更難以支援。
您的新 go.mod
檔案定義了模組的規範匯入路徑,並添加了新的最低版本需求。如果您的使用者已經使用了正確的匯入路徑,並且您的依賴項沒有進行破壞性更改,那麼新增 go.mod
檔案是向後相容的——但這一個重大的變化,可能會暴露現有問題。如果您有現有的版本標籤,您應該增加次版本號。請參閱釋出 Go Modules 瞭解如何增加和釋出版本。
匯入和規範模組路徑
每個模組在其 go.mod
檔案中宣告其模組路徑。引用模組內包的每個 import
語句都必須以模組路徑作為包路徑的字首。但是,go
命令可能透過許多不同的遠端匯入路徑遇到包含該模組的倉庫。例如,golang.org/x/lint
和 github.com/golang/lint
都解析到包含託管在 go.googlesource.com/lint 的程式碼的倉庫。該倉庫中包含的go.mod
檔案宣告其路徑為 golang.org/x/lint
,因此只有該路徑對應於一個有效的模組。
Go 1.4 提供了一種使用// import
註釋宣告規範匯入路徑的機制,但包作者並非總是提供它們。因此,在模組之前編寫的程式碼可能使用了非規範的模組匯入路徑,而不會因此類不匹配而引發錯誤。使用模組時,匯入路徑必須與規範模組路徑匹配,因此您可能需要更新 import
語句:例如,您可能需要將 import "github.com/golang/lint"
更改為 import "golang.org/x/lint"
。
模組的規範路徑與其倉庫路徑不同的另一種情況發生在主版本為 2 或更高的 Go 模組。主版本大於 1 的 Go 模組必須在其模組路徑中包含主版本字尾:例如,版本 v2.0.0
必須帶有 /v2
字尾。但是,import
語句可能引用了模組內沒有該字尾的包。例如,v2.0.1
版本的 github.com/russross/blackfriday/v2
的非模組使用者可能將其匯入為 github.com/russross/blackfriday
,並且需要更新匯入路徑以包含 /v2
字尾。
結論
對於大多數使用者而言,轉換為 Go modules 應該是一個簡單的過程。偶爾可能由於非規範匯入路徑或依賴項中的破壞性更改而出現問題。未來的文章將探討釋出新版本、v2 及更高版本以及除錯奇怪情況的方法。
感謝您的所有反饋以及對改進模組的幫助。
下一篇文章:Go Module Mirror 和 Checksum 資料庫已啟動
上一篇文章:2019 年貢獻者峰會