最近认真学习了一下 go mod
,整理成文。这篇文章是系统的了解 go mod
,并不是简单介绍如何使用。
在 go mod
出来之前,社区采用的是类似 NodeJS
那种模式,vendor
,也就是把所有的包都保存在 vendor
这个目录下,但是
这种方式显然不够优雅,后来有了 go mod
,到今天为止,go mod
已然成为了Go社区模块管理的标准。
go.mod
的文件夹,就是一个 module。简单来说,一个module,里面包含了若干个package。*.go
文件组成一个包,每个 .go
文件顶部,都会有一个 package xxx
来声明自己是什么包。module
有版本,Go里遵守 Semantic Versioning 2.0.0,因此,版本基本都会长这样:
v0.0.1
, v1.2.3
, v2.0.0
等等。通常从git仓库中读取的时候,就是以tag为准,有的仓库没有打tag,那么Go会自动生成一个
版本号,称之为 pseudo-version
,也就是伪版本号,例如 v0.0.0-20191109021931-daa7c04131f5
。大概的格式是:
上面三个,用 -
连起来。
也许我们的程序,会出现 2.0
, 3.0
, 4.0
,那么,Go提供的方案是什么呢?答案是,建一个子目录,例如 v2
版本,就建立一个 v2
子目录,v3
就建 v3
子目录。或者如果在顶级目录的话,就要在 go.mod
声明的路径里最后加一个 v2
或者 v3
的后缀。
说实话,不是很优雅。他这个决定,主要是基于这么一条准则:
If an old package and a new package have the same import path, the new package
must be backwards compatible with the old package.
也就是说,同一个大版本号内的代码,必须是兼容的。Go这样做,带来的一个问题是,例如从 v2
升级到 v3
的时候,也许
的确有一些不兼容的地方,但是大部分代码都还是兼容的,又不是完全重写。而这样做了以后呢,调用者原本是导入
github.com/x/aaa
,现在要全部改成导入 github.com/x/aaa/v2
,并且以前所有引用到的地方都需要改。此外 v2
里的aaa.XXXStruct
和 v1
版本里的 aaa.XXXStruct
是不兼容的,无法互相赋值的,这样就会导致调用者需要改很多东西,带来不少负担。
在国内,go get
基本上是无法直接使用的,原因你懂的。因此我们一般都要设置 GOPROXY
这个环境变量:
$ go env -w GOPROXY="https://goproxy.cn,direct"
如果你的项目是私有项目,那么你还需要把对应的路径加到 GOPRIVATE
这个变量里:
$ go env -w GOPRIVATE="github.com/your_name,git.example.com"
如果只有一个,就写一个,多个,就用逗号连起来。对于私有仓库,建议同时设置一个 GONOSUMDB
,值和它一样。
上面我们说了,有 go.mod
文件的地方,就是一个module。我们来看看 go.mod
的格式,先来看一个例子:
module example.com/my/thing
go 1.12
require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]
module
声明模块自身的路径go
声明这个模块所用的Go版本require
声明这个模块依赖的模块exclude
让go mod不要加载这个模块replace
声明替换,=>
前是代码中的路径,后是要替换成的路径retract
声明这个模块中,损坏的版本,或者要撤回,要大家别用的版本。作用是,设置了的版本,使用者碰到这个
版本的时候,就会看到对应的提示里面所有的路径,都是 URL 加 空格,加 版本号的格式,例如 example.com/new/thing/v2 v2.3.4
。
go mod 是怎么做版本选择的呢?一句话:从当前项目 main
开始,构建一颗依赖树,当多个子模块依赖同一个模块时,选最新的。
如上图,main
依赖 A1.2
, B1.2
,A1.2
依赖 C1.3
,而 B1.2
依赖 C1.4
,他们同时依赖 D1.2
。最后,go mod
会选择 main
, A1.2
, B1.2
, C1.4
, D1.2
。
有的时候,我们想要给子目录打一个版本,那怎么做呢?答案是在版本号前面加上目录的路径就可以,例如:
module/
- A
- A/B
那我们执行 git tag A/B/v0.1.2
就可以。这样使用者在使用该包时,就会优先选择子目录的版本,而不是根目录的版本号。
我们来看看常见的和版本控制有关系的命令:
go mod init github.com/xxx/yyy
声明一个模块go mod tidy
执行模块选择,并且把选择的依赖写到 go.sum
文件里go mod why
解释引入某个依赖的依赖链go build
构建二进制go get
添加依赖go get -u
更新依赖这篇文章整理了 go mod
的基本概念,然后介绍了它的版本选择方案,以及常见的使用方法和命令,希望能够给读者带来帮助。
ref: