04
升级依赖项
使用 Go modules,版本使用语义版本标记进行引用。语义版本由三个部分组成:主要版本、次要版本和修补程序版本。例如,对于 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。注释「indirect」指示依赖项不直接由此模块使用,仅由其他模块依赖项间接使用。
现在,让我们尝试升级 rsc.io/sampler 版本。通过运行 go get 和运行 go test:
$ 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 $
go test 运行失败表明最新版本的 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 $$ go list -m -versions rsc.io/samplerrsc.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 指定 Module 版本。通常,传递给 go get 的每个参数都可以获取显式版本,默认值为@latest,解析为前面定义的最新版本。
05
添加对新的主版本的依赖
让我们在我们的包中添加一个新函数:func Proverb 返回 Go 并发原语,通过调用 quote.Concurrency,
由模块 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 modules 的每个不同主要版本(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。我们暂且通过起别名的方式使用。在大型程序或代码库中,增量迁移的能力尤为重要。
06
将依赖项升级到新的主版本
让我们完成从同时使用 rsc.io/quote 和 rsc.io/quote/v3
到仅使用 rsc.io/quote/v3 的依赖项升级。由于主要版本更改,我们预期某些 API 可能已被删除、重命名或以其他方式以不兼容的方式更改。
运行 go doc rsc.io/quote/v3 命令,
阅读文档, 我们可以看到, 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() }
现在仅使用依赖项 rsc.io/quote 的一个版本,不再需要给导入的依赖项定义别名 quoteV3,因此我们可以修改为:
package hello import "rsc.io/quote/v3" func Hello() string { return quote.HelloV3() } func Proverb() string { return quote.Concurrency() }
让我们重新运行 go test,以确保一切正常运行:
$ go test PASS ok example.com/hello 0.014s
07
删除未使用的依赖项
我们已经删除了我们使用的依赖项 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 $
08
总结
Go modules 是 Go 未来版本中使用的依赖项管理系统。模块功能现在在所有支持的 Go 版本中可用(即从 Go 1.11 起的版本)。
本文介绍了 Go modules 这些基本操作:
- go mod init 创建一个新模块, 并初始化描述它的 go.mod 文件。
- go build、go test 和其他包构建命令根据 go.mod 文件需要添加新的依赖项。
- go list -m all 打印当前模块的所有依赖项列表。
- go get 更新依赖项所需的版本(或添加新的依赖项)。
- go mod tidy 删除未使用的依赖项。
建议大家开始在本地开发中使用 module,并将 go.mod 和 go.sum 文件添加到项目中的版本控制。