go build 依赖查找顺序为:先查模块根目录及其子目录下的 vendor,再查 $gopath/pkg/mod 缓存,最后 fallback 到 $goroot/src;go111module=on 时 $gopath/src 不参与非标准库查找。

go build 时依赖包从哪开始找?
Go 编译器不会凭空猜路径,它有一套明确的、分层的查找顺序,且该顺序受 GO111MODULE 环境变量和当前目录结构共同决定。
当 GO111MODULE=on(默认开启,自 Go 1.16 起)且当前目录或其任意上级存在 go.mod 文件时,查找顺序为:
- 当前源文件所在目录的
vendor子目录(若有) - 向上逐级查找,直到模块根目录(即含
go.mod的目录)下的vendor - 若未命中 vendor,则查
$GOPATH/pkg/mod中缓存的模块版本(如github.com/gorilla/mux@v1.8.0) - 最后 fallback 到
$GOROOT/src(仅限标准库)
注意:此时 $GOPATH/src 完全不参与查找 —— 这是模块模式与旧 GOPATH 模式最根本的区别。一旦启用 modules,$GOPATH/src 对非标准库包已失效。
vendor 目录真的“优先于所有”吗?
不是。vendor 的优先级只在模块模式下局部生效,且有严格前提:必须位于模块根目录或其子目录中,并且该 vendor 是由 go mod vendor 生成的(而非手动复制)。
常见误解场景:
- 项目 A 依赖 B,B 的
go.mod里写了replace github.com/x/y => ./local,但 A 执行go build时仍会忽略 B 的 vendor,直接走 A 的模块解析逻辑 - 你在
cmd/下新建一个vendor/目录,go build不会理它 —— vendor 必须在模块根或其直接子路径下才被识别 -
GO111MODULE=off时,vendor 仍有效,但此时$GOPATH/src和$GOROOT/src也参与竞争,且规则是“全用 vendor 或全不用”,不混合
为什么 go mod tidy 后某些包突然找不到?
因为 go mod tidy 会清理 go.mod 中未被代码 import 的依赖,但它**不修改 vendor 目录内容**。如果你之前靠 go mod vendor 把一堆包拷进 vendor,又没在代码里显式 import 其中某些包,tidy 就会把它们从 go.mod 里删掉。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
后果是:下次构建时,这些包既不在 go.mod 里声明,也不在 vendor 中(因 tidy 不同步 vendor),自然报错 cannot find package。
解决办法只有两个:
- 确保所有 vendor 中的包都被当前 module 的代码实际 import(哪怕只是 blank import:
_ "github.com/some/unused") - 或每次
go mod tidy后手动补执行go mod vendor,让 vendor 再次对齐go.mod
go list -m all 显示的包,为什么 go build 不用它?
go list -m all 展示的是模块层面的“声明依赖图”,包含间接依赖;而 go build 只按实际 import 语句去 resolve 包路径 —— 两者视角不同。
典型差异点:
- 某个间接依赖(如
golang.org/x/sys)可能被多个上游模块共用,list -m all会列出它一次,但build时只认你代码里import "golang.org/x/sys/unix"这一行是否真实存在 -
replace指令会影响list -m all输出(显示替换后的路径),但build时仍按原始 import 路径去 vendor 或$GOPATH/pkg/mod查找,再根据 replace 规则重定向 -
exclude会从list -m all中剔除模块,但若你的代码仍 import 了被 exclude 的包,build仍会失败 —— exclude 不等于删除,只是禁止自动选择该版本
真正决定构建行为的,永远是 import 语句 + 当前模块的 go.mod + vendor 存在与否 + 环境变量开关,而不是列表命令的输出。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










