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 buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []string) {
281
282
283 cfg.ForceHost()
284
285
286
287
288 modload.RootMode = modload.NoRoot
289
290 runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
291 cmdline := str.StringList(a.Deps[0].BuiltTarget(), a.Args)
292 return runBuiltTool(toolName, nil, cmdline)
293 }
294
295 buildAndRunTool(ctx, tool, args, runFunc)
296 }
297
298 func buildAndRunModtool(ctx context.Context, toolName, tool string, args []string) {
299 runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
300
301
302
303 cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].BuiltTarget(), a.Args)
304
305
306 env := slices.Clip(cfg.OrigEnv)
307 env = base.AppendPATH(env)
308
309 return runBuiltTool(toolName, env, cmdline)
310 }
311
312 buildAndRunTool(ctx, tool, args, runFunc)
313 }
314
315 func buildAndRunTool(ctx context.Context, tool string, args []string, runTool work.ActorFunc) {
316 work.BuildInit()
317 b := work.NewBuilder("")
318 defer func() {
319 if err := b.Close(); err != nil {
320 base.Fatal(err)
321 }
322 }()
323
324 pkgOpts := load.PackageOpts{MainOnly: true}
325 p := load.PackagesAndErrors(ctx, pkgOpts, []string{tool})[0]
326 p.Internal.OmitDebug = true
327 p.Internal.ExeName = p.DefaultExecName()
328
329 a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p)
330 a1.CacheExecutable = true
331 a := &work.Action{Mode: "go tool", Actor: runTool, Args: args, Deps: []*work.Action{a1}}
332 b.Do(ctx, a)
333 }
334
335 func runBuiltTool(toolName string, env, cmdline []string) error {
336 if toolN {
337 fmt.Println(strings.Join(cmdline, " "))
338 return nil
339 }
340
341 toolCmd := &exec.Cmd{
342 Path: cmdline[0],
343 Args: cmdline,
344 Stdin: os.Stdin,
345 Stdout: os.Stdout,
346 Stderr: os.Stderr,
347 Env: env,
348 }
349 err := toolCmd.Start()
350 if err == nil {
351 c := make(chan os.Signal, 100)
352 signal.Notify(c)
353 go func() {
354 for sig := range c {
355 toolCmd.Process.Signal(sig)
356 }
357 }()
358 err = toolCmd.Wait()
359 signal.Stop(c)
360 close(c)
361 }
362 if err != nil {
363
364
365
366
367
368 e, ok := err.(*exec.ExitError)
369 if !ok || !e.Exited() || cfg.BuildX {
370 fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
371 }
372 if ok {
373 base.SetExitStatus(e.ExitCode())
374 } else {
375 base.SetExitStatus(1)
376 }
377 }
378
379 return nil
380 }
381
View as plain text