同时改一个应用和它依赖的库,传统做法是在应用的 go.mod 里加 replace,然后记得别提交。go.work 把“这几个目录一起开发”变成一个不进入各模块 go.mod 的本地配置。这篇用真实命令验证它的效果与几个容易踩的点。
app 里 go run .:
lib v1.0.0(代理里已发布的版本),使用 e v1.3.0
app 直接使用 e v1.3.0
要在 app 里试用本地改过的 lib,传统做法是改 app/go.mod 加 replace(会改动要提交的文件):
加了 replace 之后 go run .:
lib 本地开发版(正在改的代码),使用 e v1.3.0
app 直接使用 e v1.3.0
app/go.mod 被改动的部分:replace lib => ../lib在 app/go.mod 里加 replace example.test/lib => ../lib 能用上本地代码,但改动的是要提交的文件:路径是本机的,别人拉到就编译失败;提交前还要记得删。
go work init ./app ./lib:rc=0
go.work 内容:
go 1.27.1
use (
./app
./lib
)
在工作区里沿用 GOFLAGS=-mod=mod 时 go run .(工作区模式不允许 -mod=mod):
go: -mod may only be set to readonly or vendor when in workspace mode, but it is set to "mod"
Remove the -mod flag to use the default readonly value,
or set GOWORK=off to disable workspace mode.
去掉 -mod=mod 之后在 app 目录里 go run .:
lib 本地开发版(正在改的代码),使用 e v1.3.0
app 直接使用 e v1.3.0
app/go.mod 与之前相同?True
go env GOWORK(在 app 里):<gtmp>/ws/go.work
工作区里 go list -m all:
app
lib
e v1.3.0
GOWORK=off 之后 go run .(回到发布的 lib):
lib v1.0.0(代理里已发布的版本),使用 e v1.3.0
app 直接使用 e v1.3.0go.work 只有 go 版本与 use 列表(也可以有 replace),通常不提交(或者只放在 monorepo 根)。有了它,在任一 use 的目录里构建,都会把这些目录当作主模块的集合:app 直接用本地的 lib,两个 go.mod 都不动。工作区模式有自己的限制,比如不允许 -mod=mod(实验里的报错),要用默认的 readonly。
工作区里 go list -m e → e v1.3.0 在 lib 目录里(也在工作区中)go list -m e → e v1.3.0 lib 目录里 GOWORK=off:go list -m e → e v1.1.0
工作区里的构建列表是所有 use 模块的需求合在一起做最小版本选择。所以 lib 自己只要 e v1.1.0,在工作区里却得到 v1.3.0(app 要求的)。好处是联合开发时版本一致;风险是在工作区里“能跑”不代表 lib 单独发布后也能跑——发布前用 GOWORK=off 再验证一次。
go work edit -json:
{
"Go": "1.27.1",
"Use": [
{
"DiskPath": "./app"
},
{
"DiskPath": "./lib"
}
]
}
go work init ./app 之后 go work use ./lib:rc=0
再 go work use ./不存在 :go: directory nothing does not exist
go work sync:rc=0
go.work 内容:
go 1.27.1
use (
./app
./lib
)go work use 增删目录,go work edit -json 供脚本读取或修改,go work sync 把工作区算出的版本同步回各模块的 go.mod(这个实验里已是同步状态,没有观察到它改了什么)。
工作区里在 app 目录 go mod tidy:rc=0;app/go.mod 变了吗?False GOWORK=off 构建:rc=0
go mod tidy 只整理当前这一个模块的 go.mod,不看工作区,所以结果与有没有 go.work 无关。