翻译自 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代码时出现的一系列常见操做:函数
建立一个新模块。测试
在 $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
。
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/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 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都应检入版本控制。
使用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 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 文件也已经更新为指定的 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
,它解析为以前定义的最新版本。
让咱们为咱们的包添加一个新函数: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/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
:相反,它由模块路径 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.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
(输出中还有一个已知错误:显示的导入路径错误地删除了 /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
咱们已经删除了对 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
,能够很容易地判断哪些是缺乏的,哪些须要添加,可是不知道哪些是能够安全删除的。 只有在检查模块中的全部包以及这些包的全部可能的构建标记组合以后,才能删除依赖项。 普通的构建命令不会加载此信息,所以没法安全地删除依赖项。
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 build
,go test
和其余包构建命令根据须要为 go.mod
添加新的依赖项。go list -m all
打印当前模块的依赖项。go get
更改所需的依赖项的版本(或添加新的依赖项)。go mod tidy
删除未使用的依赖项。咱们鼓励您开始在本地开发中使用模块,并将 go.mod 和 go.sum 文件添加到项目中。 为了提供反馈并帮助塑造Go中依赖管理的将来,请向咱们发送错误报告或体验报告。
感谢您的全部反馈和帮助改进模块。
By Tyler Bui-Palsulich and Eno Compton