我们经常提到的 go.mod 是什么?
go.modgopathvender
modmodulego.modGo ModuleGo Module
- 准确记录项目依赖:当前项目使用了哪些第三方包、依赖包的版本
- 可重复构建:当前项目在任何环境或平台构建产物相同
module 是什么?
packagepackage
module 版本号规则
一个 module 的版本号规则必须遵循语义化规范:v(major).(minor).(patch)格式。
- major:大版本,发生不可兼容的改动时增加该版本
- minor:小版本,当有新特性时增加该版本
- patch:补丁版本,当有bug修复时增加该版本
举例说明
下面是开源 web 框架 gin 的 go.mod 文件,首行声明了 gin 的 module 名称,yi'gon
module github.com/gin-gonic/gin
go 1.13
require (
github.com/gin-contrib/sse v0.1.0
github.com/go-playground/validator/v10 v10.9.0
github.com/goccy/go-json v0.7.8
github.com/json-iterator/go v1.1.12
github.com/mattn/go-isatty v0.0.14
github.com/stretchr/testify v1.7.0
github.com/ugorji/go/codec v1.2.6
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v2 v2.4.0
)
用法概述
- 初始化 module
- 如果一个项目想使用 Go Module,那么其本身需要先成为一个 module, 需要有一个 module 名字。
项目的module名字以及依赖关系记录在 go.mod 的文件中,该文件可以手动创建,也可以使用 go mod init 命令自动生成
go mod init xxx(module 的名称)
- 在 go.mod 文件中记录go的版本号是go 1.12 中引入的新特性,表示开发此项目的Go语言版本
- 添加依赖
可以使用go get 下载指定版本依赖包,也可以直接使用 go build 去编译文件,此时会自动分析并下载依赖包进行编译。go get 总是会下载依赖包的最新版本。
如果是第一次引用外部包,go get 还会生成一个 go.sum 文件,这个文件存储的是依赖包的哈希值信息。
/ 包名 版本号 哈希算法类型:哈希值
// 这个哈希值是这个包整体文件计算得到的
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
// 包名 版本号 包go.mod 哈希算法类型:哈希值
// 这个哈希值仅仅是这个包、这个版本的 go.mod 文件的哈希值
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.mod文件中常见的关键字
- module:声明 module 名称
- require:声明依赖及版本号
- replace:替换 require 中声明的依赖
- exclude:禁用指定依赖
- indirect:表示间接依赖
replace
replace 仅在当前module 为 main module 时生效,main module指的值当前正在编译的项目
replace 指令中 "⇒"前面的包以及版本号,必须出现在require中才生效,表示替换的包以及版本号必须是本项目中用到的。
- 使用场景
- 替换无法下载的包。比如有些包因为网络问题无法下载,如果这个包在 Github 上有镜像,那么可以替换为 Github 上包。
- 替换为 fork 仓库。比如有的包有 bug,在开源版本还没有修复时,可以暂时fork下来修复 bug,替换为 fork 版本,等修复后再使用开源包,这种是临时做法。
- 禁止被依赖。比如某个 module 不希望被直接引用,那么可以在 require 中把包的版本号都写为 v0.0.0,然后在下面 replace 中替换为实际的版本号。这样其他包引用这个包时,会因为找不到 v0.0.0而无法使用。
indirect
indirect 总是出现在require指令中,表示这个包是被间接依赖的 // 表示注释的开始,例如:
require (
github.com/google/uuid v1.3.0 // indirect
)
- 间接依赖使用的场景:
- 直接依赖的那个包,没有使用 Go Module 管理依赖,不存在 go.mod 文件,那么这个包的所有依赖都会以间接的方式出现在当前的 go.mod 文件中
- 直接依赖那个包,go.mod 文件不全,缺失的依赖也会以间接的方式出现在当前的 go.mod 中
- 间接依赖理论上不应该出现,如果 go.mod 中出现了间接依赖,那么要小心,看看自己是否使用了过时的开源包
查看间接依赖的方法:
# [pkg] 表示被间接依赖的包名
go mod why -m [pkg]
# 分析所有依赖的依赖链
go mod why -m all
go.sum 文件的作用
gin
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
...
go.sum 文件存在的意义主要是为了实现构建一致性,在任何环境中构建项目时使用的依赖包必须跟 go.sum 中记录的完全一致。
- 在 go.sum 文件中会把直接依赖、间接依赖全部记录;
- 项目中依赖的每一个包在 go.sum 中都会有两条记录,第一条是整个包所有文件一起计算得到哈希值,第二条表示该依赖包的 go.mod 文件计算得到的哈希值。
版本选择机制
版本选择:
最新版本选择:当某个包第一次被引用时。go get 或 go build 都会选择这个包的最新的版本使用
最小版本选择:当某个包的多个版本都被项目依赖时,依赖可能会发生变化,会自动选择依赖包的最小可用版本。
对于不规范的包的如何处理
如果包的版本号大于1,但是包引用时,包名没有带上版本号,那么就称为这个包不规范的包,此时Go命令会给这个包加上+incompatible标识,对于当前项目不影响使用。
如果其它项目使用这个不规范的包时,go get 不会自动选择不兼容的版本,即不会使用版本号大于1的版本。
如果发现go.mod中有不规范的标识,应该及时修正。
require (
// major 版本号大于3,引用的时候,包名后应该加上版本号
// 正确:github.com/agiledragon/gomonkey/v2 v2.0.2
github.com/agiledragon/gomonkey v2.0.2+incompatible
)
go.sum 文件的作用
gin
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
...
go.sum 文件存在的意义主要是为了实现构建一致性,在任何环境中构建项目时使用的依赖包必须跟 go.sum 中记录的完全一致。
- 在 go.sum 文件中会把直接依赖、间接依赖全部记录;
- 项目中依赖的每一个包在 go.sum 中都会有两条记录,第一条是整个包所有文件一起计算得到哈希值,第二条表示该依赖包的 go.mod 文件计算得到的哈希值。