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