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