Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Go】Go Modules的用法 #18

Open
yankewei opened this issue Jun 27, 2019 · 0 comments
Open

【Go】Go Modules的用法 #18

yankewei opened this issue Jun 27, 2019 · 0 comments
Labels
Go 有关Go语言的 翻译 文档翻译

Comments

@yankewei
Copy link
Owner

yankewei commented Jun 27, 2019

简介

Go 1.11 和 1.12 都对模块有了初步的支持,使得对依赖的管理更加详细和容易。这个文章对模块的使用做一个基本的介绍。
模块是对Go包一个集合,以一个文件树的形式存储在根目录的go.mod文件中。go.mod文件定义了模块的路径,还有相关的依赖项。每一个依赖项都会被认为是一个模块路径和指定的版本规则,其实就是依赖项也是一个模块。
在Go 1.11的时候,Go命令行就有了对模块的支持,当当前的目录或者父目录有go.mod文件的时候,并且可以在GOPATH以外的地方使用,为了兼容性,即使在GOPATH中发现了go.mod文件,也会使用GOPATH的方式来加载文件。从Go 1.13开始,模块将被默认支持。
本文将介绍使用模块开发Go代码时出现的一系列常见操作:

  • 创建一个模块
  • 添加一项依赖
  • 更新一项依赖
  • 添加一个新的主版本
  • 更新依赖为新的主版本
  • 删除未使用的依赖

创建一个模块

让我们来创建一个模块。
$GOPATH/src目录之外创建一个新的,空的目录,进入目录然后创建一个新文件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文件,如果我们在当前目录下执行go test,可以看到:

D:\Code\go>go test
PASS
ok      _/D_/Code/go    0.327s
D:\Code\go>

最后一行总结了整体包测试。 因为我们在$GOPATH之外以及任何模块之外工作,所以go命令不知道当前目录的导入路径,并根据目录名称构成假路径:_/D_/Code/go
现在,我们在当前目录下执行go mod init,然后再进行go test:

D:\Code\go>go mod init example.com/hello
go: creating new go.mod: module example.com/hello

D:\Code\go>go test
PASS
ok      example.com/hello       0.393s

D:\Code\go>

恭喜!你已经写了一个模块,并且通过了测试
go mod init命令生成一个go.mod文件:

PS D:\Code\go> cat .\go.mod                                                                                                                                                                   module example.com/hello                                                                                                                                                                      
go 1.12
PS D:\Code\go>

go.mod文件只会出现再模块的根目录,子目录中的包具有导入路径,包括模块路径和子目录的路径。例如,我们创建一个子目录world,我们可以不在再次执行go mod init,包会自动加载并且可以被识别出是example.com/hello的一部分,导入路径就是example.com/hello/world

添加一项依赖

Go模块的主要作用是提升可以使用其他开发人员提供的代码。
更新hello.go,导入rsc.io/quote包:

package hello

import "rsc.io/quote"

func Hello() string {
	return quote.Hello()
}

再一次执行go test

D:\Code\go>go test
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
--- FAIL: TestHello (0.00s)
    hello_test.go:10: Hello() = "Hello, world.", want "Hello, world"
FAIL
exit status 1
FAIL    example.com/hello       0.431s

D:\Code\go>

go命令会解析go.mod文件中指定的依赖和版本。当遇见一个在go.mod中没有提供的包时,go命令会自动查找这个包并且添加到go.mod文件中,使用最新的版本(最新的版本指的时被打标签的稳定版,或者最新的预发布版本,或者最新的版本),在我们这个例子中,go test解析了一个新的包rsc.io/quote,版本为v1.5.2。还下载了两个包rsc.io/quote使用的两个依赖项,就是rsc.io/samplergolang.org/x/testgo.mod文件指记录直接的依赖关系:

D:\Code\go>type go.mod
module example.com/hello

go 1.12

require rsc.io/quote v1.5.2

D:\Code\go>

再一次执行go test的时候,就不会重复上一次的任务了,因为go.mod现在已经更新了,并且依赖已经被缓存到了本地($GOPATH/pkg/mod)
正如我们看到的,添加一个依赖经常会带来其他的依赖,go list -m命令可以列出当前模块的所有依赖

yankeweideMacBook-Pro:hello yankewei$ go list -m all
hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
yankeweideMacBook-Pro:hello yankewei$ 

在输出的内容中,当前的模块名总是会出现在第一行,紧跟着按依赖的模块路径排序。
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c是一个伪版本号,这个go系统对于一个没有打标的commit的标识。
除了go.mod之外,go命令还维护一个名为go.sum的文件,其中包含特定模块版本内容的预期加密哈希:

yankeweideMacBook-Pro:hello yankewei$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
yankeweideMacBook-Pro:hello yankewei$ 

go使用go.sum文件来确保未来下载这些模块的时候保证和第一次下载的一样,也确保你项目的依赖不会改变,所以go.mod和go.sum都应该纳入版本控制中。

更新依赖

在Go模块中,版本使用语义化的标记来表示的。一个语义化的版本有三个部分:主版本,次要版本,修补版本。例如:对于 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/test包已经被更新到了最新的版本(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
$

糟糕!测试失败了,最新的版本和我们要使用的不兼容,先看一下这个模块的可用版本:

$ 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/[email protected]
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。默认是@latest,也就是最新的版本

添加一个新的主版本

我们添加一个新的函数func Proverb, 函数会调用rsc.io/quote/v3的模块,在这个模块中可以调用quote.Concurrency,返回一个字符串,首次我们先来更新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
每个不同的主版本(v1,v2等等)的模块使用的是不同的路径,这样就可以选择性的使用,并且在版本迁移的时候也可以逐步的进行。

更新依赖为新的主版本

现在我们想要把rsc.io/quote更新到rsc.io/quote/v3。因为主版本号已经变了,我们可能会意识到某些api被移除,被重命名或者调用方法发生了改变,所以首先我们需要先看一下文档:

$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote/v3"

Package quote collects pithy sayings.

func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string**

可以看到原来的 Hello 改为了 HelloV3,那我们就可以把使用的 Hello 改为 HelloV3,并且文件中没有对旧版本的依赖了,可以把导入重命名去掉:

package hello

import (
	"rsc.io/quote/v3"
)

func Hello() string {
	return quote.HelloV3()
}

func Proverb() string {
	return quote.Concurrency()
}

可以自行测试,就不演示了。

删除未使用的依赖

在Go语言中删除未使用的依赖相当的简单,只需要执行go mod tidy

原文链接

@yankewei yankewei added the 翻译 文档翻译 label Jun 27, 2019
@yankewei yankewei added the Go 有关Go语言的 label Aug 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Go 有关Go语言的 翻译 文档翻译
Projects
None yet
Development

No branches or pull requests

1 participant