使用GoModule(Using Go Modules译文)
Posted -_-void
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用GoModule(Using Go Modules译文)相关的知识,希望对你有一定的参考价值。
使用GoModule
英文原版:https://blog.golang.org/using-go-modules
介绍
这是系列文章的第一部分
- 1-使用GoModule(当前)
- 2-迁移到GoModule
- 3-发布GoModule
- 4-GoModule: v2及新版本
Go1.11和Go1.12包含了初步的GoModule支持,这是一种新的依赖管理系统,它能够更简单精确地管理依赖包的版本信息。本文将介绍使用GoModule所需要的基本操作。
一个module是一个存储在go.mod
文件中的GoPackage的集合,go.mod
文件定义了这个module的module path(此module被import的根路径)及其所需依赖(其他被此module依赖的module)。每个依赖都以semantic version
的形式作为一条module path
写入go.mod
。
在Go1.11时,在$GOPATH/src
之外的文件夹或任意父级路径包含go.mod
文件时,可以在go命令中使用GoModule。(考虑到兼容性,$GOPATH/src
下的文件夹即使存在go.mod
文件,也以GOPATH模式运行。)从Go1.13开始module模式会成为默认开发模式。
本文主要内容如下:
- 新建module
- 添加依赖
- 升级依赖
- 添加新major版本的依赖
- 升级依赖到新major版本
- 移除未使用的依赖
新建module
在$GOPATH/src
之外新建一个文件夹,并在文件夹中创建hello.go
:
package hello
func Hello() string
return "Hello, world."
然后再写个hello_test.go
:
package hello
import "testing"
func TestHello(t *testing.T)
wang := "Hello, world."
if got := Hello(); got != want
t.Errorf("Hello() = %q, want %q", got, want)
现在这个package还不是module,因为没有go.mod
文件,假设当前是在/home/gopher/hello
下,运行go test
会看到:
$ go test
PASS
ok _/home/gopher/hello 0.020s
$
最后一行是全部package的test总结。由于当前既不是在$GOPATH
下也不是module模式,而且go命令发现没有用到import,因此当前路径就成了一个“fake”的GOPATH。
现在我们执行go mod init
来启用module模式并把当前路径做成module的根路径,然后再执行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
$
现在我们就完成了新建module,go mod init
命令生成了一个go.mod
文件:
$ cat go.mod
module example.com/hello
go 1.12
$
go.mod
文件只会出现在module的根目录,子目录的import path
由module path
及子目录的相对路径组成。假设存在子目录world
,那么这里就不需要执行go mod init
了,这个package会被自动认为是example.com/hello
的一部分,其导包路径为example.com/hello/world
。
添加依赖
GoModule的主要目的就是提高使用他人代码(即添加依赖)的体验。现在在上面的例子中添加一个import:
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: 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
中没有的import,则自动把它的最新版本放进去。(最新版本指的是最新的稳定版或者最新的tag或commit。)本例中go test
解析到新的import——rsc.io/quote
的最新版本为v1.5.2
,以及它用到的两个依赖——rsc.io/sampler
及golang.org/x/text
,只有直接依赖会记录到go.mod
:
$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote v1.5.2
$
在go.mod
up-to-date的情况下再次执行go test
将不会重复此操作,下载过的module会被缓存到$GOPATH/pkg/mod
中。
注意虽然go命令方便快捷,但不意味到此结束。现在module严格依赖go.mod
中所给出的指定名称、版本。
如上所示,添加一个直接依赖通常会带来其他间接依赖,执行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
$
可以看出当前module作为main module
总是在第一行出现,然后是按module的path排序的依赖包。
依赖包golang.org/x/text
的版本v0.0.0-20170915032832-14c0d48ead0c
是pseudo-version
的一种,这也是对于没有tag情况下的版本语法。
另外go.sum
会作为go.mod
的附属,包含各个module版本的cryptographic hashes
:
$ 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
来确保将来下载的module跟首次下载有相同的校验和,依次确保项目所以来的module未被篡改。go.mod
与go.sum
都应该被包含进版本控制。
升级依赖
在GoModule中,版本通过semantic版本标签来提现。semantic版本标签包含三个部分:major、minor、patch,比如v0.1.2的major是0,minor是1,patch是2。下面看下minor版本升级,下一部分看major版本升级。
从go lit -m all
的输出中可以看到,当前使用的golang.gor/x/text
是没有tag的版本,现在更新到最新tag的版本然后试试是否正常工作:
$ 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/text
已经升级到了最新tag版本(v0.3.0),并且go.mod
也同步更新。这里的indirect
注释表示这个依赖并没有被当前module直接使用,只被其他依赖所依赖。可以通过go help modules
查看详细信息。
现在将rsc.io/sampler
升级一下minor版本,同样执行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 example.com/hello 0.014s
$
报错了,说明新版本的rsc.io/sampler
并不兼容当前使用,来看下这个依赖包的可用tag版本列表:
$ 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
表示最新版本。
添加新主版本的依赖
现在在保重添加一个洗呢func:func Proverb
来返回一个go并发谚语,通过调用rsc.io/quote/v3
中的quote.Concurrency
来实现。首先在hello.go
中添加func:
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 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/quote
和rsc.io/quote/v3
两个module:
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$
一个module的每个不同的major版本都有不同的导包路径:从v2开始,导包路径必须以major版本结尾。例如v3
版本的rsc.io/quote
导包路径就是rsc.io/quote/v3
。这种习惯称为semantic import versioning
,同时它使不同major版本间的不兼容依赖包具有不同的名字。因此,rsc.io/quote
的v1.6.0
与v1.5.2
两个版本复用了rsc.io/quote
。(前面的例子中,rsc.io/sampler v1.99.99
应该是向后兼容rsc.io/sampler v1.3.0
的,但是bug或其他错误可能会导致这种情况。)
go命令允许在一次构建中每个独立的导包路径包含最多一个版本,也就是说最多存在每个major版本中的一个版本:一个rsc.io/quote
,一个rsc.io/quote/v2
,一个rsc.io/quote/v3
等。这相当于告诉module的作者一个明确的规则:对于一个module来说,不允许同时依赖rsc.io/quote v1.5.2
与rsc.io/quote v1.6.0
,但是允许依赖多个不同的major版本,因为他们的导包路径不同。这使得module的使用者可以增量升级到新的major版本。本例中既需要rsc/quote/v3 v3.1.0
中的quote.Concurrency
又需要继续使用rsc.io/quote v1.5.2
,在比较大的项目中这种增量升级的能力特别重要。
升级依赖到新主版本
现在来完成从rsc.io/quote
到rsc.io/quote/v3
的升级,由于major版本变了,我们需要预料到有些API可能有不兼容变更,看过依赖包的doc之后,可以看到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
$
(这里有个已知的bug,所显示的import中丢失了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
移除未使用的依赖
现在已经移除了所有对rsc.io/quote
的使用,但是它还在go.mod
中,并且执行go list -m all
也能看到:
$ 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 build
或go test
这样构建单个包时可以很容易判断缺了什么需要加什么,但是不能确定什么可以被安全移除。仅当检查完module中的所有包并且这些包都整合之后才能移除未使用的依赖。普通的构建命令不会加载此信息,因此它不能安全地删除依赖。
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
$
总结
GoModule是go依赖管理的未来,moduel模式在所有已支持的go版本(go1.11与go1.12)都可以使用。
本文介绍了使用GoModule的以下工作流:
go mod init
创建新的module,同时初始化go.mod
build``go test
及其他构建命令会按需将依赖添加到go.mod
go list -m all
查看当前module的依赖go get
变更依赖的指定版本(或者添加新的依赖)go mod tidy
移除未使用的依赖
我们鼓励开发者在开发中开始使用GoModule并在项目中添加go.mod
及go.sum
。
以上是关于使用GoModule(Using Go Modules译文)的主要内容,如果未能解决你的问题,请参考以下文章
发布GoModule(Publishing Go Modules译文)