深入Go Module之未说的秘密

正常情况下,我们的go.mod依赖库的版本都是符合语义化版本 2.0.0的版本格式,或者伪版本格式。在前面的文章我没有特别提到一点的事,Go使用服务端提交的日期和commit id生成的伪版本号是符合语义化版本号2.0.0的,因为语义化版本号中规定pre-release以连接号-加一连串以逗号分隔的标识符组成,标识符以字母数字和连接号组成,所以你看到-yyyyMMddhhmmss-comitid包含两个连接号,这是正常的。

go要求依赖库要么不包含go.mod,要么依赖库中的go.mod定义的依赖库版本必须以语义化版本 2.0.0格式(或伪版本号)标志(其实更严格,除了+incompatible不能加meta字段),因为这样我们你能够明确标识某个依赖库确切的版本,这样的版本号被称之为canonical version

其实main module还可以定义non-canonical version,通过go get或者go mod tidy更新go.mod的时候,命令会尝试更新go.mod,尝试把non-canonical version转变为canonical version版本。

但是,到底有哪些non-canonical version呢?我还没看到官方文章介绍,本文尝试整理这些non-canonical version。

只定义major或者major.minor

你可以不指定minor.patch或者patch,而是让go命令尝试去寻找最大的minor和patch,所以你可以在go.mod只定义vmajor或者vmajor.minor:

1
2
3
github.com/panicthis/B v1.2
github.com/panicthis/C v1
github.com/panicthis/G/v2 v2

运行go mod tidy它们会被转换成

1
2
3
github.com/panicthis/B v1.2.1
github.com/panicthis/C v1.4.0
github.com/panicthis/G/v2 v2.0.0

go get命令也一样,你也可以直接指定major,忽略minor和patch, 比如

1
2
3
go get github.com/panicthis/B@v1.2
go get github.com/panicthis/C@v1
go get github.com/panicthis/G/v2@v2

latest, upgrade 和 patch

有三个单词有特别的语义

  • latest: 选择最高的release版本,如果没有release版本,则选择最高的pre-release版本,如果根本就没有打过tag,则选择最高的伪版本号的版本(默认分支的最后的提交版本)
  • upgrade: 类似latest,但是如果有比release更高的版本(比如pre-release),会选择更高的版本
  • patch: major和minor和当前的版本相同,只把patch升级到最高。当然如果没有当前的版本,则无从比较,则patch退化成latest语义

比如下面的格式:

1
2
3
github.com/panicthis/D latest
github.com/panicthis/E upgrade
github.com/panicthis/F patch

使用go get命令也一样

1
2
3
4
go get github.com/panicthis/D@latest
go get github.com/panicthis/E@upgrade
go get github.com/panicthis/F@patch //因为没有本地版本,所以此命令在go 1.16下可能出错
go get -u=patch github.com/panicthis/F@v1.1.0

指定特定的commit id

因为有时候proxy有缓存时间或者更新周期,如果你提交了一个新的commit,或者新打了一个tag,通过 latest不一定能拉取到最新的提交,这个时候你可以通过指定commit id的方式拉取。或者你就想测试某个特定的版本。

1
github.com/panicthis/H 4f7657a

或者

1
go get github.com/panicthis/H@4f7657a

甚至,你可以使用HEAD,作为你最新的commit id:

1
github.com/panicthis/H HEAD

或者

1
go get github.com/panicthis/H@HEAD

特定的分支

你还可以拉取特定的分支

1
2
github.com/panicthis/J master
github.com/panicthis/K feat-123

或者

1
2
go get github.com/panicthis/J@master
go get github.com/panicthis/K@feat-123

更有甚者,你可以使用>>=<<=比较符,选取某个符合条件的最大的版本。

比如:

1
2
3
4
github.com/stretchr/testify >v1.7.0
github.com/panicthis/F >=v1.1.0
github.com/panicthis/F <v1.1.0
github.com/panicthis/F <=v1.1.0

运行go mod tidy它会转换成canonical version。

或者(注意命令行中需要使用转义符):

1
2
3
4
go get github.com/stretchr/testify@\>v1.7.0
go get -u=patch github.com/panicthis/F@\>=v1.1.0
go get -u=patch github.com/panicthis/F@\<v1.1.0
go get -u=patch github.com/panicthis/F@\<=v1.1.0

虽然我们可以在go.mod中使用non-canonical version,但是在提交和发布的时候,我们需要使用go mod tidy把它们转换成canonical version,让依赖库的版本对应一个确定的版本,否则masterHEAD在不同的人使用的时候可能会对应不同的版本。

none

还有一个特殊的字符可以作为non-canonical version,比如

1
go get github.com/stretchr/testify@none

它会从go module中移出这个依赖。

tip

有人提议支持go从开发分支上拉取最新的版本。 有几个单次可以候选,但是感觉从 hg/mercurial中借鉴来的tip很合适。go最新的开发版本也叫做tip

这只是一个提议,未必最终支持。 #42545