我们经常提到的 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 中记录的完全一致。

  1. 在 go.sum 文件中会把直接依赖、间接依赖全部记录;
  2. 项目中依赖的每一个包在 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 中记录的完全一致。

  1. 在 go.sum 文件中会把直接依赖、间接依赖全部记录;
  2. 项目中依赖的每一个包在 go.sum 中都会有两条记录,第一条是整个包所有文件一起计算得到哈希值,第二条表示该依赖包的 go.mod 文件计算得到的哈希值。