这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
Go 依赖管理的演化
GOPATH -> Go Vendor -> Go Module
GOPATH
Go 最原始的包管理机制, 通过环境变量 $GOPATH 指定一个工作区,
其目录结构为:
.
├── bin/ # 项目编译的二进制文件
├── pkg/ # 用以加速编译的中间产物
└── pkg/ # 项目源代码
项目所有依赖的源代码都会放到 src/ 文件夹下, go get
会下载最新版本的包到此文件夹下。
弊端
此方法的弊端是无法实现多版本控制。 例如 A、B 同时依赖于 C 的不同版本,
$GOPATH 却只能保存其中一个版本。 如果 C 的更新做了破坏性的变更,
两者之间可能只有一个能编译成功。
Go Vendor
为了解决多版本的问题,Go Vendor 应运而生。 在bin项目的根目录下存在一个
vendor/ 文件夹, 此项目所有依赖包的副本都会放在这个文件夹下。 如:
├── READEME.md
├── man.go
└── vendor/ # 存放所有依赖的副本
依赖会以 vendor -> $GOPATH 的顺序寻找。
弊端
此方法的弊端是无法实现更深层次的多版本控制。 例如包 C 依赖包 A、B,而 A、B 又同时依赖于 D 的不同版本。 它仍旧依赖于项目源码,不能清晰标识使用的版本。
Go Module
1.11 实验性引入,1.16 默认开启。 它通过 go.mod 文件管理依赖包的版本。
Go Module 的依赖管理
依赖管理三要素
- 配置文件 描述依赖 对应
go.mod - 中心仓库 存放依赖库 对应各种 Proxy
- 本地工具 对应
go get或go mod
典型的 go.mod 文件
module example/project/app
go 1.20
require(
example/lib1 v1.0.2
example/lib2 v2.3.0 // indirect
example/lib3 v1.5.4+incompatible
)
版本号与版本管理规则
Go Module 使用定义了两种类型的版本规则, Go 会通过它计算最低的依赖版本。
- 语义化版本 ${MAJOR}.${MINOR}.${PATCH} 如 v1.2.3
- 基于 commmit 的伪版本 v0.0.0-yyyymmddhhmmss-${commit harsh} 如 v1.0.0-20201130134442-10cb98267c6c
require 单元中的关键字
-
indirect
如
example/lib2 v2.3.0 // indirect, 代表间接依赖。例如 A 依赖于 B,B 依赖于 C, A 对 C 就是间接依赖。
-
incompatible
如
example/lib3 v1.5.4+incompatible。没有
go.mod(即没用使用 Go Module),会被以此标识。 代表可能存在不兼容的代码逻辑。
依赖分发与 Go Proxy
直接由第三方平台(如 GitHub)下载依赖存在以下问题
- 无法保证作者不随意更改版本
- 无法保证作者不删除代码仓库
- 增加了第三方平台的访问压力
为了解决这些问题,Go 提供了 Proxy,它会缓存原站中的内容, 缓存过后的版本不会改变, 这样可以避免作者删除历史版本或者整个代码仓库, 保证依赖的稳定与可靠。
通过环境变量 $GOPROXY 指定代理,其值为以逗号分隔的 url 列表。 如
"https://proxy1.cn,https://proxy2.cn,https://proxy3.cn,direct" 。
其中, direct 标识原始站点。 这样前面的站点都没有包含依赖的情况下,Go
会直接从原站中下载依赖。 即依赖查找顺序是 proxy1 -> proxy2 -> direct。
管理工具
go get
典型的命令如:
go get example.org/pkg
其中 pkg 后可通过 @ 接特定值代表版本:
- @update 默认,拉取 major 版本的最新提交
- @none 删除依赖
- @v1.1.2 标有 tag 的特定版本
- @23dfdd5 特命的 commit
- @master 分支的最新版本
go mod
典型的命令如:
go mod <subcommand>
subcommand 主要有三个:
- init 初始化,创建
go.mod文件 - download 下载模块到本地缓存,即把拉取所有依赖
- tidy 增加需要的依赖,删除不需要的依赖