背景介绍
在Go 1.11版本开始,go支持go modules-(go官方的依赖管理系统),使得依赖版本信息的管理更加简单方便。同时官方给了对应blog,使用户能够快速上手go mod,blog链接可参考Using Go Modules。
在go 1.11中,使用go mod的modules的路径需要在非GOPATH/src路径下,则go会使用老的方式,即使存在go.mod文件)。而在Go 1.13中,go mod将成为默认方式。
快速上手
创建一个新的module
在非$GOPATH/src路径下,创建一个hello目录,并在目录中定义hello.go文件
package hello
func Hello() string {
return "Hello, world."
}
同时定义对应的单测文件。
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)
}
}
定义当前路径为一个module,使用go mod init,在当前路径下会生成一个go.mod文件。
$ export GO111MODULE=on
$ go mod init github.com/hello
go: creating new go.mod: module github.com/hello
执行go test,发现打印即为刚定义的module,github.com/hello。
$ go test
PASS
ok github.com/hello 0.675s
查看go.mod文件,发现go.mod文件中定义module的name和当前的go版本。
$ cat go.mod
module github.com/hello
go 1.13
添加依赖
更新hello.go,并导入包rsc.io/quote。
package hello
import "rsc.io/quote"
func Hello() string {
return quote.Hello()
}
再次运行go test命令
$ 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 github.com/hello 0.718s
go命令通过go.mod中定义的module版本来解析导入的包。当遇到导入的包未定义在go.mod文件中,go会自动将这个module的latest版本导入go.mod文件中。查看go.mod文件可以发现,已经导入了包rsc.io/quote v1.5.2。
module github.com/hello
go 1.13
require rsc.io/quote v1.5.2
从上面的命令来看,添加一个依赖的同时经常会引入另一个依赖,go list -m all命令列出了当前的module以及对应所有依赖。可以看到,在 go list的输出中,当前的module(github.com/hello是第一行)
$ go list -m all
github.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
在目录下除了go.mod文件,还会生成一个go.sum文件,go.sum文件中包含了特定module版本的加密哈希值,用来确保在以后的下载过程中下载的文件内容和第一次是一致的,也就是保证版本的一致性,因此go.mod和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=
更新依赖
在Go module中,版本可分为三个部分,分别为major,minor和patch。举个例子,对于版本v0.1.2,major version是0,minor version是1,patch version是2.
从go list -m all中看出,当前使用的包golang.org/x/test没有对应的tag,因此将这个包升级到最新版
$ go list -m all
github.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.1
执行go get升级包,可以发现包已经升级到最新版。
$go get golang.org/x/test
$ go list -m all
github.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.1
查看go.sum文件,可以看到当前使用的包就是最新的v0.3.2版本。
$ cat go.mod
module github.com/hello
go 1.13
require (
golang.org/x/text v0.3.2 // indirect
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.1 // 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 github.com/hello 0.633s
从结果中看到最新的包rsc.io/sampler并不兼容当前的用法,使用go list查看可用的版本。从上面的go.mod文件中可以看到,当前使用的版本是v1.3.1,而1.99.99这个版本并不适合。因此,使用版本v1.3.1来代替
$ 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 get rsc.io/sampler@v1.3.1
添加一个新的major版本的依赖
修改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()
}
并添加新的单测
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
测试新的代码,并打印当前的module
$ 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 github.com/hello 0.463s
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
更新新的major版本依赖
在本节中,将当前的包从rsc.io/quote转换到rsc.io.quote/v3版本,因为当前的主版本改变了,因此当前包中的一些APIs也会被修改。通过go doc命令,发现Hello()已经变成了HelloV3()
$ 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.go文件,不需要对包进行重命名,可以直接引用包。
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 github.com/hello 1.161s
移除未使用的依赖
在上面的例子中,已经移除了包rsc.io/quote,通过go list和go.mod文件,可以发现这个包并没有在依赖关系中删除。
$ go list -m all
github.com/hello
golang.org/x/text v0.3.2
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
$ cat go.mod
module github.com/hello
go 1.13
require (
golang.org/x/text v0.3.2 // indirect
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1 // indirect
)
从结果中看出,虽然在代码中已经移除了对于包rsc.io/quote的依赖,但是在go list和go.mod文件中依然保留。go build和go test可以用来添加缺失的依赖,但并不能用于安全地移除未使用的包。
命令go mod tidy可以用来清除这些未使用的依赖,从结果中可以看到包rsc.io/quote已经被移除。
$ go mod tidy
$ go list -m all
github.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module github.com/hello
go 1.13
require (
golang.org/x/text v0.3.2 // indirect
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1 // indirect
)
结论
从目前来看,go modules将会是go未来的版本依赖管理趋势。
- go mod init,用来创建一个新的module,并初始化go.mod文件。
- go build,go test用来添加新的依赖到go.mod文件中。
- go list -m all打印当前module的依赖。
- go get 改变当前依赖的版本或添加一个新的依赖。
- go mod tidy用来移除未使用的依赖。
对于go mod的具体用法可以,go mod的输出:
Usage:
go mod <command> [arguments]
The commands are:
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
- download 用来下载依赖的module到$GOPATH/pkg/mod路径下
- edit 用来编译go.mod文件
- graph 用来打印当前module的依赖图
- init 初始化一个新的module并生成go.mod。
- tidy 添加丢失的module,并移除未使用的module
- vendor 将依赖复制到vendor下
- verify 验证依赖
- why 解释为什么该包或module被需要