在使用 Go module 过程中,随着引入的依赖增多,也许你会发现
go.mod
文件中部分依赖包后面会出现一个// indirect
的标识。这个标识总是出现在require
指令中,其中//
与代码的行注释一样表示注释的开始,indirect
表示间接的依赖。比如开源软件 Kubernetes(v1.17.0版本)的 go.mod 文件中就有数十个依赖包被标记为
indirect
:module changeme go 1.17 require github.com/wailsapp/wails/v2 v2.0.0-beta.43 require ( github.com/bep/debounce v1.2.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/uuid v1.3.0 // indirect )
在执行命令
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
的注释。![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fd54b0f25-9426-4b83-9c51-2e6165a030b2%2Fa127a309-7b06-485e-8720-39acec158962%2FUntitled.png%3Ftable%3Dblock%26id%3Db1cf4390-273b-4318-9ee9-ab9238d6610d%26spaceId%3Dd54b0f25-9426-4b83-9c51-2e6165a030b2%26expirationTimestamp%3D1725847200000%26signature%3DLsu-5fP9DNa42aAIkaN4ZGkQXsYGQ2a7_12UPxrZ6w0?table=block&id=b1cf4390-273b-4318-9ee9-ab9238d6610d&cache=v2)
此时Module A的
go.mod
文件中require部分将会变成: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
文件中。![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fd54b0f25-9426-4b83-9c51-2e6165a030b2%2F86909342-b04f-416a-8d0e-788d555510d4%2FUntitled.png%3Ftable%3Dblock%26id%3D0943e509-e950-4a3f-b899-ff01dc225845%26spaceId%3Dd54b0f25-9426-4b83-9c51-2e6165a030b2%26expirationTimestamp%3D1725847200000%26signature%3DrKHs0oD5dPP-z59btE3-mHYG1LSc_JyXccgVtBMQIeo?table=block&id=0943e509-e950-4a3f-b899-ff01dc225845&cache=v2)
此时Module A的
go.mod
文件中require部分将会变成require ( B vx.x.x B1 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
文件片断:module changeme go 1.17 require github.com/wailsapp/wails/v2 v2.0.0-beta.43 require ( github.com/bep/debounce v1.2.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/uuid v1.3.0 // indirect )
我们希望确定间接依赖
github.com/bep/debounce v1.2.1 // indirect
是被哪个依赖引入的,则可以使用命令go mod why
来查看:$ go mod why -m github.com/bep/debounce github.com/wailsapp/wails/v2 github.com/wailsapp/wails/v2/internal/appng github.com/wailsapp/wails/v2/internal/frontend/desktop github.com/wailsapp/wails/v2/internal/frontend/desktop/windows github.com/bep/debounce
按照从上到下的加载顺序来看,
github.com/bep/debounce
被 github.com/wailsapp/wails/v2/internal/frontend/desktop/windows
使用,也确定了是 github.com/wailsapp/wails/v2
项目的 go.mod 问题。另外,命令
go mod why -m all
则可以分析所有依赖的依赖链。