1. 程式人生 > 程式設計 >【翻譯】【Go】Go Modules的用法

【翻譯】【Go】Go Modules的用法

簡介

Go 1.11 和 1.12 都對模組有了初步的支援,使得對依賴的管理更加詳細和容易。這個文章對模組的使用做一個基本的介紹。 模組是對Go包一個集合,以一個檔案樹的形式儲存在根目錄的go.mod檔案中。go.mod檔案定義了模組的路徑,還有相關的依賴項。每一個依賴項都會被認為是一個模組路徑和指定的版本規則,其實就是依賴項也是一個模組。 在Go 1.11的時候,Go命令列就有了對模組的支援,噹噹前的目錄或者父目錄有go.mod檔案的時候,並且可以在GOPATH以外的地方使用,為了相容性,即使在GOPATH中發現了go.mod檔案,也會使用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) } } 複製程式碼

這是,當前目錄中包含了一個包,並不是一個模組,因為這裡還沒有go.mod檔案,如果我們在當前目錄下執行go test,可以看到:

D:\Code\go>go test
PASS
ok      _/D_/Code/go    0.327s
D:\Code\go>
複製程式碼

最後一行總結了整體包測試。 因為我們在$GOPATH之外以及任何模組之外工作,所以go命令不知道當前目錄的匯入路徑,並根據目錄名稱構成假路徑:_/D_/Code/go。 現在,我們在當前目錄下執行go mod init,然後再進行go test:

D:\Code\go>go mod init example.com/hello
go: creating new go.mod: module example.com/hello

D:\Code\go>go test
PASS ok example.com/hello 0.393s D:\Code\go> 複製程式碼

恭喜!你已經寫了一個模組,並且通過了測試 go mod init命令生成一個go.mod檔案:

PS D:\Code\go> cat .\go.mod                                                                                                                                                                   module example.com/hello                                                                                                                                                                      
go 1.12
PS D:\Code\go>
複製程式碼

go.mod檔案只會出現在模組的根目錄,子目錄中的包具有匯入路徑,包括模組路徑和子目錄的路徑。例如,我們建立一個子目錄world,我們可以不在再次執行go mod init,包會自動載入並且可以被識別出是example.com/hello的一部分,匯入路徑就是example.com/hello/world

新增依賴

Go模組的主要作用是提升可以使用其他開發人員提供的程式碼。 更新hello.go,匯入rsc.io/quote包:

package hello

import "rsc.io/quote"

func Hello() string {
	return quote.Hello()
}

複製程式碼

再一次執行go test

D:\Code\go>go test
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
--- FAIL: TestHello (0.00s)
    hello_test.go:10: Hello() = "Hello,world.",want "Hello,world"
FAIL
exit status 1
FAIL    example.com/hello       0.431s

D:\Code\go>
複製程式碼

go命令會解析go.mod檔案中指定的依賴和版本。當遇見一個在go.mod中沒有提供的包時,go命令會自動查詢這個包並且新增到go.mod檔案中,使用最新的版本(最新的版本指的時被打標籤的穩定版,或者最新的預釋出版本,或者最新的版本),在我們這個例子中,go test解析了一個新的包rsc.io/quote,版本為v1.5.2。還下載了兩個包rsc.io/quote使用的兩個依賴項,就是rsc.io/samplergolang.org/x/testgo.mod檔案指記錄直接的依賴關係:

D:\Code\go>type go.mod
module example.com/hello

go 1.12

require rsc.io/quote v1.5.2

D:\Code\go>
複製程式碼

再一次執行go test的時候,就不會重複上一次的任務了,因為go.mod現在已經更新了,並且依賴已經被快取到了本地($GOPATH/pkg/mod) 正如我們看到的,新增一個依賴經常會帶來其他的依賴,go list -m命令可以列出當前模組的所有依賴

yankeweideMacBook-Pro:hello yankewei$ go list -m all
hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
yankeweideMacBook-Pro:hello yankewei$ 
複製程式碼

在輸出的內容中,當前的模組名總是會出現在第一行,緊跟著按依賴的模組路徑排序。 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c是一個偽版本號,這個go系統對於一個沒有打標的commit的標識。 除了go.mod之外,go命令還維護一個名為go.sum的檔案,其中包含特定模組版本內容的預期加密雜湊:

yankeweideMacBook-Pro:hello yankewei$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
yankeweideMacBook-Pro:hello yankewei$ 
複製程式碼

go使用go.sum檔案來確保未來下載這些模組的時候保證和第一次下載的一樣,也確保你專案的依賴不會改變,所以go.mod和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/test包已經被更新到了最新的版本(v0.3.0)。indirect註釋表示依賴沒有被這個模組直接使用,僅僅被其他的模組間接使用,可以通過go help modules檢視相信資訊。

現在讓我們嘗試更新rsc.io/sampler版本。

$ 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,...",world."
FAIL
exit status 1
FAIL    example.com/hello    0.014s
$
複製程式碼

Shit!測試失敗了,最新的版本和我們要使用的不相容,先看一下這個模組的可用版本:

$ 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/[email protected]
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,函式會呼叫rsc.io/quote/v3的模組,在這個模組中可以呼叫quote.Concurrency,返回一個字串,首次我們先來更新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)
    }
}
複製程式碼

現在可以執行程式碼:

$ 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/quotersc.io/quote/v3 每個不同的主版本(v1,v2等等)的模組使用的是不同的路徑,這樣就可以選擇性的使用,並且在版本遷移的時候也可以逐步的進行。

更新依賴為新的主版本

現在我們想要把rsc.io/quote更新到rsc.io/quote/v3。因為主版本號已經變了,我們可能會意識到某些api被移除,被重新命名或者呼叫方法發生了改變,所以首先我們需要先看一下檔案:

$ 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 改為了 HelloV3,那我們就可以把使用的 Hello 改為 HelloV3,並且檔案中沒有對舊版本的依賴了,可以把匯入重新命名去掉:

package hello

import (
	"rsc.io/quote/v3"
)

func Hello() string {
	return quote.HelloV3()
}

func Proverb() string {
	return quote.Concurrency()
}
複製程式碼

可以自行測試,就不演示了。

刪除未使用的依賴

在Go語言中刪除未使用的依賴相當的簡單,只需要執行go mod tidy

原文連結