go-module的使用

Go 1.11和1.12已经基本上支持Go Modules,Go的新依赖管理系统,更好的管理依赖的版本信息。

本文使用版本Go1.13.
Go Modules之前,每个项目都必须设置一个GOPATH,并将第三方包安装在各自的目录下,多个项目之间不能共用第三方包。

1
2
3
4
5
6
7
8
9
10
11
12
GO111MODULE
GO111MODULE 有三个值:off, on和auto(默认值)。

GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找
GO111MODULE=on,go命令行会使用module功能
GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能

如下两种情况会启用module功能:
- 当前目录在GOPATH/src之外且该目录包含go.mod文件
- 当前文件在包含go.mod文件的目录下面。

当module功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的 module。

新建一个module

$GOPATH/src目录之外新建一个目录hello,并新建一个文件server.go
整个目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  gopro mkdir hello
➜ gopro cd hello
➜ hello vim hello.go
➜ hello cat hello.go
package hello

func Hello() string {
return "Hello, world."
}

➜ hello cat 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)
}
}

到目前为止这个目录包含package,但不是一个module,因为不包含go.mod文件。
执行go test,将会提示如下错误(使用的go版本为1.13)

1
2
➜  hello go test
go: cannot find main module; see 'go help modules'

使用go mod init把目录转换成为一个module

1
2
3
4
5
6
7
8
9
➜  hello go mod init example.com/hello
go: creating new go.mod: module example.com/hello
➜ hello go test
PASS
ok example.com/hello 0.002s
➜ hello cat go.mod
module example.com/hello //定义了 module path,是导入路径的根目录

go 1.13

go.mod文件一般只出现于module的根目录。如果你在module下面新建了一个子目录world,world会自动被认为是example.com/hello的一部分,导入路径为example.com/hello/world。(go.mod所在的目录被认为是一个module的根目录,导入路径为example.com/hello加入对应的package名称)

添加依赖

1
2
3
4
5
6
7
8
➜  hello cat hello.go
package hello

import "rsc.io/quote"

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

执行go test命令,该命令会自动下载所需要的包。并将所需的依赖写入到go.mod

1
2
3
4
5
6
7
8
9
10
11
12
➜  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: 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
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok example.com/hello 0.003s

1
2
3
4
5
6
➜  hello cat go.mod 
module example.com/hello

go 1.13

require rsc.io/quote v1.5.2

go命令还维护了一个文件go.sum,里面定义了go.mod包中所需的依赖项。

1
2
3
4
5
6
7
➜  hello 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=

更新依赖

1
2
3
4
5
6
7
➜  hello go get golang.org/x/text
go: finding golang.org/x/text v0.3.3
go: downloading golang.org/x/text v0.3.3
go: extracting golang.org/x/text v0.3.3
➜ hello go test
PASS
ok example.com/hello 0.003s

此时查看依赖项,golang.org/x/text已经被升级到v0.3.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  hello go list -m all
go: finding golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
example.com/hello
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
➜ hello cat go.mod
module example.com/hello

go 1.13

require (
golang.org/x/text v0.3.3 // indirect
rsc.io/quote v1.5.2
)

尝试升级rsc.io/sampler

1
2
3
4
5
6
7
8
9
10
➜  hello 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
➜ hello 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.002s

rsc.io/sampler包是更新成功了,但go test命令执行失败。
查看rsc.io/sampler版本信息

1
2
➜  hello 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

导致出错的原因可能是,rsc.io/sampler@v1.99.99与原来版本的包不兼容所致。我们重新安装
rsc.io/sampler@v1.3.1然后再进行测试。

1
2
3
4
5
6
7
➜  hello 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
➜ hello go test
PASS
ok example.com/hello 0.003s

添加对新主版本的依赖

修改hello.go的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  hello cat 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的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}
}

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命令

1
2
3
4
5
6
➜  hello 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.003s

查看rsc.io/quote,发现存在不同版本的两个包。

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

升级新主版本的依赖

因为一些原因我们需要将quote升级到V3版本。
修改hello.go的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  hello cat hello.go
package hello

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

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

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

执行go test命令

1
2
3
➜  hello go test
PASS
ok example.com/hello 0.003s

移除没有使用的依赖

在上一步中,我们的代码已经没有使用rsc.io/quote/v1版本,但rsc.io/quote v1.5.2还是存在于依赖中。

1
2
3
4
5
6
7
➜  hello go list -m all
example.com/hello
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1

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

使用go mod tidy命令清除未使用的依赖。

1
2
3
4
5
6
7
➜  hello go mod tidy
➜ hello go list -m all
example.com/hello
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1


go mod相关命令:

COMMAND DESC
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed

Ref:
1.https://blog.golang.org/using-go-modules
2.https://juejin.im/post/5c8e503a6fb9a070d878184a