1
2
3
4
5
6 package tool
7
8 import (
9 "cmd/internal/telemetry/counter"
10 "context"
11 "encoding/json"
12 "errors"
13 "flag"
14 "fmt"
15 "go/build"
16 "internal/platform"
17 "maps"
18 "os"
19 "os/exec"
20 "os/signal"
21 "path"
22 "slices"
23 "sort"
24 "strings"
25
26 "cmd/go/internal/base"
27 "cmd/go/internal/cfg"
28 "cmd/go/internal/load"
29 "cmd/go/internal/modindex"
30 "cmd/go/internal/modload"
31 "cmd/go/internal/str"
32 "cmd/go/internal/work"
33 )
34
35 var CmdTool = &base.Command{
36 Run: runTool,
37 UsageLine: "go tool [-n] command [args...]",
38 Short: "run specified go tool",
39 Long: `
40 Tool runs the go tool command identified by the arguments.
41
42 Go ships with a number of builtin tools, and additional tools
43 may be defined in the go.mod of the current module.
44
45 With no arguments it prints the list of known tools.
46
47 The -n flag causes tool to print the command that would be
48 executed but not execute it.
49
50 The -modfile=file.mod build flag causes tool to use an alternate file
51 instead of the go.mod in the module root directory.
52
53 Tool also provides the -C, -overlay, and -modcacherw build flags.
54
55 The go command places $GOROOT/bin at the beginning of $PATH in the
56 environment of commands run via tool directives, so that they use the
57 same 'go' as the parent 'go tool'.
58
59 For more about build flags, see 'go help build'.
60
61 For more about each builtin tool command, see 'go doc cmd/<command>'.
62 `,
63 }
64
65 var toolN bool
66
67
68
69
70 func isGccgoTool(tool string) bool {
71 switch tool {
72 case "cgo", "fix", "cover", "godoc", "vet":
73 return true
74 }
75 return false
76 }
77
78 func init() {
79 base.AddChdirFlag(&CmdTool.Flag)
80 base.AddModCommonFlags(&CmdTool.Flag)
81 CmdTool.Flag.BoolVar(&toolN, "n", false, "")
82 }
83
84 func runTool(ctx context.Context, cmd *base.Command, args []string) {
85 moduleLoaderState := modload.NewState()
86 if len(args) == 0 {
87 counter.Inc("go/subcommand:tool")
88 listTools(moduleLoaderState, ctx)
89 return
90 }
91 toolName := args[0]
92
93 toolPath, err := base.ToolPath(toolName)
94 if err != nil {
95 if toolName == "dist" && len(args) > 1 && args[1] == "list" {
96
97
98
99
100
101
102 if impersonateDistList(args[2:]) {
103
104
105 counter.Inc("go/subcommand:tool-dist")
106 return
107 }
108 }
109
110
111
112
113 if tool := loadBuiltinTool(toolName); tool != "" {
114
115 counter.Inc("go/subcommand:tool-" + toolName)
116 buildAndRunBuiltinTool(moduleLoaderState, ctx, toolName, tool, args[1:])
117 return
118 }
119
120
121 tool := loadModTool(moduleLoaderState, ctx, toolName)
122 if tool != "" {
123 buildAndRunModtool(moduleLoaderState, ctx, toolName, tool, args[1:])
124 return
125 }
126
127 counter.Inc("go/subcommand:tool-unknown")
128
129
130 _ = base.Tool(toolName)
131 } else {
132
133 counter.Inc("go/subcommand:tool-" + toolName)
134 }
135
136 runBuiltTool(toolName, nil, append([]string{toolPath}, args[1:]...))
137 }
138
139
140 func listTools(loaderstate *modload.State, ctx context.Context) {
141 f, err := os.Open(build.ToolDir)
142 if err != nil {
143 fmt.Fprintf(os.Stderr, "go: no tool directory: %s\n", err)
144 base.SetExitStatus(2)
145 return
146 }
147 defer f.Close()
148 names, err := f.Readdirnames(-1)
149 if err != nil {
150 fmt.Fprintf(os.Stderr, "go: can't read tool directory: %s\n", err)
151 base.SetExitStatus(2)
152 return
153 }
154
155 sort.Strings(names)
156 for _, name := range names {
157
158
159 name = strings.TrimSuffix(strings.ToLower(name), cfg.ToolExeSuffix())
160
161
162
163 if cfg.BuildToolchainName == "gccgo" && !isGccgoTool(name) {
164 continue
165 }
166 fmt.Println(name)
167 }
168
169 loaderstate.InitWorkfile()
170 modload.LoadModFile(loaderstate, ctx)
171 modTools := slices.Sorted(maps.Keys(loaderstate.MainModules.Tools()))
172 for _, tool := range modTools {
173 fmt.Println(tool)
174 }
175 }
176
177 func impersonateDistList(args []string) (handled bool) {
178 fs := flag.NewFlagSet("go tool dist list", flag.ContinueOnError)
179 jsonFlag := fs.Bool("json", false, "produce JSON output")
180 brokenFlag := fs.Bool("broken", false, "include broken ports")
181
182
183
184
185 _ = fs.Bool("v", false, "emit extra information")
186
187 if err := fs.Parse(args); err != nil || len(fs.Args()) > 0 {
188
189
190 return false
191 }
192
193 if !*jsonFlag {
194 for _, p := range platform.List {
195 if !*brokenFlag && platform.Broken(p.GOOS, p.GOARCH) {
196 continue
197 }
198 fmt.Println(p)
199 }
200 return true
201 }
202
203 type jsonResult struct {
204 GOOS string
205 GOARCH string
206 CgoSupported bool
207 FirstClass bool
208 Broken bool `json:",omitempty"`
209 }
210
211 var results []jsonResult
212 for _, p := range platform.List {
213 broken := platform.Broken(p.GOOS, p.GOARCH)
214 if broken && !*brokenFlag {
215 continue
216 }
217 if *jsonFlag {
218 results = append(results, jsonResult{
219 GOOS: p.GOOS,
220 GOARCH: p.GOARCH,
221 CgoSupported: platform.CgoSupported(p.GOOS, p.GOARCH),
222 FirstClass: platform.FirstClass(p.GOOS, p.GOARCH),
223 Broken: broken,
224 })
225 }
226 }
227 out, err := json.MarshalIndent(results, "", "\t")
228 if err != nil {
229 return false
230 }
231
232 os.Stdout.Write(out)
233 return true
234 }
235
236 func defaultExecName(importPath string) string {
237 var p load.Package
238 p.ImportPath = importPath
239 return p.DefaultExecName()
240 }
241
242 func loadBuiltinTool(toolName string) string {
243 if !base.ValidToolName(toolName) {
244 return ""
245 }
246 cmdTool := path.Join("cmd", toolName)
247 if !modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, cmdTool) {
248 return ""
249 }
250
251
252 p := &load.Package{PackagePublic: load.PackagePublic{Name: "main", ImportPath: cmdTool, Goroot: true}}
253 if load.InstallTargetDir(p) != load.ToTool {
254 return ""
255 }
256 return cmdTool
257 }
258
259 func loadModTool(loaderstate *modload.State, ctx context.Context, name string) string {
260 loaderstate.InitWorkfile()
261 modload.LoadModFile(loaderstate, ctx)
262
263 matches := []string{}
264 for tool := range loaderstate.MainModules.Tools() {
265 if tool == name || defaultExecName(tool) == name {
266 matches = append(matches, tool)
267 }
268 }
269
270 if len(matches) == 1 {
271 return matches[0]
272 }
273
274 if len(matches) > 1 {
275 message := fmt.Sprintf("tool %q is ambiguous; choose one of:\n\t", name)
276 for _, tool := range matches {
277 message += tool + "\n\t"
278 }
279 base.Fatal(errors.New(message))
280 }
281
282 return ""
283 }
284
285 func builtTool(runAction *work.Action) string {
286 linkAction := runAction.Deps[0]
287 if toolN {
288
289
290
291
292
293
294
295
296
297
298
299
300
301 if cached := linkAction.CachedExecutable(); cached != "" {
302 return cached
303 }
304 }
305 return linkAction.BuiltTarget()
306 }
307
308 func buildAndRunBuiltinTool(loaderstate *modload.State, ctx context.Context, toolName, tool string, args []string) {
309
310
311 cfg.ForceHost()
312
313
314
315
316 loaderstate.RootMode = modload.NoRoot
317
318 runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
319 cmdline := str.StringList(builtTool(a), a.Args)
320 return runBuiltTool(toolName, nil, cmdline)
321 }
322
323 buildAndRunTool(loaderstate, ctx, tool, args, runFunc)
324 }
325
326 func buildAndRunModtool(loaderstate *modload.State, ctx context.Context, toolName, tool string, args []string) {
327 runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
328
329
330
331 cmdline := str.StringList(work.FindExecCmd(), builtTool(a), a.Args)
332
333
334 env := slices.Clip(cfg.OrigEnv)
335 env = base.AppendPATH(env)
336
337 return runBuiltTool(toolName, env, cmdline)
338 }
339
340 buildAndRunTool(loaderstate, ctx, tool, args, runFunc)
341 }
342
343 func buildAndRunTool(loaderstate *modload.State, ctx context.Context, tool string, args []string, runTool work.ActorFunc) {
344 work.BuildInit(loaderstate)
345 b := work.NewBuilder("", loaderstate.VendorDirOrEmpty)
346 defer func() {
347 if err := b.Close(); err != nil {
348 base.Fatal(err)
349 }
350 }()
351
352 pkgOpts := load.PackageOpts{MainOnly: true}
353 p := load.PackagesAndErrors(loaderstate, ctx, pkgOpts, []string{tool})[0]
354 p.Internal.OmitDebug = true
355 p.Internal.ExeName = p.DefaultExecName()
356
357 a1 := b.LinkAction(loaderstate, work.ModeBuild, work.ModeBuild, p)
358 a1.CacheExecutable = true
359 a := &work.Action{Mode: "go tool", Actor: runTool, Args: args, Deps: []*work.Action{a1}}
360 b.Do(ctx, a)
361 }
362
363 func runBuiltTool(toolName string, env, cmdline []string) error {
364 if toolN {
365 fmt.Println(strings.Join(cmdline, " "))
366 return nil
367 }
368
369 toolCmd := &exec.Cmd{
370 Path: cmdline[0],
371 Args: cmdline,
372 Stdin: os.Stdin,
373 Stdout: os.Stdout,
374 Stderr: os.Stderr,
375 Env: env,
376 }
377 err := toolCmd.Start()
378 if err == nil {
379 c := make(chan os.Signal, 100)
380 signal.Notify(c)
381 go func() {
382 for sig := range c {
383 toolCmd.Process.Signal(sig)
384 }
385 }()
386 err = toolCmd.Wait()
387 signal.Stop(c)
388 close(c)
389 }
390 if err != nil {
391
392
393
394
395
396 e, ok := err.(*exec.ExitError)
397 if !ok || !e.Exited() || cfg.BuildX {
398 fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
399 }
400 if ok {
401 base.SetExitStatus(e.ExitCode())
402 } else {
403 base.SetExitStatus(1)
404 }
405 }
406
407 return nil
408 }
409
View as plain text