go work 不改变单个模块的 go.mod 解析规则,仅在含 go.work 的目录执行命令时叠加模块查找上下文:先扫描 use 列表注入本地模块,再按常规流程解析依赖图;子模块内执行 go build 仍只读自身 go.mod。

go work 不改变单个模块的 go.mod 解析规则
工作区模式只是叠加了一层“模块查找上下文”,它不会重写或忽略子模块自身的 go.mod。当你在 service_a 目录里执行 go build,Go 仍然只看 service_a/go.mod 里的 require 和 replace,完全不知道 lib_b 已被加进 go.work。
真正起作用的时机,是当你在 go.work 所在目录(比如项目根)运行命令时,Go 工具链才会启用工作区逻辑:先扫描 use 列表里的本地路径,把它们当作“已安装模块”注入解析器,再按常规流程处理依赖图。
- 必须在含
go.work的目录下执行go build、go test、go list等命令 -
go.work中的use ./lib_b是相对路径,不能写成use ../lib_b或绝对路径 - 如果子模块
go.mod里还留着replace example.com/lib => ./local/path,它会优先于go.work生效——得手动删掉这行,工作区才能接管
为什么 go list -m all 显示一堆 indirect 模块
这不是工作区的问题,而是 Go 模块机制本身的递归依赖推导行为:indirect 表示这个模块没被你的代码直接 import,但被某个你依赖的库用到了。工作区模式下,它依然会出现,且版本号可能和你预期不符。
关键点在于:工作区只影响“模块来源”(本地 or 远程),不改变“依赖图结构”。如果你发现某个 indirect 模块版本突然跳变,大概率是上游某个依赖升级了它,而你没在 go.mod 里显式约束。
立即学习“go语言免费学习笔记(深入)”;
- 用
go mod graph | grep 'bad/pkg'查谁引入了可疑模块 - 想锁定版本?别在自己项目
require它,要用replace或exclude控制解析结果 -
go list -deps -f '{{.Path}} {{.Version}}' ./...比go list -m all更能反映真实加载链
go build 报 “module xxx is not in main module” 的真实原因
这句话本质是说:“我在当前模块的 go.mod 里找不到 xxx 的声明,也没在工作区上下文里看到它被 use”。常见于两种情况:
- 你在子模块目录执行命令,而该子模块的
go.mod没require也没replace那个模块——工作区此时根本没机会介入 -
go.work里漏写了use ./xxx,或者路径拼错(比如少了个/或多了一层../) - 模块路径和
import路径不一致:比如代码里写import "example.com/lib",但lib的go.mod声明的是module github.com/user/lib
replace 和 go work use 的选择边界
replace 是模块级覆盖,go work use 是工作区级声明。开发阶段几乎总是选后者,除非你要临时绕过某个模块做 patch 测试。
区别很实际:改一个模块的路径,replace 得全量更新所有引用它的 go.mod;而 go work use 只要 go.work 文件不动,所有模块都自动跟着走新路径。
-
replace适合 CI 构建或发布前的最终锁定,不适合日常开发 -
go work use要求所有被use的模块都有合法go.mod,且不能有循环require - IDE(如 VS Code + Go extension)对
go.work支持已较成熟,但部分老插件仍只认replace
最易被忽略的点:工作区模式下,go.sum 仍按每个模块独立生成,不是全局一份。你改了 lib_b 代码,service_a 的测试跑通了,不代表 service_a/go.sum 里对应 lib_b 的 hash 就更新了——它只在 service_a 执行 go mod tidy 或 go build 时才刷新。这点在 CI 环境里常引发校验失败。


















