bigywater.tech
Go · 44

go.work 工作区:多模块开发时 replace 的替代方案

同时改一个应用和它依赖的库,传统做法是在应用的 go.mod 里加 replace,然后记得别提交。go.work 把“这几个目录一起开发”变成一个不进入各模块 go.mod 的本地配置。这篇用真实命令验证它的效果与几个容易踩的点。

0 行改动
使用 go.work 后,app/go.mod 与使用前完全相同(replace 方案要多一行)
e v1.3.0 / v1.1.0
同一个 lib 在工作区里与单独构建时用到的 e 版本不同
GOWORK=off
关掉工作区,立即回到发布的 lib v1.0.0

replace 的问题

实测 · 本机执行输出
  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

实测 · 本机执行输出
  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.0

go.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(这个实验里已是同步状态,没有观察到它改了什么)。

与 go mod tidy

实测 · 本机执行输出
  工作区里在 app 目录 go mod tidy:rc=0;app/go.mod 变了吗?False
  GOWORK=off 构建:rc=0

go mod tidy 只整理当前这一个模块的 go.mod,不看工作区,所以结果与有没有 go.work 无关。

分步演示

go.work 工作区
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。lib v1.0.0 与 e 的两个版本放在本机 file:// 代理里(GOPROXY=file://…,GOSUMDB=off),不联网;命令输出来自真实运行;沿用 GOFLAGS=-mod=mod 的报错是实验中故意保留的
    • 没有验证:go.work 里的 replace 与 toolchain 指令、go.work.sum 的作用、工作区与 vendor 的关系、go test ./... 在工作区里的范围(只试了 go run 与 go list)、在 CI 中是否应当提交 go.work 的利弊(只是常见建议,没有验证)
    京ICP备2026067502号-1京公网安备11010502063487号