Go的1.11和1.12版本包括對模塊--新的Go依賴管理系統的初步支持,使依賴版本信息變得明確且更易於管理。這篇博客文章介紹了開始使用模塊所需的基本操做。golang
模塊是存儲在根目錄有一個 go.mod
文件的文件樹中的 Go 包(package)的集合。go.mod
文件定義了模塊的module path(也是模塊根目錄的導入路徑)以及模塊依賴的其餘模塊的要求,知足了依賴要求模塊才能被成功構建起來。每一個依賴模塊的要求被寫爲一個模塊路徑和相應的模塊版本。shell
下面展現了一個簡單的go.mod
文件json
module example.com/hello go 1.12 require rsc.io/quote v1.5.2
從Go 1.11開始,噹噹前目錄或任何父目錄有go.mod
時,只要該目錄位於$GOPATH/src
以外,go命令就可使用模塊。 (在$ GOPATH/src
內部,出於兼容性考慮,即便找到了go.mod
,go命令仍然在舊的GOPATH模式下運行。)從Go 1.13開始,模塊模式將是全部開發的默認模式。緩存
本文介紹了使用模塊開發Go代碼時出現的一系列常見操做:安全
在$GOPATH/src
以外的某個地方建立一個新的空目錄,而後在新目錄下建立一個新的源文件hello.go
:併發
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) } }
假設咱們新建的目錄爲/home/gopher/hello
,此時該目錄包含一個包,而不是模塊,由於目錄中沒有go.mod
文件。使用 go 命令運行測試會看到:學習
$ go test PASS ok _/home/gopher/hello 0.020s $
輸出的最後一行彙總了整個包的測試信息。由於咱們工做在$GOPATH
和任意模塊以外,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 mod init
命令編寫了一個go.mod
文件:
$ cat go.mod module example.com/hello go 1.12 $
go.mod
僅出如今模塊的根目錄中。位於子目錄中的包的導入路徑將由模塊路徑加上子目錄路徑組成。好比說若是咱們建立了一個子目錄world
無需(也不但願)在其中運行go mod init
。該包將自動被識別爲example.com/hello
模塊的一部分,導入路徑爲example.com/hello/world
。
如今再運行go test
其運行結果以下:
$ go test PASS ok example.com/hello 0.020s $
如今輸出中的導入路徑變成了example.com/hello
,不知不覺中就編寫並測試了咱們的第一個go模塊。
Go模塊的主要動機是改善管理使用其餘開發者編寫的代碼(代碼依賴)的體驗。 讓咱們更新hello.go
以導入rsc.io/quote
並使用它來實現Hello
函數:
package hello import "rsc.io/quote" func Hello() string { return quote.Hello() }
如今再次運行go test
:
$ 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
中的任何模塊提供的包的導入時,go
命令將自動查找包含該軟件包的模塊,使用其最新的穩定版本,並將其添加到go.mod中。 在咱們的示例中,go test
將新的導入rsc.io/quote
解析爲rsc.io/quote v1.5.2
模塊,它還下載了rsc.io/quote
使用的兩個依賴項,即rsc.io/sampler
和golang.org/x/text
。可是隻有直接依賴項被記錄在go.mod
文件中:
$ cat go.mod module example.com/hello go 1.12 require rsc.io/quote v1.5.2 $
再次運行go test
命令不會重複上面的依賴下載工做,由於go.mod
如今是最新的,而且下載的模塊已本地緩存在$ GOPATH/pkg / mod
中了。
正如咱們在上面看到的,添加一個直接依賴項一般也會帶來其餘間接依賴項。命令go list -m all列出當前模塊及其全部依賴項:
$ 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
的輸出中,當前模塊也被稱爲主模塊,老是會出如今第一行,後面跟隨的是根據模塊路徑排序後展現的依賴項:
除了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.sum
並非相似package-lock.json
的包管理器鎖文件,它是一個構建狀態跟蹤文件。它會記錄當前模塊全部的直接和間接依賴,以及這些依賴的校驗和,從而提供一個能夠100%復現的構建過程並對構建對象提供安全性的保證。因此應該將go.mod
和go.sum
都添加到版本控制中。go.sum
同時還會保留過去使用的包的版本信息,以便往後可能的版本回退,這一點也與普通的鎖文件不一樣。因此go.sum並非包管理器的鎖文件。
對於Go模塊,使用語義版本標記引用模塊版本。語義版本包括三個部分:主要,次要和補丁。例如,對於v0.1.2,主要版本爲0,次要版本爲1,補丁版本爲2。讓咱們逐步進行幾個次要版本升級。在下一節中,咱們將考慮進行主要版本升級
從go list -m all
的輸出中,咱們能夠看到咱們正在使用未標記版本的golang.org/x/text
。讓咱們升級到最新的標記版本,並測試一切是否正常:
$ 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/text
軟件包已升級到最新的標記版本(v0.3.0)。 go.mod
文件中golang.org/x/text
也已更新爲指定的v0.3.0
。indirect
註釋指明依賴項不被當前模塊直接使用,而是由其依賴的模塊所使用的。
如今,讓咱們嘗試升級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 $
咱們將 rsc.io/sampler
升級到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。一般,傳遞給get的每一個參數均可以採用顯式形式。默認值爲@latest,它將解析爲先前定義的最新版本。
讓咱們在包中添加一個新函數:函數Proverb
經過調用quote.Concurrency
返回Go併發諺語(就是Pike說在某年 Go 開發大會上說的金句:"Concurrency is not parallelism"),這是由rsc.io/quote/v3
模塊提供的。首先,咱們更新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 := Proverb(); got != want { t.Errorf("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 $
能夠看到 go 命令下載安裝了rsc.io/quote/v3
模塊,如今咱們的模塊同時依賴了 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模塊的每一個不一樣的主要版本(v1,v2等)都使用不一樣的模塊路徑:從v2開始,該路徑必須以主要版本結尾。在示例中,rsc.io/quote
的v3版本的模塊路徑再也不是rsc.io/quote
,而是rsc.io/quote/v3
。此約定稱爲語義導入版本控制,它爲不兼容的程序包(具備不一樣主要版本的程序包)提供了不一樣的名稱。相反,rsc.io/quote的v1.6.0
應該與v1.5.2
向後兼容,所以它重用了名稱rsc.io/quote
。
go命令要求每一個主版本模塊路徑不可重複,每一個主要版本的至多:一個rsc.io/quote
,一個rsc.io/quote/v2
,一個rsc.io/quote/v3
,依此類推。這爲模塊做者提供了關於可能重複單個模塊路徑的明確規則:程序沒法同時使用rsc.io/quote
v1.5.2和rsc.io/quote
v1.6.0來構建。同時,容許模塊的不一樣主要版本(由於它們具備不一樣的路徑)使模塊使用者能夠逐步升級到新的主要版本。在此示例中,咱們想使用rsc/quote/v3
v3.1.0中的quote.Concurrency
,但還沒有準備好遷移rsc.io/quote
v1.5.2的使用。在大型程序或代碼庫中,增量遷移的能力尤爲重要。
讓咱們完成從使用rsc.io/quote
兩個版本的包到僅使用rsc.io/quote/v3
的轉換。因爲版本的重大更改,咱們應該指望某些API可能已以不兼容的方式被刪除,重命名或以其餘方式更改。閱讀文檔,咱們能夠看到Hello已經變成HelloV3
:
$ go doc rsc.io/quote/v3 package quote // import "rsc.io/quote" Package quote collects pithy sayings. func Concurrency() string func GlassV3() string func GoV3() string func HelloV3() string func OptV3() string $
咱們能夠把hello.go
中對qoute.Hello()
的調用更新爲使用quoteV3.HelloV3()
,如今已經不須要對 v3 版本的導入路徑重命名了因此咱們撤銷包的重命名(注意默認包名不會包含版本後綴)。
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 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/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)能夠輕鬆判斷出來缺乏某些內容並須要添加,但沒法肯定某些內容是否能夠安全刪除。只有在檢查模塊中的全部軟件包以及這些軟件包的全部可能的構建標記組合以後,才能刪除依賴項。普通的build
命令不會加載此信息,所以它不能安全地刪除依賴項。
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依賴管理的將來。從 Go1.11都提供模塊功能。 這篇文章介紹了使用Go模塊的這些工做流程:
go.mod
添加新的依賴項。參考文章:https://blog.golang.org/using...
如今愈來愈多的項目都開始用Go Modules來管理依賴包,我也是剛開始嘗試將現有項目遷移到用 Go Modules管理的模式,在實踐中發現還有不少要學習的地方,後期會分享更多這方面的學習文章和總結。