Go 项目里有个很常见的小混乱:代码生成用一个版本的 stringer,本地 lint 用另一个版本,CI 里又临时 go install latest。最后问题不是代码错,而是大家手里的工具根本不是同一套。Go 1.24 的 tool 指令,就是专门来收拾这类工程卫生问题的。
以前很多团队会放一个 tools.go,用空导入把工具依赖塞进模块里。能用,但看起来绕,维护也容易忘。现在可以把 Go 工具直接记录到 go.mod,再用 go tool 运行,版本和依赖都能跟项目一起管理。
它解决的不是语法问题,是团队一致性
如果你是一个人写小项目,直接 go install 工具也没什么。但团队项目不一样。代码生成、mock、lint、protobuf、swagger,这些工具只要版本不一致,就可能生成不同文件,或者 CI 和本地结果对不上。
tool 指令的价值在于:工具也成为模块依赖的一部分。大家 clone 项目后,不需要猜该装哪个版本;CI 也不应该每次拉最新工具。
以前的 tools.go 为什么尴尬
旧做法通常是建一个 tools.go:
//go:build tools package tools import _ "golang.org/x/tools/cmd/stringer"
这招能把工具写进 go.mod,但它本质上是用一个不会参与构建的 Go 文件来“骗”模块系统。新人经常问:这个文件为什么存在?为什么 import 了不用?为什么 build tag 是 tools?
Go 1.24 后,工具依赖有了更直白的位置:直接写进 go.mod 的 tool 块。
最小用法
你可以用 go get -tool 把工具加入模块。之后它会出现在 go.mod 里,工具版本也会被 go.sum 管起来。
go get -tool golang.org/x/tools/cmd/stringer
go.mod 里会出现类似这样的内容:
module example.com/app
go 1.24
tool (
golang.org/x/tools/cmd/stringer
)
运行工具时,不需要依赖全局 PATH 里刚好有某个版本,可以使用:
go tool stringer -type=State
它和 go install 有什么区别
go install pkg@version 更像是给你当前机器安装一个命令;tool 指令更像是项目声明“我需要这些工具”。前者偏个人环境,后者偏项目协作。
生产团队里我更喜欢后者。因为代码仓库本身就应该告诉你:生成代码用哪个工具、lint 用哪个版本、CI 应该跑什么。
CI 里怎么落地
CI 里最忌讳的是临时安装 latest。今天过、明天不过,经常不是代码问题,而是工具升级了。使用 tool 指令后,CI 可以直接跑 go tool,让版本跟随模块。
steps: - run: go mod download - run: go tool stringer -type=State - run: go test ./...
如果你们有生成代码检查,可以再加一步 git diff --exit-code,确保生成文件没有漏提交。这样新人本地、老同事本地和 CI 环境会更一致。
哪些工具适合放进去
我会优先放“和项目构建、生成、检查强相关”的 Go 工具,比如 stringer、mockgen、protobuf 相关 Go 插件、内部代码生成器、特定版本的 lint 工具。
但不要把所有命令都往里塞。比如系统级工具、Docker、Node 工具链、数据库命令行,这些不是 Go 模块工具,还是交给对应的环境管理更清楚。
多模块仓库要注意边界
如果你的仓库是 monorepo,里面有多个 Go module,不要想当然认为根目录声明一次所有子模块都能自然共享。每个 module 的 go.mod 管的是自己的依赖和工具。
我的建议是:哪个 module 需要哪个工具,就在那个 module 里声明。公共脚本可以封装命令,但不要让工具版本散落在 README、Makefile 和 CI 配置里。
我的迁移建议
- 先列出现有
tools.go和 CI 里临时安装的 Go 工具。 - 用
go get -tool把项目相关工具加入go.mod。 - 把脚本里的直接命令改成
go tool xxx。 - 删除不再需要的
tools.go,并跑go mod tidy。 - CI 不再安装 latest,统一使用模块声明的工具。
- 在 README 里写清楚最低 Go 版本要求是 1.24。
别忽略版本门槛
这是 Go 1.24 的能力。如果你的项目还需要支持更老版本 Go,就不能贸然迁移。公共库尤其要小心,别因为内部工具管理变舒服,就把用户的最低 Go 版本抬高了。
服务端项目通常好处理,团队升级工具链即可;开源库则要看目标用户。工程化能力再好,也要和兼容性一起评估。
最后说句实在话
tool 指令不是那种让你代码性能提升 30% 的特性,但它能减少很多“为什么我本地生成的文件和你不一样”的无聊争吵。工具版本一致,团队协作会顺很多。
我建议 Go 1.24 项目尽快把 tools.go 和 CI 里的 go install latest 梳理一遍。不是为了追新,而是为了让项目自己说清楚:我依赖哪些工具、这些工具应该用什么版本。
参考资料:Go 1.24 Release Notes、Go Modules Reference、go command 文档。

Go sync.Pool 别当缓存用:我在高并发接口里踩过的对象复用坑
