[关闭]
@zhongdao 2020-07-17T10:01:45.000000Z 字数 21672 阅读 1111

Using Go Modules 翻译版

未分类


Using Go Modules

Tyler Bui-Palsulich and Eno Compton
19 March 2019

Introduction

This post is part 1 in a series.

Go 1.11 and 1.12 include preliminary support for modules, Go’s new dependency management system that makes dependency version information explicit and easier to manage. This blog post is an introduction to the basic operations needed to get started using modules.

Go 1.11和1.12包括对模块的初步 支持,Go的 新依赖管理系统 使依赖版本信息明确且易于管理。这篇博客文章介绍了开始使用模块所需的基本操作。

A module is a collection of Go packages stored in a file tree with a go.mod file at its root. The go.mod file defines the module’s module path, which is also the import path used for the root directory, and its dependency requirements, which are the other modules needed for a successful build. Each dependency requirement is written as a module path and a specific semantic version.

模块是 存储在文件树中且根目录为文件的Go软件包的集合 go.mod。该go.mod文件定义了模块的模块路径(它也是用于根目录的导入路径)及其依赖项要求(它们是成功构建所需的其他模块)。每个依赖项要求都写为模块路径和特定的 语义版本

As of Go 1.11, the go command enables the use of modules when the current directory or any parent directory has a go.mod, provided the directory is outside $GOPATH/src. (Inside $GOPATH/src, for compatibility, the go command still runs in the old GOPATH mode, even if a go.mod is found. See the go command documentation for details.) Starting in Go 1.13, module mode will be the default for all development.

从Go 1.11开始,如果当前目录或任何父目录具有,则go命令启用模块的使用go.mod,前提是该目录位于外部 $GOPATH/src。(在内部$GOPATH/src,出于兼容性考虑,即使go.mod找到了a,go命令仍然以旧的GOPATH模式运行。有关 详细信息,请参见 go命令文档。)从Go 1.13开始,模块模式将是所有开发的默认模式。

This post walks through a sequence of common operations that arise when developing Go code with modules:

本文介绍了使用模块开发Go代码时出现的一系列常见操作:

Creating a new module

Let's create a new module.

Create a new, empty directory somewhere outside $GOPATH/src, cd into that directory, and then create a new source file, hello.go:

  1. package hello
  2. func Hello() string {
  3. return "Hello, world."
  4. }

Let's write a test, too, in hello_test.go:

  1. package hello
  2. import "testing"
  3. func TestHello(t *testing.T) {
  4. want := "Hello, world."
  5. if got := Hello(); got != want {
  6. t.Errorf("Hello() = %q, want %q", got, want)
  7. }
  8. }

At this point, the directory contains a package, but not a module, because there is no go.mod file. If we were working in /home/gopher/hello and ran go test now, we'd see:

  1. $ go test
  2. PASS
  3. ok _/home/gopher/hello 0.020s
  4. $

The last line summarizes the overall package test. Because we are working outside $GOPATH and also outside any module, the go command knows no import path for the current directory and makes up a fake one based on the directory name: _/home/gopher/hello.

Let's make the current directory the root of a module by using go mod init and then try go test again:

  1. $ go mod init example.com/hello
  2. go: creating new go.mod: module example.com/hello
  3. $ go test
  4. PASS
  5. ok example.com/hello 0.020s
  6. $

Congratulations! You’ve written and tested your first module.

The go mod init command wrote a go.mod file:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. $

The go.mod file only appears in the root of the module. Packages in subdirectories have import paths consisting of the module path plus the path to the subdirectory. For example, if we created a subdirectory world, we would not need to (nor want to) run go mod init there. The package would automatically be recognized as part of the example.com/hello module, with import path example.com/hello/world.

Adding a dependency

The primary motivation for Go modules was to improve the experience of using (that is, adding a dependency on) code written by other developers.

Let's update our hello.go to import rsc.io/quote and use it to implement Hello:

  1. package hello
  2. import "rsc.io/quote"
  3. func Hello() string {
  4. return quote.Hello()
  5. }

Now let’s run the test again:

  1. $ go test
  2. go: finding rsc.io/quote v1.5.2
  3. go: downloading rsc.io/quote v1.5.2
  4. go: extracting rsc.io/quote v1.5.2
  5. go: finding rsc.io/sampler v1.3.0
  6. go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  7. go: downloading rsc.io/sampler v1.3.0
  8. go: extracting rsc.io/sampler v1.3.0
  9. go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  10. go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  11. PASS
  12. ok example.com/hello 0.023s
  13. $

The go command resolves imports by using the specific dependency module versions listed in go.mod. When it encounters an import of a package not provided by any module in go.mod, the go command automatically looks up the module containing that package and adds it to go.mod, using the latest version. (“Latest” is defined as the latest tagged stable (non-prerelease) version, or else the latest tagged prerelease version, or else the latest untagged version.) In our example, go test resolved the new import rsc.io/quote to the module rsc.io/quote v1.5.2. It also downloaded two dependencies used by rsc.io/quote, namely rsc.io/sampler and golang.org/x/text. Only direct dependencies are recorded in the go.mod file:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. require rsc.io/quote v1.5.2
  5. $

A second go test command will not repeat this work, since the go.mod is now up-to-date and the downloaded modules are cached locally (in $GOPATH/pkg/mod):

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.020s
  4. $

Note that while the go command makes adding a new dependency quick and easy, it is not without cost. Your module now literally depends on the new dependency in critical areas such as correctness, security, and proper licensing, just to name a few. For more considerations, see Russ Cox's blog post, “Our Software Dependency Problem.”

As we saw above, adding one direct dependency often brings in other indirect dependencies too. The command go list -m all lists the current module and all its dependencies:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  4. rsc.io/quote v1.5.2
  5. rsc.io/sampler v1.3.0
  6. $

In the go list output, the current module, also known as the main module, is always the first line, followed by dependencies sorted by module path.

The golang.org/x/text version v0.0.0-20170915032832-14c0d48ead0c is an example of a pseudo-version, which is the go command's version syntax for a specific untagged commit.

In addition to go.mod, the go command maintains a file named go.sum containing the expected cryptographic hashes of the content of specific module versions:

  1. $ cat go.sum
  2. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
  3. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
  4. rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
  5. rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
  6. rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
  7. rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
  8. $

The go command uses the go.sum file to ensure that future downloads of these modules retrieve the same bits as the first download, to ensure the modules your project depends on do not change unexpectedly, whether for malicious, accidental, or other reasons. Both go.mod and go.sum should be checked into version control.

Upgrading dependencies

With Go modules, versions are referenced with semantic version tags. A semantic version has three parts: major, minor, and patch. For example, for v0.1.2, the major version is 0, the minor version is 1, and the patch version is 2. Let's walk through a couple minor version upgrades. In the next section, we’ll consider a major version upgrade.

From the output of go list -m all, we can see we're using an untagged version of golang.org/x/text. Let's upgrade to the latest tagged version and test that everything still works:

  1. $ go get golang.org/x/text
  2. go: finding golang.org/x/text v0.3.0
  3. go: downloading golang.org/x/text v0.3.0
  4. go: extracting golang.org/x/text v0.3.0
  5. $ go test
  6. PASS
  7. ok example.com/hello 0.013s
  8. $

Woohoo! Everything passes. Let's take another look at go list -m all and the go.mod file:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/sampler v1.3.0
  6. $ cat go.mod
  7. module example.com/hello
  8. go 1.12
  9. require (
  10. golang.org/x/text v0.3.0 // indirect
  11. rsc.io/quote v1.5.2
  12. )
  13. $

The golang.org/x/text package has been upgraded to the latest tagged version (v0.3.0). The go.mod file has been updated to specify v0.3.0 too. The indirect comment indicates a dependency is not used directly by this module, only indirectly by other module dependencies. See go help modules for details.

Now let's try upgrading the rsc.io/sampler minor version. Start the same way, by running go get and running tests:

  1. $ go get rsc.io/sampler
  2. go: finding rsc.io/sampler v1.99.99
  3. go: downloading rsc.io/sampler v1.99.99
  4. go: extracting rsc.io/sampler v1.99.99
  5. $ go test
  6. --- FAIL: TestHello (0.00s)
  7. hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
  8. FAIL
  9. exit status 1
  10. FAIL example.com/hello 0.014s
  11. $

Uh, oh! The test failure shows that the latest version of rsc.io/sampler is incompatible with our usage. Let's list the available tagged versions of that module:

  1. $ go list -m -versions rsc.io/sampler
  2. rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
  3. $

We had been using v1.3.0; v1.99.99 is clearly no good. Maybe we can try using v1.3.1 instead:

  1. $ go get rsc.io/sampler@v1.3.1
  2. go: finding rsc.io/sampler v1.3.1
  3. go: downloading rsc.io/sampler v1.3.1
  4. go: extracting rsc.io/sampler v1.3.1
  5. $ go test
  6. PASS
  7. ok example.com/hello 0.022s
  8. $

Note the explicit @v1.3.1 in the go get argument. In general each argument passed to go get can take an explicit version; the default is @latest, which resolves to the latest version as defined earlier.

Adding a dependency on a new major version

Let's add a new function to our package: func Proverb returns a Go concurrency proverb, by calling quote.Concurrency, which is provided by the module rsc.io/quote/v3. First we update hello.go to add the new function:

  1. package hello
  2. import (
  3. "rsc.io/quote"
  4. quoteV3 "rsc.io/quote/v3"
  5. )
  6. func Hello() string {
  7. return quote.Hello()
  8. }
  9. func Proverb() string {
  10. return quoteV3.Concurrency()
  11. }

Then we add a test to hello_test.go:

  1. func TestProverb(t *testing.T) {
  2. want := "Concurrency is not parallelism."
  3. if got := Proverb(); got != want {
  4. t.Errorf("Proverb() = %q, want %q", got, want)
  5. }
  6. }

Then we can test our code:

  1. $ go test
  2. go: finding rsc.io/quote/v3 v3.1.0
  3. go: downloading rsc.io/quote/v3 v3.1.0
  4. go: extracting rsc.io/quote/v3 v3.1.0
  5. PASS
  6. ok example.com/hello 0.024s
  7. $

Note that our module now depends on both rsc.io/quote and rsc.io/quote/v3:

  1. $ go list -m rsc.io/q...
  2. rsc.io/quote v1.5.2
  3. rsc.io/quote/v3 v3.1.0
  4. $

Each different major version (v1, v2, and so on) of a Go module uses a different module path: starting at v2, the path must end in the major version. In the example, v3 of rsc.io/quote is no longer rsc.io/quote: instead, it is identified by the module path rsc.io/quote/v3. This convention is called semantic import versioning, and it gives incompatible packages (those with different major versions) different names. In contrast, v1.6.0 of rsc.io/quote should be backwards-compatible with v1.5.2, so it reuses the name rsc.io/quote. (In the previous section, rsc.io/sampler v1.99.99 should have been backwards-compatible with rsc.io/sampler v1.3.0, but bugs or incorrect client assumptions about module behavior can both happen.)

The go command allows a build to include at most one version of any particular module path, meaning at most one of each major version: one rsc.io/quote, one rsc.io/quote/v2, one rsc.io/quote/v3, and so on. This gives module authors a clear rule about possible duplication of a single module path: it is impossible for a program to build with both rsc.io/quote v1.5.2 and rsc.io/quote v1.6.0. At the same time, allowing different major versions of a module (because they have different paths) gives module consumers the ability to upgrade to a new major version incrementally. In this example, we wanted to use quote.Concurrency from rsc/quote/v3 v3.1.0 but are not yet ready to migrate our uses of rsc.io/quote v1.5.2. The ability to migrate incrementally is especially important in a large program or codebase.

Upgrading a dependency to a new major version

Let's complete our conversion from using rsc.io/quote to using only rsc.io/quote/v3. Because of the major version change, we should expect that some APIs may have been removed, renamed, or otherwise changed in incompatible ways. Reading the docs, we can see that Hello has become HelloV3:

  1. $ go doc rsc.io/quote/v3
  2. package quote // import "rsc.io/quote"
  3. Package quote collects pithy sayings.
  4. func Concurrency() string
  5. func GlassV3() string
  6. func GoV3() string
  7. func HelloV3() string
  8. func OptV3() string
  9. $

(There is also a known bug in the output; the displayed import path has incorrectly dropped the /v3.)

We can update our use of quote.Hello() in hello.go to use quoteV3.HelloV3():

  1. package hello
  2. import quoteV3 "rsc.io/quote/v3"
  3. func Hello() string {
  4. return quoteV3.HelloV3()
  5. }
  6. func Proverb() string {
  7. return quoteV3.Concurrency()
  8. }

And then at this point, there's no need for the renamed import anymore, so we can undo that:

  1. package hello
  2. import "rsc.io/quote/v3"
  3. func Hello() string {
  4. return quote.HelloV3()
  5. }
  6. func Proverb() string {
  7. return quote.Concurrency()
  8. }

Let's re-run the tests to make sure everything is working:

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.014s

Removing unused dependencies

We've removed all our uses of rsc.io/quote, but it still shows up in go list -m all and in our go.mod file:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote v1.5.2
  13. rsc.io/quote/v3 v3.0.0
  14. rsc.io/sampler v1.3.1 // indirect
  15. )
  16. $

Why? Because building a single package, like with go build or go test, can easily tell when something is missing and needs to be added, but not when something can safely be removed. Removing a dependency can only be done after checking all packages in a module, and all possible build tag combinations for those packages. An ordinary build command does not load this information, and so it cannot safely remove dependencies.

The go mod tidy command cleans up these unused dependencies:

  1. $ go mod tidy
  2. $ go list -m all
  3. example.com/hello
  4. golang.org/x/text v0.3.0
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote/v3 v3.1.0
  13. rsc.io/sampler v1.3.1 // indirect
  14. )
  15. $ go test
  16. PASS
  17. ok example.com/hello 0.020s
  18. $

Conclusion

Go modules are the future of dependency management in Go. Module functionality is now available in all supported Go versions (that is, in Go 1.11 and Go 1.12).

This post introduced these workflows using Go modules:

We encourage you to start using modules in your local development and to add go.mod and go.sum files to your projects. To provide feedback and help shape the future of dependency management in Go, please send us bug reports or experience reports.

Thanks for all your feedback and help improving modules.

创建一个新模块

让我们创建一个新模块。

创建一个新的空目录外的地方$GOPATH/srccd到该目录,然后创建一个新的源文件,hello.go

  1. package hello
  2. func Hello() string {
  3. return "Hello, world."
  4. }

让我们也在中编写一个测试hello_test.go

  1. package hello
  2. import "testing"
  3. func TestHello(t *testing.T) {
  4. want := "Hello, world."
  5. if got := Hello(); got != want {
  6. t.Errorf("Hello() = %q, want %q", got, want)
  7. }
  8. }

此时,该目录包含一个包,但没有模块,因为没有go.mod文件。如果我们正在工作/home/gopher/hellogo test现在运行,我们将看到:

  1. $ go test
  2. PASS
  3. ok _/home/gopher/hello 0.020s
  4. $

最后一行总结了整体包装测试。由于我们$GOPATH 在任何模块的外部以及外部进行工作,因此该go命令不知道当前目录的导入路径,而是根据目录名称组成一个假目录_/home/gopher/hello

通过使用以下命令将当前目录设为模块的根目录go mod init,然后重试go test

  1. $ go mod init example.com/hello
  2. go: creating new go.mod: module example.com/hello
  3. $ go test
  4. PASS
  5. ok example.com/hello 0.020s
  6. $

恭喜你!您已经编写并测试了您的第一个模块。

go mod init命令编写了一个go.mod文件:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. $

go.mod文件仅出现在模块的根目录中。子目录中的程序包具有由模块路径和子目录路径组成的导入路径。例如,如果我们创建了一个子目录world,则无需(也不希望)在该目录中运行go mod init。该包将example.com/hello通过导入路径自动识别为模块的 一部分 example.com/hello/world

添加依赖项

Go模块的主要动机是为了改善使用其他开发人员编写的代码的体验(即添加依赖)。

让我们更新hello.go以导入rsc.io/quote 并使用它来实现Hello

  1. package hello
  2. import "rsc.io/quote"
  3. func Hello() string {
  4. return quote.Hello()
  5. }

现在,让我们再次运行测试:

  1. $ go test
  2. go: finding rsc.io/quote v1.5.2
  3. go: downloading rsc.io/quote v1.5.2
  4. go: extracting rsc.io/quote v1.5.2
  5. go: finding rsc.io/sampler v1.3.0
  6. go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  7. go: downloading rsc.io/sampler v1.3.0
  8. go: extracting rsc.io/sampler v1.3.0
  9. go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  10. go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  11. PASS
  12. ok example.com/hello 0.023s
  13. $

go命令使用中列出的特定依赖项模块版本来解析导入go.mod。当遇到某个import包中没有任何模块提供的软件包时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文件中:

  1. $ cat go.mod
  2. module example.com/hello
  3. go 1.12
  4. require rsc.io/quote v1.5.2
  5. $

第二个go test命令将不会重复这项工作,因为该命令go.mod现在是最新的,并且下载的模块已本地缓存(在中$GOPATH/pkg/mod):

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.020s
  4. $

请注意,尽管该go命令使添加新的依赖关系变得快速而简单,但这并非没有代价。你现在模块字面上依赖于如正确性,安全性和适当的许可关键领域的新的依赖,只是仅举几例。有关更多注意事项,请参阅Russ Cox的博客文章“ 我们的软件依赖问题”

正如我们在上面看到的,添加一个直接依赖项通常也会带来其他间接依赖项。该命令go list -m all列出了当前模块及其所有依赖项:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
  4. rsc.io/quote v1.5.2
  5. rsc.io/sampler v1.3.0
  6. $

go list输出中,当前模块(也称为主模块)始终是第一行,其后是依模块路径排序的依存关系。

golang.org/x/text版本v0.0.0-20170915032832-14c0d48ead0c 是的一个例子 的伪版本,它是go为特定的未加标签的提交命令的版本语法。

除了go.modgo命令还会维护一个名为的文件,go.sum其中包含特定模块版本内容的预期加密哈希

  1. $ cat go.sum
  2. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
  3. golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
  4. rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
  5. rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
  6. rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
  7. rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
  8. $

go命令使用该go.sum文件来确保这些模块的将来下载内容与第一次下载的内容相同,以确保您的项目所依赖的模块不会由于恶意,意外或其他原因而意外更改。双方go.modgo.sum应检查到版本控制。

升级依赖

对于Go模块,使用语义版本标记引用版本。语义版本包括三个部分:主要,次要和补丁。例如,对于v0.1.2,主要版本为0,次要版本为1,补丁版本为2。让我们逐步进行几个次要版本升级。在下一节中,我们将考虑进行主要版本升级。

从的输出中go list -m all,我们可以看到我们正在使用的未标记版本golang.org/x/text。让我们升级到最新的标记版本,并测试一切是否仍然有效:

  1. $ go get golang.org/x/text
  2. go: finding golang.org/x/text v0.3.0
  3. go: downloading golang.org/x/text v0.3.0
  4. go: extracting golang.org/x/text v0.3.0
  5. $ go test
  6. PASS
  7. ok example.com/hello 0.013s
  8. $

oo!一切都过去了。让我们再看看go list -m allgo.mod文件:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/sampler v1.3.0
  6. $ cat go.mod
  7. module example.com/hello
  8. go 1.12
  9. require (
  10. golang.org/x/text v0.3.0 // indirect
  11. rsc.io/quote v1.5.2
  12. )
  13. $

golang.org/x/text软件包已升级到最新的标记版本(v0.3.0)。该go.mod文件也已更新为指定v0.3.0。该indirect注释表示一个依赖不是直接由该模块由其他模块的依赖关系所使用的,只有间接的。有关go help modules详细信息,请参见。

现在,让我们尝试升级rsc.io/sampler次要版本。通过运行go get和运行测试以相同的方式开始:

  1. $ go get rsc.io/sampler
  2. go: finding rsc.io/sampler v1.99.99
  3. go: downloading rsc.io/sampler v1.99.99
  4. go: extracting rsc.io/sampler v1.99.99
  5. $ go test
  6. --- FAIL: TestHello (0.00s)
  7. hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
  8. FAIL
  9. exit status 1
  10. FAIL example.com/hello 0.014s
  11. $

哦,哦!测试失败表明的最新版本rsc.io/sampler与我们的用法不兼容。让我们列出该模块的可用标记版本:

  1. $ go list -m -versions rsc.io/sampler
  2. rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
  3. $

我们一直在使用v1.3.0;v1.99.99显然不好。也许我们可以尝试使用v1.3.1代替:

  1. $ go get rsc.io/sampler@v1.3.1
  2. go: finding rsc.io/sampler v1.3.1
  3. go: downloading rsc.io/sampler v1.3.1
  4. go: extracting rsc.io/sampler v1.3.1
  5. $ go test
  6. PASS
  7. ok example.com/hello 0.022s
  8. $

注意明确@v1.3.1go get说法。通常,传递给的每个参数都go get可以采用显式形式。默认值为@latest,它将解析为先前定义的最新版本。

添加对新主版本的依赖

让我们在包中添加一个新函数: func Proverb通过调用返回Go并发谚语quote.Concurrency,该谚语由module提供rsc.io/quote/v3。首先我们更新hello.go以添加新功能:

  1. package hello
  2. import (
  3. "rsc.io/quote"
  4. quoteV3 "rsc.io/quote/v3"
  5. )
  6. func Hello() string {
  7. return quote.Hello()
  8. }
  9. func Proverb() string {
  10. return quoteV3.Concurrency()
  11. }

然后我们将测试添加到hello_test.go

  1. func TestProverb(t *testing.T) {
  2. want := "Concurrency is not parallelism."
  3. if got := Proverb(); got != want {
  4. t.Errorf("Proverb() = %q, want %q", got, want)
  5. }
  6. }

然后我们可以测试我们的代码:

  1. $ go test
  2. go: finding rsc.io/quote/v3 v3.1.0
  3. go: downloading rsc.io/quote/v3 v3.1.0
  4. go: extracting rsc.io/quote/v3 v3.1.0
  5. PASS
  6. ok example.com/hello 0.024s
  7. $

请注意,我们的模块现在同时依赖rsc.io/quotersc.io/quote/v3

  1. $ go list -m rsc.io/q...
  2. rsc.io/quote v1.5.2
  3. rsc.io/quote/v3 v3.1.0
  4. $

Go模块的每个主要版本(v1v2等等)都使用不同的模块路径:从开始v2,该路径必须以主要版本结尾。在示例中,v3of rsc.io/quote不再rsc.io/quote:由模块path标识rsc.io/quote/v3。此约定称为 语义导入版本控制,它为不兼容的程序包(具有不同主要版本的程序包)提供不同的名称。相反,v1.6.0of rsc.io/quote应该与向后兼容v1.5.2,因此它重用了name 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。同时,允许模块的不同主要版本(因为它们具有不同的路径)使模块使用者可以逐步升级到新的主要版本。在此示例中,我们想使用quote.Concurrencyfrom,rsc/quote/v3 v3.1.0 但尚未准备好迁移我们对的使用rsc.io/quote v1.5.2。在大型程序或代码库中,增量迁移的能力尤其重要。

将依赖项升级到新的主要版本

让我们完成从使用rsc.io/quote到仅使用的转换rsc.io/quote/v3。由于版本的重大更改,我们应该期望某些API可能已经以不兼容的方式被删除,重命名或以其他方式更改。阅读文档,我们可以看到Hello已经变成HelloV3

  1. $ go doc rsc.io/quote/v3
  2. package quote // import "rsc.io/quote"
  3. Package quote collects pithy sayings.
  4. func Concurrency() string
  5. func GlassV3() string
  6. func GoV3() string
  7. func HelloV3() string
  8. func OptV3() string
  9. $

(输出中还存在一个 已知的错误;显示的导入路径错误地删除了/v3。)

我们可以更新quote.Hello()in hello.go中的用法以使用quoteV3.HelloV3()

  1. package hello
  2. import quoteV3 "rsc.io/quote/v3"
  3. func Hello() string {
  4. return quoteV3.HelloV3()
  5. }
  6. func Proverb() string {
  7. return quoteV3.Concurrency()
  8. }

然后在这一点上,不再需要重命名的导入,因此我们可以撤消该操作:

  1. package hello
  2. import "rsc.io/quote/v3"
  3. func Hello() string {
  4. return quote.HelloV3()
  5. }
  6. func Proverb() string {
  7. return quote.Concurrency()
  8. }

让我们重新运行测试以确保一切正常:

  1. $ go test
  2. PASS
  3. ok example.com/hello 0.014s

删除未使用的依赖项

我们删除了对的所有使用rsc.io/quote,但仍显示go list -m allgo.mod文件中和文件中:

  1. $ go list -m all
  2. example.com/hello
  3. golang.org/x/text v0.3.0
  4. rsc.io/quote v1.5.2
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote v1.5.2
  13. rsc.io/quote/v3 v3.0.0
  14. rsc.io/sampler v1.3.1 // indirect
  15. )
  16. $

为什么?因为构建单个软件包(例如使用go buildgo test)可以很容易地知道何时缺少某些东西和需要添加什么东西,但是不能确定何时可以安全地删除某些东西。仅在检查模块中的所有软件包以及这些软件包的所有可能的构建标记组合之后,才能删除依赖项。普通的build命令不会加载此信息,因此它不能安全地删除依赖项。

go mod tidy命令清除了这些未使用的依赖项:

  1. $ go mod tidy
  2. $ go list -m all
  3. example.com/hello
  4. golang.org/x/text v0.3.0
  5. rsc.io/quote/v3 v3.1.0
  6. rsc.io/sampler v1.3.1
  7. $ cat go.mod
  8. module example.com/hello
  9. go 1.12
  10. require (
  11. golang.org/x/text v0.3.0 // indirect
  12. rsc.io/quote/v3 v3.1.0
  13. rsc.io/sampler v1.3.1 // indirect
  14. )
  15. $ go test
  16. PASS
  17. ok example.com/hello 0.020s
  18. $

结论

Go模块是Go中依赖管理的未来。现在,所有受支持的Go版本(即Go 1.11和Go 1.12)都提供模块功能。

这篇文章使用Go模块介绍了这些工作流程:

我们鼓励您开始在本地开发中使用模块,并向项目中添加go.modgo.sum文件。为了提供反馈并帮助塑造Go中依赖项管理的未来,请向我们发送 错误报告体验报告

感谢您的所有反馈,并帮助改善模块。

相关文章

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注