【翻译】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

相关文章
相关标签/搜索