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/repo
PathB/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.z
v0.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.