从 Go 1.11 版本开始,除了 GOPATH 构建模式外,Go 又增加了一种 Go Module 构建模式。
在 Go Module 模式下,通常一个代码仓库对应一个 Go Module。
一个 Go Module 的顶层目录下会放置一个 go.mod 文件,每个 go.mod 文件会定义唯一一个 module,也就是说 Go Module 与 go.mod 是一一对应的。
go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。
前言
Go 的项目依赖管理经历了从 Go Path => Go Vendor => Go Moudle
的演变。
Go Path
Go Path
主要目录结构如下:
1 | . |
然而,当遇到 A 和 B 依赖于某一个包的不同版本时,无法实现 package 的多版本控制。

Go Vendor
在 Go Path
的目录结构中,新增了 vendor
目录,如果存在该目录,优先使用其下的依赖,否则从 Go Path
中寻找。
但是,Go Vendor
不能很清晰地标识版本的概念:
- 无法控制依赖的版本
- 更新项目可能出现依赖冲突,导致编译出错

Go Module 构建模式
语义导入版本机制
在 Go Module 构建模式下,一个符合 Go Module 要求的版本号,由前缀 v 和一个满足语义版本规范的版本号组成

按照语义版本规范,
- 主版本号不同的两个版本是相互不兼容的;
- 在主版本号相同的情况下,次版本号大都是向后兼容次版本号小的版本;
- 补丁版本号也不影响兼容性。
Go Module 规定:如果同一个包的新旧版本是兼容的,那么它们的包导入路径应该是相同的。
1 | // 通过在包导入路径中引入主版本号的方式,来区别同一个包的不兼容版本 |
Go Module 将这样的版本 (v0) 与主版本号 v1 做同等对待,也就是采用不带主版本号的包导入路径
最小版本选择原则
依赖关系一旦复杂起来,比如像下图中展示的这样,Go 又是如何确定使用依赖包 C 的哪个版本的呢?

当前存在的主流编程语言,以及 Go Module 出现之前的很多 Go 包依赖管理工具都会选择依赖项的“最新最大 (Latest Greatest) 版本”,对应到图中的例子,这个版本就是 v1.7.0
。
不过,Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”
这个例子中,C v1.3.0
是符合项目整体要求的版本集合中的版本最小的那个,于是 Go 命令选择了 C v1.3.0
,而不是最新最大的 C v1.7.0
。
Go Module 版本管理
(1)Go Module 创建
创建一个 Go Module,通常有如下几个步骤:
- 通过
go mod init
创建 go.mod 文件,将当前项目变为一个 Go Module; - 通过
go mod tidy
命令自动更新当前 module 的依赖信息; - 执行
go build
,执行新 module 的构建。
(2)为当前 Module 添加一个依赖
1 | 手动添加 |
(3)升级 / 降级依赖的版本
1 | 选定指定版本即可 |
(4)移除一个依赖
仅从源码中删除对依赖项的导入语句还不够,还得用 go mod tidy
命令,将这个依赖项彻底从 Go Module 构建上下文中清除掉。
go mod tidy
会自动分析源码依赖,而且将不再使用的依赖从 go.mod 和 go.sum 中移除。
(5)vendor
vendor 机制可以对 vendor 目录下缓存的依赖包进行自动管理。
在 Go 1.14 及以后版本中,如果 Go 项目的顶层目录下存在 vendor 目录,那么 go build 默认也会优先基于 vendor 构建,除非 go build 传入 -mod=mod
的参数。
1 | go mod vendor |
空导入
像下面代码这样的包导入方式被称为“空导入”:import _ "foo"
空导入也是导入,意味着我们将依赖 foo 这个路径下的包。
由于是空导入,我们并没有显式使用这个包中的任何语法元素。
通常实践中空导入意味着期望依赖包的 init 函数得到执行,这个 init 函数中有我们需要的逻辑。