Go Module

Go Module

从 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。

参考:

06|构建模式:Go是怎么解决包依赖管理问题的? (geekbang.org)

1、Go Module 构建模式

语义导入版本机制

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

语义版本号规范

按照语义版本规范,

  • 主版本号不同的两个版本是相互不兼容的;
  • 在主版本号相同的情况下,次版本号大都是向后兼容次版本号小的版本;
  • 补丁版本号也不影响兼容性。

Go Module 规定:如果同一个包的新旧版本是兼容的,那么它们的包导入路径应该是相同的。

1
2
3
4
5
6
7
8
9
10
// 通过在包导入路径中引入主版本号的方式,来区别同一个包的不兼容版本
// 假如这是 v1.x.x 版本
import "github.com/sirupsen/logrus"
// 如果要导入 v2.x.x 版本
import "github.com/sirupsen/logrus/v2"
// 甚至可以同时依赖一个包的两个不兼容版本
import (
"github.com/sirupsen/logrus"
logv2 "github.com/sirupsen/logrus/v2"
)

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

2、Go module 版本管理

(1)Go Module 创建

创建一个 Go Module,通常有如下几个步骤:

  1. 通过 go mod init 创建 go.mod 文件,将当前项目变为一个 Go Module;

  2. 通过 go mod tidy 命令自动更新当前 module 的依赖信息;

  3. 执行 go build,执行新 module 的构建。

(2)为当前 module 添加一个依赖

1
2
3
4
5
# 手动添加
$ go get github.com/google/uuid

# 自动添加
$ go mod tidy

(3)升级 / 降级依赖的版本

1
2
# 选定指定版本即可
$go get github.com/sirupsen/[email protected]

(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
2
3
4
5
6
7
8
9
10
$go mod vendor
$tree -LF 2 vendor
vendor
├── github.com/
│ ├── google/
│ ├── magefile/
│ └── sirupsen/
├── golang.org/
│ └── x/
└── modules.txt

3、空导入

像下面代码这样的包导入方式被称为“空导入”:import _ "foo"

空导入也是导入,意味着我们将依赖foo这个路径下的包。

由于是空导入,我们并没有显式使用这个包中的任何语法元素。

通常实践中空导入意味着期望依赖包的init函数得到执行,这个init函数中有我们需要的逻辑。

作者

SukiEva

发布于

2021-11-17

更新于

2021-12-22

许可协议

CC BY-NC-SA 4.0

评论


取次花丛懒回顾,半缘修道半缘君。

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×