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