【譯】Go 模塊使用入門

  • 原文標題: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 項目時一些經常使用操做:緩存

  • 新建模塊
  • 添加依賴
  • 更新依賴
  • 添加新的 major 版本的依賴
  • 更新依賴到新的 major 版本
  • 移除未使用的依賴

新建模塊

讓咱們來新建一個模塊。安全

$GOPATH/src 目錄外的任意目錄中,新建一個空的文件夾。cd 進入該文件夾,新建一個源文件,hello.gobash

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 testui

$ 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/samplergolang.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.modgo.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 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/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,也就是最新的版本。

添加一個新 major 版本依賴

咱們來添加一個新函數: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/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,它們經過模塊路徑來區別。這種約定慣例稱爲 語義化導入版本(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 的使用進行升級合併。這種平滑升級合併是很是重要的,尤爲是在大型軟件或代碼庫中。

更新一個依賴到最新的 major 版本

如今讓咱們來完成從 src.io/quotesrc.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 -mgo.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
$
複製代碼

總結

Go 模塊是 Go 語言將來的依賴管理系統。模塊功能在全部支持的 Go 版本中都是可用的(如今爲,Go 1.11,Go 1.12)。

本文介紹了使用 Go 模塊的工做流:

  • go mod init 建立一個新模塊,並初始化 go.mod 文件來描述該模塊。
  • go bulidgo test 還有其餘的包構建命令來添加一個新的依賴到 go.mod 中。
  • go list -m all 打印當前模塊的依賴。
  • go get 改變依賴的版本(或者添加新依賴)。
  • go mod tidy 移除未使用的依賴。

咱們鼓勵你在我的本機開發中使用 Go 模塊,而且把 go.modgo.sum 加入到你的項目中。提供反饋和幫助 Go 中依賴管理完善,請發送 bug 報告使用報告 給咱們。

感謝你的反饋以及對模塊功能提高的幫助。

譯者總結

本文主要講 Go 模塊的基本用法,關於模塊的更多概念和用法見 Github Page

相關文章
相關標籤/搜索