Using Go Modules
翻译自:golang.org/using-go-modules
如
果
你
能
够
完
整
阅
读
并
敲
敲
代
码
测
试
,
基
本
这
篇
文
章
已
经
能
够
让
你
理
解
g
o
−
m
o
d
u
l
e
s
了
。
\color{red}{如果你能够完整阅读并敲敲代码测试,基本这篇文章已经能够让你理解go-modules了。}
如果你能够完整阅读并敲敲代码测试,基本这篇文章已经能够让你理解go−modules了。
如
果
你
看
完
了
这
篇
文
章
,
可
以
在
看
看
这
里
2
个
例
子
:
\color{blue}{如果你看完了这篇文章,可以在看看这里2个例子:}
如果你看完了这篇文章,可以在看看这里2个例子:
例子A和B链接
Introduction介绍
Go 1.11和1.12包括对模块的初步支持,Go的新的依赖,管理系统,使依赖版本信息显式且易于管理。此博客文章介绍了开始使用模块所需的基本操作。 后续帖子将涵盖发布供其他人使用的模块。
模块是存储在文件树中的Go包的集合,其根目录中包含go.mod文件。 go.mod文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求,也是成功构建其他模块的需要,每个依赖性要求都被写为模块路径和特定版本语义。
从Go 1.11开始,go命令允许在当前目录或任何父目录具有go.mod时使用模块,如果目录在$ GOPATH / src之外。(在$ GOPATH / src中,为了兼容性,go命令仍然在旧的GOPATH模式下运行,即使找到了go.mod也是如此。 有关详细信息,请参阅go命令文档。)从Go 1.13(笔记:到目前19年5月初,依然是1.12版本,但这个point得注意,也是迫切要搞懂go module的必要性)开始,模块模式将是所有开发的默认模式。
本文将介绍使用模块开发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:
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
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文件仅出现在模块的根目录中。 子目录中的包具有导入路径,包括模块路径和子目录的路径。 例如,如果我们创建了一个子目录世界,我们就不需要(也不想)在那里运行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/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测试命令不会重复这项工作,因为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,主要版本为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文件已更新,以指定 v0.3.0。 间接注释表示此模块不直接使用依赖关系,而是仅由其他模块依赖关系间接使用。 有关详细信息,请参阅Go帮助中Module部分。
现在让我们尝试升级 rsc.io/sampler 次要版本。 通过运行go get和运行tests以相同的方式启动:
$ 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 concurrency proverb,该函数由模块 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模块的每个不同主要版本(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 这个名称。(在上一节中, 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 的使用。在大型程序或代码库中,逐步迁移的能力尤为重要。
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()
}
然后在这一点上,不再需要重命名的导入,所以我们可以撤消:
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 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
$
Conclusion结论
Go模块是Go语言中依赖管理的未来。 现在,所有支持的Go版本(即Go 1.11和Go 1.12)都提供了模块功能。
这篇文章使用Go模块介绍了这些工作流程:
- go mod init 创建一个新的模块, 初始化go.mod文件并且描述它。
- go build, go test, 与其他 package-building 命令根据需要为go.mod添加新的依赖项。
- go list -m all 打印当前模块的依赖项。
- go get 更改所需的依赖项版本(或添加新的依赖项)。
- go mod tidy 删除未使用的依赖项。
我们鼓励您开始在本地开发中使用模块,并将go.mod和go.sum文件添加到项目中。为了提供反馈并帮助塑造Go语言中依赖管理的未来,请向我们发送错误报告或体验报告。
感谢您的所有反馈和帮助改进模块。
作者:Tyler Bui-Palsulich和Eno Compton