Go 1.12+ 多模块仓库合法结构是子目录各自定义独立 go.mod,且 module 路径须与 import 路径一致;典型结构含主模块根目录go.mod 和 cmd/、pkg/ 下的子模块 go.mod,internal/ 下不设 go.mod。
Go 官方不支持“一个仓库多个 go.mod 文件共存于同一级目录”,但允许在子目录中各自定义独立模块。只要每个 go.mod 文件所在目录是该模块的根(即 module 声明的路径能通过相对路径从该目录解析),就合法。
典型合规结构:
myorg/repo/
├── go.mod # 主模块:github.com/myorg/repo
├── cmd/
│ ├── api-server/
│ │ └── go.mod # 子模块:github.com/myorg/repo/cmd/api-server
│ └── worker/
│ └── go.mod # 子模块:github.com/myorg/repo/cmd/worker
├── internal/
│ └── utils/ # 不可被外部 import,无需 go.mod
└── pkg/
└── storage/ # 可导出子库,可配独立 go.mod(如需不同依赖版本)关键点:go mod init 时模块路径必须与实际 import 路径一致;否则 go build 或 go get 会失败。
不是所有子目录都需要自己的 go.mod。只有当它满足以下至少一项时才值得拆:
pkg/storage 依赖 cloud.google.com/go@v0.110.0,而主模块锁在 v0.105.0)go get(此时模块路径应为 github.com/myorg/repo/pkg/storage)cd pkg/storage && go test)反例:仅为了“看起来更清晰”而在 internal/handler 下加 go.mod —— 这会导致 go list -m all 输出混乱,且无法被主模块直接 import(Go 拒绝 import 同一仓库内其他模块的 internal 包)。
当你本地同时修改主模块和某个子模块(比如 cmd/api-server 和 pkg/storage),又不想反复 go mod edit -replace,go.work 是唯一推荐方式。
在仓库根目录运行:
go work init go work use . go work use ./pkg/storage go work use ./cmd/api-server
这会生成 go.work 文件,内容类似:
go 1.21use ( . ./pkg/storage ./cmd/api-server )
此后在任意子目录执行 go run、go test,都会按 go.work 中声明的路径解析模块,跳过 proxy.golang.org 拉取本地代码。注意:go.work 不提交到 CI,仅用于本地开发协同。
这是多模块仓库最常导致 import cycle not allowed 或 cannot find module providing package 的原因。
检查步骤:
import "github.com/myorg/repo/pkg/storage" 对应的目录下 go.mod 第一行是 module github.com/myorg/repo/pkg/storage
module github.com/myorg/repo/pkg/storage/v2),则 import 必须带 /v2
go.mod 中不能出现 replace github.com/myorg/repo/pkg/storage => ./pkg/storage —— 这会破坏模块一致性,且 go.work 已提供更干净的替代方案真正棘手的是混合使用 replace 和 go.work:Go 会优先用 replace,导致 go.work 失效,而且错误提示极不直观。