1. 版本号规范
go mod 对版本号的定义是有一定要求的,它要求的格式为
v<major>.<minor>.<patch>
,如果 major 版本号大于 1 时,其版本号还需要体现在 Module 名字中。比如 我的项目github.com/pibigstar/go-demo
,如果我的版本号增长到v2.x.x
时,我的 Module 名字也需要相应的改变为:github.com/pibigstar/go-demo/v2
, 有人可能就要问了,我不改可以吗? 可以的!但是 go mod 会在你依赖的后面打一个+incompatible
标志
2. 伪版本
我们将项目上传到 github 后,如果不打 tag,或 tag 不符合
v<major>.<minor>.<patch>
这个格式,那么当我们用 go mod 去拉这个项目的时候,就会将commitId
作为版本号,它的格式大概是vx.y.z-yyyymmddhhmmss-abcdef格式
虽然不太好看,但是这个玩意其实挺有用的,省的你每次都需要打 tag 了,这里介绍一个直接拉取小技巧
require (
github.com/pibigstar/go-demo master
)
我们直接在后面写 master
分支,这样它就会拉取 master 分支最后一次提交的commitId
作为版本号
3. indirect 标志
我们用 go mod 的时候应该经常会看到 有的依赖后面会打了一个
// indirect
的标识位,这个标识位是表示 间接的依赖。
什么叫间接依赖呢?打个比方,项目 A 依赖了项目 B,项目 B 又依赖了项目 C,那么对项目 A 而言,项目 C 就是间接依赖,这里要注意,并不是所有的间接依赖都会出现在 go.mod 文件中。间接依赖出现在 go.mod 文件的情况,可能符合下面的场景的一种或多种:
- 直接依赖未启用 Go module
- 直接依赖 go.mod 文件中缺失部分依赖
3.1 直接依赖未启用 Go module
如下图所示,Module A 依赖 B,但是 B 还未切换成 Module,即没有 go.mod 文件,此时,当使用 go mod tidy 命令更新 A 的 go.mod 文件时,B 的两个依赖 B1 和 B2 将会被添加到 A 的 go.mod 文件中(前提是 A 之前没有依赖 B1 和 B2),并且 B1 和 B2 还会被添加// indirect
的注释。
那么项目 A 的依赖关系就会变成下面这个样子
require (
B vx.x.x
B1 vx.x.x // indirect
B2 vx.x.x // indirect
)
3.2 go.mod 文件中缺失部分依赖
如下图所示,Module B 虽然提供了 go.mod 文件中,但 go.mod 文件中只添加了依赖 B1,那么此时 A 在引用 B 时,则会在 A 的 go.mod 文件中添加 B2 作为间接依赖,B1 则不会出现在 A 的 go.mod 文件中。
此时项目 A 的依赖关系就会变成下面这个样子
require (
B vx.x.x
B2 vx.x.x // indirect
)
间接依赖出现在 go.mod 中,可以一定程度上说明依赖有瑕疵,要么是其不支持 Go module,要么是其 go.mod 文件不完整。
由于 Go 语言从 v1.11 版本才推出 module 的特性,众多开源软件迁移到 go module 还需要一段时间,在过渡期必然会出现间接依赖,但随着时间的推进,在 go.mod 中出现// indirect 的机率会越来越低。
出现间接依赖可能意味着你在使用过时的软件,如果有精力的话还是推荐尽快消除间接依赖。可以通过使用依赖的新版本或者替换依赖的方式消除间接依赖。
3.3 如果查找间接依赖
可以通过 go mod why -m <pkg>
来查看这个间接依赖是被谁引入的
4. replace 使用
replace 指替换,它指示编译工具替换 require 指定中出现的包
这里要注意两点:
replace 只在 main module 里面有效
什么叫 main module? 打个比方,项目 A 的 module 用 replace 替换了本地文件,那么当项目 B 引用项目 A 后,项目 A 的 replace 会失效,此时对 replace 而言,项目 A 就是 main module
replace 指定中需要替换的包及其版本号必须出现在 require 列表中才有效
这里我再简单谈下 replace 的使用场景
- 替换无法下载的包
- 替换本地自己的包
- 替换 fork 包
有时候我们依赖的第三方库可能有 bug,我们就可以 fork 一份他们的库,然后自己改下,然后通过 replace 将我们 fork 的替换成原来的
5. exclude 使用
go.mod 文件中的 exclude 指令用于排除某个包的特定版本,其与 replace 类似,也仅在当前 module 为 main module 时有效,其他项目引用当前项目时,exclude 指令会被忽略。
exclude 指令在实际的项目中很少被使用,因为很少会显式地排除某个包的某个版本,除非我们知道某个版本有严重 bug。 比如指令exclude github.com/pibigstar/go-demo v1.1.0
,表示不使用 v1.1.0 版本。
6. 包缓存
最后简单谈一下当我们使用 go mod 后包缓存存储问题,依赖包存储在
$GOPATH/pkg/mod
,该目录中可以存储特定依赖包的多个版本。需要注意的是$GOPATH/pkg/mod
目录下有个cache
目录,它用来存储依赖包的缓存,简单说,go 命令每次下载新的依赖包都会在该 cache 目录中保存一份,每个版本都会有一份缓存。
值得注意的是包名大小写问题,有时我们使用的包名中会包含大写字母,GOMODULE
模式下,在存储时会将包名做大小写编码处理,即每个大写字母将变!+相应的小写字母
,比如github.com/pibigstar/GO-demo
包在存储时将会被放置在$GOPATH/pkg/mod/github.com/pibigstar/!g!o-demo
目录中。所以这里我不推荐你用大写字母定义包名
欢迎关注我微信公众号,一起学习Go
本作品采用《CC 协议》,转载必须注明作者和本文链接