on
go.mod和go.sum
使用go官方的依赖管理工具——go module对go项目进行依赖管理时,会生成两个文件:go.mod和go.sum,本文会聊一聊这两个文件相关的话题。
go.mod
go.mod文件是go module工具的核心,它记录了我们项目中直接引用的依赖包名以及版本,我们执行go mod的各种命令,实际上都是在修改go.mod这个文件。
文件内容
module module_url_xxx
go 1.18
require(
dep1 v0.0.1
dep2 v1.0.5
dep3 v0.0.0-20221118072143-89ae0afbf95f
)
require(
dep4 v0.1.2 // indirect
)
replace(
dep2 => dep2 v1.0.4
)
文件的第一行,要指定项目的module的url,如github.com/golang/protobuf
。
go 1.18
表示该项目基于go 1.18版本构建。
require中,每一行是一个该项目的依赖包,格式是:包名:版本
。
replace中可以强制指定包的版本,这主要用于当有依赖冲突时,手动的指定想要的版本。
如何指定版本
当我们代码中import某个依赖,执行go mod tidy
命令后,go.mod会自动的添加该依赖,并使用最新的版本。如果希望使用指定的某个版本,
那么直接在go.mod文件中该依赖项后指定版本即可。
在上面的例子中,我们可以看到dep1的版本是v0.0.1
,而dep3的版本是v0.0.0-20221118072143-89ae0afbf95f
,二者格式不同的原因是dep1的仓库中,
打了v0.0.1
的tag,而dep3中,没有打任何tag,所以就使用了主干(master)分支的最新commit作为最新的版本。
所以我们指定版本时,既可以使用tag,也可以使用分支名。
如何解决依赖冲突
依赖冲突指的是:构建项目时,对于依赖项,有多个指定的版本,这时需要选择哪个版本?
举例:当前项目直接依赖了A包的v0.0.5
版本和B包的v0.0.6
版本,而B包的v0.0.6
版本中,直接引用了A包的v0.0.7
版本。构建当前项目时,对于如何选择
A包的版本,就发生了冲突,应该选择v0.0.5
版本,还是v0.0.7
版本呢?
dep_A v0.0.5
/
project
\
dep_B v0.0.6 - dep_A v0.0.7
幸运的是,go module会自动的帮我们选择要使用的版本,对于上面的例子,go module最后会选择A包的v0.0.7
版本进行构建。而go module处理版本冲突的原则是:
选择能够支持当前项目构建的最小依赖版本。这句话是什么意思,我们下面详细的解释下。
版本名规范
一般项目的版本名都是这种格式v1.2.3
- 第一个数字是主版本号。主版本号一致的版本,向下兼容代码,即v1.2.3必须能够兼容v1.1.2。不一致时,不能向下兼容,比如api发生了变化,那么主版本号一定要变化。
- 第二个数字是次版本号。当做了向下兼容的功能更新时,这个数字要变化。
- 第三个数字修订版本号。当做了向下兼容的功能修订时,这个数字要变化。
- 所以,v1.2.3一定兼容v1.1.2的代码,但是v1.1.2的代码不一定兼容v1.2.3,因为v1.2.3中有v1.1.2中不存在的功能。v2.0.0不一定能兼容v1.9.9的代码,因为可能api发生了变化。
go module的版本选择
根据上面所述的版本名规范,我们再去理解:选择能够支持当前项目构建的最小依赖版本 这句话就比较容易了。我们分析继续上面的例子,A包有两个版本
v0.0.5
和v0.0.7
,而v0.0.7
是可以兼容v0.0.5
的,但v0.0.5
可能不兼容v0.0.7
,所以能够支持当前项目构建的版本是v0.0.7
。
至于为什么说v0.0.7
是最小版本,是因为A包可能还有v0.0.8
或v0.0.9
这种更新的版本,但由于v0.0.7
就足够构建当前项目了,所以不必使用更大的版本了。
为什么会有 // indirect
indirect表示该依赖是间接依赖,在go1.16
之前,其实并不是所有的间接依赖都会添加到go.mod中,但go1.16
之后,所有的间接依赖都会出现在go.mod中,
并且require代码块分成了两块,分别是直接依赖和间接依赖。
replace的作用
上面我们介绍了go module的依赖版本选择方式,那如果当依赖冲突时,我们想使用指定的某个版本时,应该如何做呢?这时可以通过replace来指定,使用也很简单, 直接指定版本即可。
go.sum
go.sum文件也是自动生成的,并且该文件不应该我们手动的进行编辑,它的作用是:保证一致性构建。
一致性构建指的是:在不同的环境下,构建项目时,都使用相同的依赖项。
go.sum中其实是记录了依赖项代码的hash值,当构建或拉取依赖时,会进行checksum校验。
构建时校验
这里有个疑问:go.mod中已经指定了依赖包名和版本了,这是不是可以保证一致性构建了?其实还不够,因为即使是相同的包名和版本,它的代码可能会被人篡改, 导致构建时使用的依赖代码不同。当进行构建时,会将本地缓存中的该依赖取出并计算hash值,如果与go.sum中记录的一致,那么可以构建,否则就构建失败。
拉取时校验
go module将依赖项写入go.sum文件前,会对依赖项进行校验,当计算出通过go get
命令拉下来的依赖项的hash后,go module会从GOSUMDB
配置的数据库中,
拉取该依赖的一个官方的hash,如果二者一致,说明该依赖项没问题,可以写入go.sum。默认的GOSUMDB
是sum.golang.org,这里包含几乎所有的第三方库,
但肯定是没有包含公司内部的私有仓库的,因此一般都会将公司的gitlab域名配置在GONOSUMDB
这个白名单中,这样就不会对公司的私有仓库进行checksum校验了。