Go Tool Directive
Go 1.24 引入的 tool directive,將開發工具依賴從 require 中分離,避免汙染下游消費者
Overview##
開發 Go 專案時,你幾乎一定會用到一些輔助工具:linter(golangci-lint)、formatter(gofumpt)、code generator(stringer)。這些工具本身是用 Go 寫的,但它們不是你的程式碼在 runtime 需要的東西。
問題是:在 Go 1.24 之前,沒有官方的方式來區分「我的程式碼需要這個 package」和「我的開發流程需要這個工具」。大家要嘛把工具塞進 require,要嘛用社群慣例 tools.go 來處理。兩種做法都有缺陷。
Go 1.24 引入了 tool directive — 在 go.mod 中正式宣告開發工具依賴,讓它們的 transitive dependencies 不會傳播給下游消費者。
Architecture##
Go 的依賴傳播機制###
Go modules 使用 Minimum Version Selection(MVS) 來解析依賴。當你的 module 被別人 require 時,你的 go.mod 中所有 require 的 package 都會進入對方的依賴圖。
這對 runtime 依賴來說完全合理,但對開發工具來說就是災難:
graph TD
A[your-app] -->|require| B[my-library]
B -->|require| C[runtime dep A]
B -->|require| D[runtime dep B]
B -->|require| E[golangci-lint]
E -->|transitive| F[160+ indirect deps]
F -->|pollutes| A
style E fill:#f66,stroke:#333
style F fill:#f66,stroke:#333
一個 linter 像 golangci-lint 自身就有 160+ 個 transitive dependencies。如果你把它放在 require 裡,任何 import 你的 library 的專案都會被迫拉進這些完全無關的依賴。
Warning
更隱蔽的問題是 MVS 的版本提升效應。如果 linter 依賴了 google.golang.org/api v0.198.0,而下游專案原本用 v0.114.0,Go 會自動選擇較高的版本。你的開發工具可能無聲地升級了別人的 production 依賴。
三種管理方式的比較###
| 做法 | 語法 | 傳播給下游 | 官方支援 |
|---|---|---|---|
require(直接放) | require example.com/tool v1.0.0 | 會,包含所有 transitive deps | 不適用於 CLI 工具 |
tools.go + build tag | //go:build tools + blank import | 會(但不編譯進 binary) | 社群慣例,非官方 |
tool directive | tool example.com/tool | 不會 | Go 1.24+ 官方 |
Note
tools.go 是 Go 1.23 以前最常見的做法。它透過 //go:build tools 的 build tag 來排除編譯,但依賴仍然在 require 裡,所以下游消費者還是會被汙染。tool directive 是官方對這個問題的根本解決方案。
Setup##
Prerequisites###
- Go 1.24 以上(
tooldirective 在此版本引入)
go version
# go version go1.24.6 darwin/arm64
如果你的 go.mod 中 go 版本低於 1.24,需要先升級:
go mod edit -go=1.24
Usage##
Basic Usage###
宣告工具依賴:
使用 go get -tool 一步完成 — 它會同時加入 tool directive 和對應的 require:
# 新增一個工具依賴
go get -tool golang.org/x/tools/cmd/stringer
執行後,go.mod 會變成:
module example.com/myproject
go 1.24
tool golang.org/x/tools/cmd/stringer
require golang.org/x/tools v0.29.0
執行工具:
# 用短名稱執行(取 import path 的最後一段)
go tool stringer
# 如果短名稱衝突,使用完整路徑
go tool golang.org/x/tools/cmd/stringer
# 列出所有已註冊的工具
go tool
移除工具:
go mod edit -droptool golang.org/x/tools/cmd/stringer
go mod tidy
Tip
go tool 列出的工具包含 Go 內建工具(如 vet、pprof)和你在 go.mod 中宣告的工具。你宣告的工具會使用與主 module 相同的 module graph,所以 replace 和 exclude directive 也適用。
Advanced Usage###
宣告多個工具:
// go.mod
module example.com/myproject
go 1.24
tool (
github.com/golangci/golangci-lint/cmd/golangci-lint
github.com/daixiang0/gci
mvdan.cc/gofumpt
golang.org/x/vuln/cmd/govulncheck
)
require (
// runtime 依賴
github.com/google/uuid v1.6.0
go.uber.org/zap v1.27.0
// 工具的 require(go get -tool 會自動加)
github.com/golangci/golangci-lint v1.62.2
github.com/daixiang0/gci v0.13.7
mvdan.cc/gofumpt v0.9.1
golang.org/x/vuln v1.1.4
)
依賴傳播的差異:
graph TD
subgraph Before
A1[consumer] -->|require| B1[my-library]
B1 -->|require| C1[uuid]
B1 -->|require| D1[zap]
B1 -->|require| E1[golangci-lint + 160 deps]
end
subgraph After
A2[consumer] -->|require| B2[my-library]
B2 -->|require| C2[uuid]
B2 -->|require| D2[zap]
B2 -.->|tool, isolated| E2[golangci-lint + 160 deps]
end
style E1 fill:#f66,stroke:#333
style E2 fill:#4a4,stroke:#333
從 tools.go 遷移:
如果你的專案還在用 tools.go 的舊模式:
// tools/tools.go(舊做法,可以刪除)
//go:build tools
package tools
import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "mvdan.cc/gofumpt"
)
遷移步驟:
# 1. 對每個工具執行 go get -tool
go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint
go get -tool mvdan.cc/gofumpt
# 2. 刪除 tools.go
rm tools/tools.go
# 3. 清理不再需要的依賴
go mod tidy
常見搭配情境###
| 情境 | 工具範例 | tool directive path |
|---|---|---|
| Linting | golangci-lint | github.com/golangci/golangci-lint/cmd/golangci-lint |
| Formatting | gofumpt, gci | mvdan.cc/gofumpt |
| Code generation | stringer, enumer | golang.org/x/tools/cmd/stringer |
| Vulnerability scan | govulncheck | golang.org/x/vuln/cmd/govulncheck |
| API docs | swag | github.com/swaggo/swag/cmd/swag |
Configuration Reference##
| 指令 | 用途 | 範例 |
|---|---|---|
tool <path> | 在 go.mod 中宣告工具 | tool mvdan.cc/gofumpt |
go get -tool <path> | 新增工具(自動加 tool + require) | go get -tool mvdan.cc/gofumpt |
go mod edit -tool <path> | 手動加入 tool directive | go mod edit -tool mvdan.cc/gofumpt |
go mod edit -droptool <path> | 移除 tool directive | go mod edit -droptool mvdan.cc/gofumpt |
go tool <name> | 執行已註冊的工具 | go tool gofumpt -w . |
go tool | 列出所有可用工具 | — |
Warning
tool directive 需要對應的 require 才能運作。如果你手動用 go mod edit -tool 加入,記得也要 go get 該 module。使用 go get -tool 則會自動處理兩者。
Summary##
tooldirective 是 Go 1.24+ 管理開發工具依賴的官方方案。 它將工具的 transitive dependencies 隔離,不會傳播給下游消費者。- 永遠不要把 CLI 工具放在
require裡。 這會汙染所有 import 你的 module 的專案的依賴樹,還可能透過 MVS 無聲地升級別人的 production 依賴版本。 - 從
tools.go遷移很簡單。go get -tool→ 刪除tools.go→go mod tidy,三步完成。 - 使用
go get -tool新增、go tool執行、go mod edit -droptool移除。 這是完整的工具生命週期管理。
留言 (0)
登入後即可留言