【翻譯】Using Go Modules

Go Modules

翻譯自 Using Go Modulesgolang

Go 1.11 和 1.12 包括對模塊的初步支持,Go的新依賴管理系統使依賴版本信息明確且易於管理。此博客文章是介紹開始使用模塊所需的基本操做的教程。後續帖子將涵蓋發佈供其餘人使用的模塊。緩存

module 是存儲在文件樹中的Go包的集合,其根目錄中包含 go.mod 文件。go.mod 文件定義了模塊的 module path,它也是用於根目錄的導入路徑,以及它的依賴性要求,它們是成功構建所需的其餘模塊。每一個依賴性要求都被寫爲模塊路徑和特定語義版本。安全

從Go 1.11開始,go命令容許在當前目錄或任何父目錄具備 go.mod 時使用模塊,前提是目錄在 $GOPATH/src 以外。(在 $GOPATH/src 中,爲了兼容性,go命令仍然在舊的GOPATH模式下運行,即便找到了 go.mod 也是如此。詳情請參閱 go命令文檔)。從Go 1.13開始,模塊模式將是全部開發的默認模式。併發

本文將介紹使用模塊開發Go代碼時出現的一系列常見操做:函數

  • Creating a new module. 建立新模塊
  • Adding a dependency. 添加依賴項
  • Upgrading dependencies. 升級依賴項
  • Adding a dependency on a new major version. 添加新版本的依賴項
  • Upgrading a dependency to a new major version. 升級依賴項到新版本
  • Removing unused dependencies. 刪除未使用的依賴項

Creating a new module

建立一個新模塊。測試

$GOPATH/src 外建立一個空目錄,cd 到這個目錄,而後建立一個源文件 hello.go:ui

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文件。若是咱們工做在 /home/gopher/hello 目錄執行 go test,咱們會看到:翻譯

$ go test
PASS
ok      _/home/gopher/hello    0.020s

最後一行總結了總體包測試。由於咱們在 $GOPATH 以外以及任何模塊以外工做,因此go命令不知道當前目錄的導入路徑,而是根據目錄名稱構成假路徑:_/home/gopher/hello版本控制

讓咱們使用 go mod init 將當前目錄做爲模塊的根目錄,而後再次嘗試 go test

$ 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,咱們不須要(也不想)在那個目錄下運行 go mod init。這個包會被自動識別爲 example.com/hello 的一部分,其導入路徑是 example.com/hello/world

Adding a dependency

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 中任何模塊都未提供的軟件包導入時,go命令會自動查找包含該軟件包的模塊,並使用最新版本將其添加到go.mod中(「最新」被定義爲最新的標記穩定版(非預發行版),或者最新的標記預發佈版本,或者最新的未標記版本)。在咱們的示例中,go test 將新導入 rsc.io/quote 解析爲模塊 rsc.io/quote v1.5.2。它還下載了 rsc.io/quote 使用的兩個依賴項,即 rsc.io/samplergolang.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 test
PASS
ok      example.com/hello    0.020s

請注意,雖然go命令能夠快速輕鬆地添加新的依賴項,但它並不是沒有成本。如今,您的模塊依賴於關鍵區域中的新依賴關係,例如正確性,安全性和適當的許可,僅舉幾例。有關更多注意事項,請參閱 Russ Cox 的博客文章 咱們的軟件依賴性問題

如上所述,添加一個直接依賴一般也會帶來其餘間接依賴。命令 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 輸出中,當前模塊(也稱爲主模塊)始終是第一行,後面是按模塊路徑排序的依賴項。

golang.org/x/text 版本 v0.0.0-20170915032832-14c0d48ead0c僞版本的示例,它是特定無標記提交的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都應檢入版本控制。

Upgrading dependencies

使用Go模塊,版本使用語義版本標記引用。語義版本包含三個部分:major,minor和patch。例如,對於 v0.1.2,主要版本 major 爲0,次要版本 minor 爲1,修補程序版本 patch 爲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 allgo.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 文件也已經更新爲指定的 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, ...", 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。一般,傳遞給 go get 的每一個參數均可以採用顯式版本; 默認值爲 @latest,它解析爲以前定義的最新版本。

Adding a dependency on a new major version

讓咱們爲咱們的包添加一個新函數:func Proverb 經過調用 quote.Concurrency 返回一個Go併發諺語,該函數由模塊 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

請注意,咱們的模塊如今依賴於 rsc.io/quotersc.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:相反,它由模塊路徑 rsc.io/quote/v3 標識。 此約定稱爲語義導入版本控制,它爲不兼容的包(具備不一樣主要版本的包)提供不一樣的名稱。 相比之下,rsc.io /quote 的v1.6.0應該與v1.5.2向後兼容,所以它重用 rsc.io/quote 這個名稱。 (在上一節中,rsc.io/sampler v1.99.99應該與 rsc.io/sampler v1.3.0向後兼容,可是有關模塊行爲的錯誤或不正確的客戶端假設均可能發生。)

go命令容許構建包含任何特定模塊路徑的最多一個版本,最多意味着每一個主要版本之一:一個 rsc.io/quote,一個 rsc.io/quote/v2,一個 rsc.io/quote/ v3,依此類推。 這爲模塊做者提供了關於單個模塊路徑可能重複的明確規則:使用 rsc.io/quote v1.5.2rsc.io/quote v1.6.0 構建程序是不可能的。 同時,容許模塊的不一樣主要版本(由於它們具備不一樣的路徑)使模塊使用者可以以遞增方式升級到新的主要版本。 在這個例子中,咱們想使用 rsc/quote/v3 v3.1.0中的 quote.Concurrency 但還沒有準備好遷移咱們對 rsc.io/quote v1.5.2 的使用。 在大型程序或代碼庫中,逐步遷移的能力尤其重要。

Upgrading a dependency to a new major version

讓咱們完成從使用 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

(輸出中還有一個已知錯誤:顯示的導入路徑錯誤地刪除了 /v3)。

咱們能夠更新 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()
}

而後在這一點上,再也不須要重命名 import 的名稱,因此咱們能夠撤消它:

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

Removing unused dependencies

咱們已經刪除了對 rsc.io/quote 的全部使用,但它仍然顯示在 go list -m allgo.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 buildgo 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

Conclusion

Go模塊是Go中依賴管理的將來。 如今,全部支持的Go版本(即Go 1.11和Go 1.12)都提供了模塊功能。

這篇文章使用Go模塊介紹了這些工做流程:

  • go mod init 建立一個新模塊,初始化描述它的 go.mod 文件。
  • go buildgo test 和其餘包構建命令根據須要爲 go.mod 添加新的依賴項。
  • go list -m all 打印當前模塊的依賴項。
  • go get 更改所需的依賴項的版本(或添加新的依賴項)。
  • go mod tidy 刪除未使用的依賴項。

咱們鼓勵您開始在本地開發中使用模塊,並將 go.mod 和 go.sum 文件添加到項目中。 爲了提供反饋並幫助塑造Go中依賴管理的將來,請向咱們發送錯誤報告體驗報告

感謝您的全部反饋和幫助改進模塊。

By Tyler Bui-Palsulich and Eno Compton

相關文章
相關標籤/搜索