一。Go 包管理历程

大部分语言都有版本管理工具,比如nodejs的npm,python中的pip,java里的maven,但是go语言的版本管理经历了漫长的演进历程:

  • Go1.5以前,golang使用GOPATH方式管理代码。
    • 代码开发必须在go path src目录下。
    • 依赖包没有版本可言,都是指master最新代码。这个阶段只能手动管理依赖。

开始Golang这么设计是有原因的,因为Google是一个实践Mono Repo(把所有的相关项目都放在一个仓库中)的公司。但更多的公司和组织更多的是用Multi Repo(按模块分为多个仓库),GOPATH 至少解决了第三方源码依赖的问题,虽然它还不够完美。

go vendorgo build -mod vendor

二。GO Module的简介

在go1.12以前,我们知道golang的依赖包管理仅仅只是可用而已。go1.12之后,go mod才真正解决了依赖包管理的几个核心问题。

go 通过go mod 命令来管理依赖包。其中提供了以下几个命令,一般我用的比较多的是go mod tidy

Go module最重要的是go.mod文件的定义,它用来标记一个module和它的依赖库以及依赖库的版本。作用有点类似于Maven里面的pom.xml文件。

Go module遵循语义化版本规范 2.0.0。语义化版本规范 2.0.0规定了版本号的格式,版本格式定义为:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

下面我们来看一下go.mod的文件到底长啥样:

go.mod下还会有一个go.sum文件,其中包含特定模块版本内容的预期加密哈希,go命令使用go.sum文件确保这些模块的未来下载检索与第一次下载相同的位,以确保项目所依赖的模块不会出现意外更改,无论是出于恶意、意外还是其他原因。

【1】module path

go.mod的第一行是module path, 一般采用仓库+module name的方式定义。这样我们获取一个module的时候,就可以到它的仓库中去查询,或者让go proxy到仓库中去查询。

如果你的版本已经大于等于2.0.0,按照Go的规范,你应该加上major的后缀,module path改成下面的方式:

v2v3vx
【2】go directive
go 1.xx
v1.3.0

正式的版本号我们就不需要介绍了,大家都懂:

【3】伪版本号
v0.0.0-20200330080233-e4ea8bd1cbed

go module的目的就是在go.mod中标记出这个项目所有的依赖以及它们确定的某个版本。

20200330080233yyyyMMddhhmmsse4ea8bd1cbed

而前面的v0.0.0可能有多种生成方式,主要看你这个commit的base version:

  • vX.0.0-yyyymmddhhmmss-abcdefabcdef: 如果没有base version,那么就是vX.0.0的形式
  • vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef: 如果base version是一个预发布的版本,比如vX.Y.Z-pre,那么它就用vX.Y.Z-pre.0的形式
  • vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef: 如果base version是一个正式发布的版本,那么它就patch号加1,如vX.Y.(Z+1)-0

伪版本号之所以存在主要是由于提交的记录没有打tag, 打了tag就会直接使用tag的版本号。

【4】indirect 间接引用

有些库后面加了indirect后缀,这又是什么意思的。

如果用一句话总结,间接的使用了这个库,但是又没有被列到某个go.mod中,当然这句话也不算太准确,更精确的说法是下面的情况之一就会对这个库加indirect后缀:

  • 当前项目依赖A,但是A的go.mod遗漏了B, 那么就会在当前项目的go.mod中补充B, 加indirect注释
  • 当前项目依赖A,但是A没有go.mod,同样就会在当前项目的go.mod中补充B, 加indirect注释
  • 当前项目依赖A,A又依赖B,当对A降级的时候,降级的A不再依赖B,这个时候B就标记indirect注释
【5】incompatible 不规范的依赖

有些库后面加了incompatible后缀,但是你如果看这些项目,它们只是发布了v2.2.1的tag,并没有+incompatible后缀。

这些库采用了go.mod的管理,但是不幸的是,虽然这些库的版major版本已经大于等于2了,但是他们的module path中依然没有添加v2, v3这样的后缀。

所以go module把它们标记为incompatible的,虽然可以引用,但是实际它们是不符合规范的。

【6】exclude 排除依赖

如果你想在你的项目中跳过某个依赖库的某个版本,你就可以使用这个段。

go get -u ......go get github.com/xxx/xxx@latest
【7】replace 替换依赖

replace也是常用的一个手段,用来解决一些错误的依赖库的引用或者调试依赖库。

github.com/coreos/bboltgo.etcd.io/bbolt

replace可以替换某个库的所有版本到另一个库的特定版本,也可以替换某个库的特定版本到另一个库的特定版本。