这一次,彻底掌握go mod

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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!