go.mod版本管理

发布时间 2023-04-13 15:41:56作者: 星宇x

开心一刻

       打麻将时,老板娘说快过年了想家了,特怀恋家乡的大饼卷大葱,蘸点酱就是人间美味,以前在老家她可以一口气吃三个。
       两个女同事跟着附和,说胃口好身体好,什么山珍海味都比不过家乡的味道。。。
       这些溜须拍马的话我真心说不出口,然后默默地。。。打出一张三筒。
       老板娘:“胡!”

写在前面

       现在大部分 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 主要有以下几个用途:

  1. 替换成镜像包,某些包无法直接下载,用 replace 替换成镜像包后就可以下载成功,使用时仍然使用原来的 replace 前的 module name
  2. 想要隐藏项目中使用的包的版本,require 中将包的版本定义成v0.0.0,然后在 replace 中替换成其他版本。主要用于某些项目不想让其他项目引用的情况,k8s 的库便是如此
  3. 将某些包替换成其他的包。可以这样做,但是不如直接使用 replace 之后的包。
  4. 使用本地的其他项目,本地其他项目必须用 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 重新下载所有的包。

总结

       还是熟能生巧,要多踩些坑,才能更快解决碰到的问题。

参考