- 原文標題:Using Go Modules
- 原文做者:Tyler Bui-Palsulich and Eno Compton
- 原文時間:2019-03-19
Go 1.11 和 1.12 版本初步支持了 模塊(modules)。它是 Go 中新的依賴管理系統,能更簡單的顯示、管理依賴的版本。本文介紹將模塊
使用入門所須要的一些基本操做。後續將有文章來涵蓋跟多模塊
的使用。git
一個模塊
就是一系列 Go packages
的集合,其存儲在一個根目錄中包含 go.mod
文件的文件夾中。go.mod
文件定義了模塊的 模塊路徑
,這也是導入路徑中的根路徑,還定義了能成功構建本模塊所需的依賴模塊。每一項依賴表示爲一個模塊路徑和一個特殊的語義化的版本號。github
Go 1.11 中,go 會在下面狀況下使用模塊
:當前目錄或任何上級目錄中有 go.mod
文件,且在 $GOPATH/src
目錄以外(爲了兼容,在 $GOPATH/src
目錄中依然使用老的 GOPATH 模式,即便存在 go.mod
,詳情見 go 命令文檔)。從 Go 1.13 開始,模塊模式
將成爲默認的依賴管理系統。golang
本文介紹使用模塊
來開發 Go 項目時一些經常使用操做:緩存
讓咱們來新建一個模塊。安全
在 $GOPATH/src
目錄外的任意目錄中,新建一個空的文件夾。cd 進入該文件夾,新建一個源文件,hello.go
:bash
package hello
func Hello() string {
return "Hello, World."
}
複製代碼
而後再寫個測試,名爲 hello_test.go
:併發
package hello
import "testing"
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello() =%q, want %q", got, want)
}
}
複製代碼
如今,這個目錄下包含一個 包(package)
,而不是一個 模塊(module)
,由於沒有 go.mod
文件。若是該目錄爲 /home/gopher/hello,運行 go test
,咱們將看到:函數
$ go test
PASS
ok _/home/gopher/hello 0.020s
$
複製代碼
最後一行列出了全部被測試的包。由於咱們的目錄在 $GOPATH
以外,也不屬於任何模塊,go 知道對於當前目錄沒有導入路徑,因此 go 建立了一個基於當前文件路徑的假模塊:_/home/gopher/hello。測試
使用 go mod init
來把當前目錄變成一個模塊的根路徑,而後再試一次 go test
:ui
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
$ go test
PASS
ok example.com/hello 0.020s
$
複製代碼
恭喜!你已經完成並測試了你的第一個模塊
。
go mod init
命令將如下內容寫入 go.mod
文件中:
$ cat go.mod
module example.com/hello
go 1.12
$
複製代碼
go.mod
文件僅出如今模塊的根路徑中。子目錄下的包的導入路徑爲模塊路徑加上子目錄路徑。例如,咱們新建一個 world
子目錄,咱們不須要在 world
中運行(也不想) go mod init
。這個包將自動成爲模塊 example.com/hello
的一部分,導入路徑爲 example.com/hello/world
。
Go 模塊
的首要目標是提高使用別人寫的代碼(也是添加依賴)的體驗。
讓咱們更新 hello.go
,導入 rsc.io/quote
依賴並使用它來實現 Hello:
package hello
import "rsc.io/quote"
func Hello() string {
return quote.Hello()
}
複製代碼
如今,讓咱們再次運行測試:
$ go test
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok example.com/hello 0.023s
$
複製代碼
go 使用 go.mod
中的特定版本的依賴模塊來解析包導入。當它遇到一個不屬於 go.mod
中任何一個模塊中的導入包時,它會自動尋找包含該缺失包的模塊,並把這個模塊的最新版本(最新版本
的定義爲最新的一個被標記爲穩定(stable)
的版本,若是沒有則爲最新的預覽(prerelease)
版本,若是仍是沒有,則爲最新的沒有標記的版本)加入 go.mod
中。在咱們的例子中,go test 解析新的 rsc.io/quote
導入爲模塊 rsc.io/quote v1.5.2
。它同時也下載了 rsc.io/quote
所須要的兩個依賴:rsc.io/sampler
和 golang.org/x/test
。只有直接依賴會出如今 go.mod
文件中:
$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote v1.5.2
$
複製代碼
再次運行 go test 不會重複以上的工做,由於 go.mod
已經被更新,並且能下載的模塊也緩存到了本地(在 $GOPATH/pgk/mod
中):
$ go test
PASS
ok example.com/hello 0.020s
$
複製代碼
注意,雖然go 命令能輕鬆快速的添加一個新依賴,但這並不是沒有代價。你的模塊如今依賴新的模塊,須要注意不少特殊的點,例如:正確性、安全性以及合適的許可證,以上僅僅是幾個例子。更多思考,見 Russ Cox 的這篇文章,Our Software Dependency Problem。
就如咱們上面看到的,添加一個直接依賴每每會帶來其餘的非直接依賴。命令 go list -m
會列出當前模塊和全部的依賴模塊:
$ go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$
複製代碼
go list
輸出中第一行永遠是當前模塊,也叫主模塊
。接下來是按模塊路徑排序的依賴模塊。
上面 golang.org/x/text version v0.0.0-20170915032832-14c0d48ead0c
是一個 pseudo-version
的例子,這是 go 對於沒有標籤的版本語法。
還有一點關於 go.mod
,就是 go 會維護一個名爲 go.sum
文件,該文件包含目標依賴特定版本的哈希值:
$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
$
複製代碼
go 使用這個 go.sum
來確保未來下載的依賴版本和第一次下載的是如出一轍的,以確保你項目中依賴的模塊沒有改變,不管是惡意的或意外的仍是其餘緣由致使的改變。go.mod
和 go.sum
都應該加入到版本控制中。
Go 模塊中,版本號是語義化的。語義化版本號有三個部分:major、minor、patch。例如,版本號 v0.1.2
中,major 版本是 0,minor 版本是 1,patch 版本是 2。讓咱們試一試 minor 版本更新。下一節,進行 major 版本更新。
從 go list -m all
的輸出中,咱們能夠看到,咱們使用的是沒有版本號的 golang.org/x/test
。讓咱們來升級到最新的版本,看看是否還能正常工做:
$ go get golang.org/x/text
go: finding golang.org/x/text v0.3.0
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
$ go test
PASS
ok example.com/hello 0.013s
$
複製代碼
哇,全部都經過了。讓咱們看看 go list -m all
和 go.mod
文件:
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote v1.5.2
)
$
複製代碼
golang.org/x/test
包已經更新到最新的帶版本號的版本(v0.3.0)。go.mod
中也更新到了 v0.3.0。indirect
註釋表示它不是被本模塊直接使用,而是被本模塊依賴的模塊使用。詳情見 go modules
。
如今,讓咱們來試下更新 rsc.io/sampler
的 minor 版本。和上面同樣,運行 go get
而後運行測試:
$ go get rsc.io/sampler
go: finding rsc.io/sampler v1.99.99
go: downloading rsc.io/sampler v1.99.99
go: extracting rsc.io/sampler v1.99.99
$ go test
--- FAIL: TestHello (0.00s)
hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL example.com/hello 0.014s
$
複製代碼
啊,測試失敗。輸出代表最新版的 rsc.io/sampler
與咱們的用法不兼容。那讓咱們看看這個模塊有哪些可用的版本:
$ go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
$
複製代碼
咱們以前使用的就是 v1.3.0
,而 v1.99.99
與咱們不兼容。那試一試 v1.3.1
:
$ go get rsc.io/sampler@v1.3.1
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
$ go test
PASS
ok example.com/hello 0.022s
$
複製代碼
注意,go get
中使用 @v1.3.1
來顯示的指明版本,默認是 @latest
,也就是最新的版本。
咱們來添加一個新函數:func Proverb
返回一個 Go 併發 proverb。這是經過調用 rsc.io/quote/v3
中的 quote
實現的。首先,咱們在 hello.go
中添加一個新函數:
package hello
import (
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string {
return quote.Hello()
}
func Proverb() string {
return quoteV3.Concurrency()
}
複製代碼
而後在 hello_test.go
中添加一個測試:
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Prover(); got != want {
t.Error("proverb() = %q, want %q", got, want)
}
}
複製代碼
而後,咱們運行測試:
$ go test
go: finding rsc.io/quote/v3 v3.1.0
go: downloading rsc.io/quote/v3 v3.1.0
go: extracting rsc.io/quote/v3 v3.1.0
PASS
ok example.com/hello 0.024s
$
複製代碼
注意,咱們的模塊如今同時依賴 rsc.io/quote
和 rsc.io/quote/v3
:
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$
複製代碼
Go 模塊的每個不一樣的 major 的版本(v1, v2等等)都使用不一樣的模塊路徑:從 v2
開始,路徑必須以 major 版本號結尾。例如, rsc.io/quote
的 v3 版本不一樣於 rsc.io/quote
,它們經過模塊路徑來區別。這種約定慣例稱爲 語義化導入版本(semantic import versioning),它給不兼容的包(不一樣的 major 版本號)不一樣的名字。與之對比,rsc.io/quote
的 v1.6.0 版應該向後兼容 v1.5.2 版,因此它能重用 rsc.io/quote
這個名字。(上節中,rsc.io/sampler
的 v1.99.99 版應該向後兼容 v1.3.0 版,但因爲 bug 或者客戶端錯誤的使用致使不兼容。)
go 構建的時候某個模塊路徑最多隻能有一個版本,也就是每一個 major 版本最多隻能有一個:一個 rsc.io/quote
,一個 rsc.io/quote/v2
,一個 rsc.io/quote/v3
等等。這給模塊做者一個模塊可能重複使用的清晰規則:同時使用 rsc.io/quote
的 v1.5.2 和 1.6.0 是不容許的。同時,容許使用同一模塊的不一樣 major 版本(由於它們有不一樣的模塊路徑),這給模塊使用者升級到一個新的 major 版本的能力。例如,咱們想使用 rsc.io/quote/v3
中的quote.Concurrency
,還咱們還不許備對rsc.io/quote
v1.5.2 的使用進行升級合併。這種平滑升級合併是很是重要的,尤爲是在大型軟件或代碼庫中。
如今讓咱們來完成從 src.io/quote
到 src.io/quote/v3
的升級轉化。由於 major 版本號變了,那可能存在一些 API 被移除,或重命名,或有不兼容的變化。經過文檔,咱們發現 Hello
變成了 HelloV3
:
$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote/v3"
Package quote collects pithy sayings.
func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$
複製代碼
咱們將 hello.go
中的 quote.Hello()
升級爲 quoteV3.HelloV3()
:
package hello
import quoteV3 "rsc.io/quote/v3"
func Hello() string {
return quoteV3.HelloV3()
}
func Proverb() string {
return quoteV3.Concurrency()
}
複製代碼
如今,咱們也不用重命名導入了,因此去掉重命名:
package hello
import "rsc.io/quote/v3"
func Hello() string {
return quote.HelloV3()
}
func Proverb() string {
return quote.Concurrency()
}
複製代碼
最後,運行測試看看一切都是否正確:
$ go test
PASS
ok example.com/hello 0.014s
複製代碼
咱們移除了全部關於 rsc.io/quote
的使用,但這個模塊依然在 go list -m
和 go.mod
文件中:
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.0.0
rsc.io/sampler v1.3.1 // indirect
)
$
複製代碼
這是爲何?由於構建單個包,好比 go build
或 go test
,能輕鬆的發現缺什麼包並須要加入到依賴中,卻不能發現能安全移除的包。只有在檢查一個模塊中全部的包以後,才能肯定哪些依賴能安全移除。普通的構建命令不會加載全部的包,於是也不能安全的移除沒有使用的依賴。
go mod tidy
命令能清理這些沒有使用的依賴:
$ go mod tidy
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello
go 1.12
require (
golang.org/x/text v0.3.0 // indirect
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1 // indirect
)
$ go test
PASS
ok example.com/hello 0.020s
$
複製代碼
Go 模塊
是 Go 語言將來的依賴管理系統。模塊功能在全部支持的 Go 版本中都是可用的(如今爲,Go 1.11,Go 1.12)。
本文介紹了使用 Go 模塊的工做流:
go mod init
建立一個新模塊,並初始化 go.mod
文件來描述該模塊。go bulid
,go test
還有其餘的包構建命令來添加一個新的依賴到 go.mod
中。go list -m all
打印當前模塊的依賴。go get
改變依賴的版本(或者添加新依賴)。go mod tidy
移除未使用的依賴。咱們鼓勵你在我的本機開發中使用 Go 模塊,而且把 go.mod
和 go.sum
加入到你的項目中。提供反饋和幫助 Go 中依賴管理完善,請發送 bug 報告 或 使用報告 給咱們。
感謝你的反饋以及對模塊功能提高的幫助。
本文主要講 Go 模塊的基本用法,關於模塊的更多概念和用法見 Github Page