go.mod版本管理
开心一刻
打麻将时,老板娘说快过年了想家了,特怀恋家乡的大饼卷大葱,蘸点酱就是人间美味,以前在老家她可以一口气吃三个。
两个女同事跟着附和,说胃口好身体好,什么山珍海味都比不过家乡的味道。。。
这些溜须拍马的话我真心说不出口,然后默默地。。。打出一张三筒。
老板娘:“胡!”
写在前面
现在大部分 go 项目使用 go.mod 做版本控制,虽然能做到依赖的多版本共存,但是也会碰到一些不太好理解的地方,这里对此进行一些记录,方便查阅。
go module 版本格式
go.mod使用的版本号协议是 semver (Semantic Versioning),定义的版本号格式为:
vMAJOR.MINOR.PATCH
MAJOR 主版本号,如果有大的版本更新,导致 API 和之前版本不兼容。我们遇到的就是这个问题。
MINOR 次版本号,当你做了向下兼容的新 feature。
PATCH 修订版本号,当你做了向下兼容的修复 bug fix。
v 所有版本号都是 v 开头。
在 go.mod 文件中见到的所有的依赖包均是上述格式。如果依赖的第三方包中打的 tag 不符合上面的标准或者根本没有打 tag,那么 go.mod 中会自动生成满足上述格式的伪版本号,这个时候可以查看伪版本号中的最后一个数字,最后一个数字是仓库中的 commitId,commitId 是正确的就是没问题的。
标准样式
类似下面格式,表明在 sarama 的库中打了 v1.26.4 的 tag。注意,必须是打 v1.26.4 这样的 tag,如果打的 tag 是 1.26.4、v1.26.4.2 等样式均不符合 go.mod 的标准,go.mod 中均会生成伪版本号,所以尽量用标准形式打 tag。
github.com/Shopify/sarama v1.26.4
伪版本号
tag 不符合标准或者没有 tag 就会生成伪版本号,类似下面这样
github.com/faceair/sarama v1.27.3-0.20201026102015-6f053e317d37
生成伪版本号的时候主要就看最后一个数字,最后一个数字代表提交到仓库的 commitId,可以根据这个 commitId 确定当前在使用那个版本的代码。
伪版本号具体的含义如下:v0.0.0-yyyymmddhhmmss-abcdefabcdef
yyyymmddhhmmss表示提交到仓库的时间,abcdefabcdef就表示 commitId。如果打的 tag 为 v1.27.2.3(多用了一个小数点),生产的伪版本号中就会变成示例中的样子。
间接依赖
在使用 Go module 过程中,随着引入的依赖增多,也许你会发现go.mod文件中部分依赖包后面会出现一个// indirect的标识。这个标识总是出现在require指令中,其中//与代码的行注释一样表示注释的开始,indirect表示间接的依赖。
比如开源软件 Kubernetes(v1.17.0版本)的 go.mod 文件中就有数十个依赖包被标记为indirect:
require (
github.com/Rican7/retry v0.1.0 // indirect
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 // indirect
github.com/boltdb/bolt v1.3.1 // indirect
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b // indirect
github.com/codegangsta/negroni v1.0.0 // indirect
...
)
在执行命令go mod tidy时,Go module 会自动整理go.mod 文件,如果有必要会在部分依赖包的后面增加// indirect注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加// indirect注释的包则是直接依赖的包,即明确的出现在某个import语句中。
然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod文件中。
间接依赖出现在go.mod文件的情况,可能符合下面所列场景的一种或多种:
- 直接依赖未启用 Go module
- 直接依赖go.mod 文件中缺失部分依赖
不兼容标识+incompatible
+incompatible 标识项目中引入了一个不兼容的包。产生的原因是项目中引用的第三方依赖的版本已经升级到 v2 甚至更高,但是第三方包的 module name 没有显式加上 v2 标识。正确做法应该是让第三方包的 module name 显式加上 v2 标识,然后重新打一个 v2 版本的 tag,然后在自己的项目中使用新的 tag。
产生了不兼容标识对第三方依赖包会有所影响,因为他们本身没有按照规范打 tag,这种标识会让使用方知道他们正在使用的包发生了不兼容的变更,不利于第三方包的推广。
replace 语句
go.mod 中的 replace 语句只会对当前项目产生影响,不会对其他引用该项目的代码产生影响。
项目中使用 replace 主要有以下几个用途:
- 替换成镜像包,某些包无法直接下载,用 replace 替换成镜像包后就可以下载成功,使用时仍然使用原来的 replace 前的 module name
- 想要隐藏项目中使用的包的版本,require 中将包的版本定义成v0.0.0,然后在 replace 中替换成其他版本。主要用于某些项目不想让其他项目引用的情况,k8s 的库便是如此
- 将某些包替换成其他的包。可以这样做,但是不如直接使用 replace 之后的包。
- 使用本地的其他项目,本地其他项目必须用 replace,可以用相对路径和绝对路径引用到另一个项目的根路径中即可。
上面说的四种用途其实都是在说一件事,使用 replace 之后项目中实际使用的代码其实是 replace 之后的包的代码。
用到的一些 go 命令
go mod tidy
比较常用的一个命令,该命令会自动整理依赖,可以添加依赖,删除不需要的依赖,同时还会对 go.sum 文件进行修改。手动修改go.mod 中某个包的版本,然后执行 go mod tidy 相当于执行 go get 命令
go get
用于下载或更新包,下载时不指定包的版本默认会下载最新包,需要指定版本时包名和版本号之间用『@』符号相连(中间没有空格)。指定版本时可以使用仓库中打的 tag、commitId 或者 branchName,如果 tag 不符合标准,在下载时要先使用不标准的 tag,下载完成会后自动变成伪版本号。
go mod download
在 go.mod 手动修改版本号,然后可以执行 go mod download 下载,该命令不会对包进行整理,go.sum 也不会修改,执行完后应该再执行一下 go mod tidy
go clean -modcache
如果有时候下载包一直不成功,有可能是缓存中的包冲突导致,可以使用该命令将缓存删除,然后执行 go mod tidy 重新下载所有的包。
总结
还是熟能生巧,要多踩些坑,才能更快解决碰到的问题。