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