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