Go - Build Mode
构建过程
确定包版本 + 编译包 + 链接目标文件(编译后得到的)
构建模式
| Mode | Desc |
|---|---|
| GOPATH | 不关注依赖版本 |
| Vendor - 1.5 | Reproducible Build |
| Go Module - 1.11 | Dependency Management |
GOPATH
- Go 首次开源时,内置了
GOPATH的构建模式 - Go 编译器可以在本地 GOPATH 下
搜索Go 程序依赖的第三方包- 如果存在,则使用这个本地包进行编译;如果不存在,则会报编译错误
1 | package main |
无法找到依赖包而构建失败
1 | $ go version |
搜索规则
- Go 程序需要导入
github.com/user/repo这个包路径,假设 GOPATH 为PathA:PathB - 在 GOPATH 构建模式下,Go 编译器在编译 Go 程序时,会从下列路径搜索第三方包
PathA/src/github.com/user/repoPathB/src/github.com/user/repo
GOPATH默认值为$HOME/go
可以通过
go get命令将缺失的第三方包下载到GOPATH配置的目录下,并确保间接依赖也存在
1 | $ go get github.com/sirupsen/logrus |
go get下载的包只是当前时刻各个依赖的最新主线版本,无法保证Reproducible Build
如果依赖包引入了不兼容的代码,Go 程序将编译失败
- 在 GOPATH 构建模式下,Go 编译器实际上并没有关注 Go 项目所依赖的第三方包的
版本 - Go 核心开发团队因此引入了
vendor机制
Vendor
Go 1.5引入了vendor机制- vendor 机制的本质:在 Go 项目的 vendor 目录下,
缓存项目的所有依赖包 - Go 编译器会
优先感知和使用vendor目录下缓存的第三方版本,而不是GOPATH - 将
vendor 目录和项目源码一并提交到代码仓库后,可以实现Reproducible build
1 | $ tree -LF 4 . |
Go 项目必须要位于GOPATH/src的目录下,才能开启 vendor 机制
如果不满足该路径要求,Go 编译器会忽略Go 项目下的 vendor 目录
vendor机制虽然解决了Reproducible Build的问题,但开发体验欠佳
- Go 项目必须放在
GOPATH/src下 庞大的 vendor 目录需要提交到代码仓库- 需要
手工管理vendor 目录下的 Go 依赖包
因此,Go 核心团队将 Go 构建的重点转移到如何解决
包依赖管理上
Go Module
- 从
Go 1.11开始,引入了Go Module构建模式,并在Go 1.16成为默认的构建模式 - 一个
Go Module是一个Go 包的集合Module是有版本的,因此 Module 下的包也是有版本的- Module 和这些包组成一个
独立的版本单元,一起打包、发布、分发
- 在 Go Module 模式下,通常
一个代码仓库对应一个 Go Module- 一个 Go Module 的顶级目录下会放置一个
go.mod文件,一一对应
- 一个 Go Module 的顶级目录下会放置一个
go.mod文件所在的顶层目录也被称为Module 的根目录- Module 根目录以及它的子目录的
所有 Go 包都归属于这个Go Module - 这个 Module 也被称为
Main Module
- Module 根目录以及它的子目录的
101
1 | package main |
通过
go mod init创建go.mod文件,将当前项目变成一个 Go Module
Go 项目可以在任意路径,不一定要在GOPATH/src
go.mod文件的第一行声明了module path,最后一行为 Go 版本指示符
1 | $ tree . |
- 通过
go mod tidy自动更新当前 Go Module 的依赖和校验和 扫描 Go 源码,自动找出项目依赖的外部 Go Module,下载直接依赖和间接依赖并更新本地的go.mod- 可以通过
$GOPROXY来加速第三方依赖(Go Module)的下载 - 下载的依赖会被放置在本地 Module 缓存路径,默认为
$GOPATH[0]/pkg/mod- 从
Go 1.15开始,可以通过GOMODCACHE来自定义Module的缓存路径
- 从
go.sum存放特定版本的 Module内容的哈希值 -安全措施- 如果某个 Module 的特定版本需要被
再次下载,需要先通过校验和校验 - 推荐将
go.mod和go.sum一并提交到代码仓库
- 如果某个 Module 的特定版本需要被
1 | $ echo $GOPROXY |
执行
go build
- 读取
go.mod中的依赖及版本信息- 在
本地 Module 缓存路径找到对应版本的依赖,执行编译和链接
1 | $ go build |
工作原理
语义导入版本
Semantic Import Versioning
- 在 Go Module 构建模式下,版本号:
vX.Y.Z Go 命令和go.mod文件都使用上述符合语义版本规范的版本号来描述Go Module 版本- 借助
语义版本规范- Go 命令可以确定同一个 Module 的两个版本
发布的先后次序,以及是否兼容
- Go 命令可以确定同一个 Module 的两个版本
- 兼容性
- 主版本号的不同版本是相互不兼容的
- 在主版本号相同的情况下,次版本号都是向后兼容次版本号小的版本
- 补丁版本号不影响兼容性
- 如果同一个包的新旧版本是
兼容的,那么它们的import path是相同的logrus v1.8.1和logrus v1.7.0是兼容的,都是v1版本import path都为import "github.com/sirupsen/logrus"
- 将
主版本号引入到import path中 -语义导入版本(Go 创新)- 发布
logrus v2.0.0 import path为import "github.com/sirupsen/logrus/v2"
- 发布
v0.y.zv0.y.z用于项目初始开发阶段的版本号,API 是不稳定的- Go Module 将
v0和v1做同等对待,降低开发人员的心智负担
通过在
import path中引入主版本号的方式,来区别同一个包的不兼容版本
1 | import ( |
最小版本选择
Minimal Version Selection
Go 选出
符合整体要求的最小版本,即v1.3.0,有助于实现Reproducible Build
切换
Go 1.11引入 Go Module 构建模式- 此时,
GOPATH构建模式和Go Module构建模式各自独立工作,可以通过环境变量GO111MODULE来切换
- 此时,
Go 1.16,Go Module 构建模式成为了默认构建模式- 后续版本可能会
彻底移除GOPATH 构建模式,Go Module构建模式将成为 Go唯一的标准构建模式
- 后续版本可能会
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.














