go.mod

go.mod 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module api.local

go 1.17

require (
github.com/jmoiron/sqlx v1.3.4
go.uber.org/zap v1.21.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v2 v2.4.0
)

require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
gopkg.in/ini.v1 v1.56.0 // indirect
)

replace (
go.uber.org/zap => go.uber.org/zap v1.21.0
github.com/jmoiron/sqlx => github.com/jmoiron/sqlx v1.3.4
)
  • module:用于定义当前项目的模块路径。
  • go:用于设置预期的 Go 版本。
  • require:用于设置一个特定的模块版本。
  • exclude:用于从使用中排除一个特定的模块版本。
  • replace:用于将一个模块版本替换为另外一个模块版本。

环境变量

GO111MODULE

Go modules 的开关,auto/on/off,推荐 on

1.17 中将完全放弃对 GOPATH 模式的支持,忽略 GO111MODULE 变量设置。

GOPROXY

设置 go 模块代理,用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式从镜像站点快速拉取。它拥有一个默认:https://proxy.golang.org,direct,但很可惜proxy.golang.org在中国无法访问,故而建议使用goproxy.cn作为替代:https://goproxy.cn,direct

“direct” 为特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),当值列表中上一个 Go module proxy 返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,遇见 EOF 时终止并抛出类似 “invalid version: unknown revision…” 的错误。

GOSUMDB

Go checksum database,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改,也可以是“off”即禁止 Go 在后续操作中校验模块版本

默认:sum.golang.org,但是在国内无法访问,故而更加建议将 GOPROXY 设置为goproxy.cn,因为goproxy.cn支持代理sum.golang.org

GONOPROXY / GONOSUMDB / GOPRIVATE

这三个环境变量都是用在当前项目依赖了私有模块,也就是依赖了由 GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 无法访问到的模块时的场景。

其中 GOPRIVATE 较为特殊,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是只是用 GOPRIVATE。

示例:

1
GOPRIVATE=*.corp.example.com

表示所有模块路径以corp.example.com的下一级域名(如:team1.corp.example.com)为前缀的模块版本都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括corp.example.com本身。

Global Caching

这个主要是针对 Go modules 的全局缓存数据说明,如下:

  • 同一个模块版本的数据只缓存一份,所有其他模块共享使用。
  • 目前所有模块版本数据均缓存在$GOPATH/pkg/mod$GOPATH/pkg/sum下,未来或将移至$GOCACHE/mod$GOCACHE/sum
  • 可以使用go clean -modcache清理所有已缓存的模块版本数据

indirect 表示什么意思

https://my.oschina.net/renhc/blog/3162751

在执行命令 go mod tidy 时,Go module 会自动整理 go.mod 文件,如果有必要会在部分依赖包的后面增加 // indirect 注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加 // indirect 注释的包则是直接依赖的包,即明确的出现在某个 import 语句中。

然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod 文件中。

间接依赖出现在 go.mod 文件的情况,可能符合下面所列场景的一种或多种:

  • 直接依赖未启用 Go module
  • 直接依赖 go.mod 文件中缺失部分依赖

直接依赖未启用 Go module

如下图所示,Module A 依赖 B,但是 B 还未切换成 Module,也即没有 go.mod 文件,此时,当使用 go mod tidy 命令更新 A 的 go.mod 文件时,B 的两个依赖 B1 和 B2 将会被添加到 A 的 go.mod 文件中(前提是 A 之前没有依赖 B1 和 B2),并且 B1 和 B2 还会被添加 // indirect 的注释。

此时 Module A 的 go.mod 文件中 require 部分将会变成:

1
2
3
4
5
require (
B vx.x.x
B1 vx.x.x // indirect
B2 vx.x.x // indirect
)

依赖 B 及 B 的依赖 B1 和 B2 都会出现在 go.mod 文件中。

直接依赖 go.mod 文件不完整

如上面所述,如果依赖 B 没有 go.mod 文件,则 Module A 将会把 B 的所有依赖记录到 A 的 go.mod 文件中。即便 B 拥有 go.mod,如果 go.mod 文件不完整的话,Module A 依然会记录部分 B 的依赖到 go.mod 文件中。

如下图所示,Module B 虽然提供了 go.mod 文件中,但 go.mod 文件中只添加了依赖 B1,那么此时 A 在引用 B 时,则会在 A 的 go.mod 文件中添加 B2 作为间接依赖,B1 则不会出现在 A 的 go.mod 文件中。

此时 Module A 的 go.mod 文件中 require 部分将会变成:

1
2
3
4
require (
B vx.x.x
B2 vx.x.x // indirect
)

由于 B1 已经包含进 B 的 go.mod 文件中,A 的 go.mod 文件则不必再记录,只会记录缺失的 B2。

总结

为什么要记录间接依赖

在上面的例子中,如果某个依赖 B 没有 go.mod 文件,在 A 的 go.mod 文件中已经记录了依赖 B 及其版本号,为什么还要增加间接依赖呢?

我们知道 Go module 需要精确地记录软件的依赖情况,虽然此处记录了依赖 B 的版本号,但 B 的依赖情况没有记录下来,所以如果 B 的 go.mod 文件缺失了(或没有)这个信息,则需要在 A 的 go.mod 文件中记录下来。此时间接依赖的版本号将会跟据 Go module 的版本选择机制确定一个最优版本。

如何处理间接依赖

综上所述间接依赖出现在 go.mod 中,可以一定程度上说明依赖有瑕疵,要么是其不支持 Go module,要么是其 go.mod 文件不完整。

由于 Go 语言从 v1.11 版本才推出 module 的特性,众多开源软件迁移到 go module 还需要一段时间,在过渡期必然会出现间接依赖,但随着时间的推进,在 go.mod 中出现 // indirect 的机率会越来越低。

出现间接依赖可能意味着你在使用过时的软件,如果有精力的话还是推荐尽快消除间接依赖。可以通过使用依赖的新版本或者替换依赖的方式消除间接依赖。

如何查找间接依赖来源

Go module 提供了 go mod why 命令来解释为什么会依赖某个软件包,若要查看 go.mod 中某个间接依赖是被哪个依赖引入的,可以使用命令 go mod why -m <pkg> 来查看。

另外,命令 go mod why -m all 则可以分析所有依赖的依赖链。

如何锁定版本号

使用 replace