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/filepath"
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/modload"
30 "cmd/go/internal/str"
31 "cmd/go/internal/work"
32 )
33
34 var CmdTool = &base.Command{
35 Run: runTool,
36 UsageLine: "go tool [-n] command [args...]",
37 Short: "run specified go tool",
38 Long: `
39 Tool runs the go tool command identified by the arguments.
40
41 Go ships with a number of builtin tools, and additional tools
42 may be defined in the go.mod of the current module.
43
44 With no arguments it prints the list of known tools.
45
46 The -n flag causes tool to print the command that would be
47 executed but not execute it.
48
49 For more about each builtin tool command, see 'go doc cmd/<command>'.
50 `,
51 }
52
53 var toolN bool
54
55
56
57
58 func isGccgoTool(tool string) bool {
59 switch tool {
60 case "cgo", "fix", "cover", "godoc", "vet":
61 return true
62 }
63 return false
64 }
65
66 func init() {
67 base.AddChdirFlag(&CmdTool.Flag)
68 base.AddModCommonFlags(&CmdTool.Flag)
69 CmdTool.Flag.BoolVar(&toolN, "n", false, "")
70 }
71
72 func runTool(ctx context.Context, cmd *base.Command, args []string) {
73 if len(args) == 0 {
74 counter.Inc("go/subcommand:tool")
75 listTools(ctx)
76 return
77 }
78 toolName := args[0]
79
80 toolPath, err := base.ToolPath(toolName)
81 if err != nil {
82 if toolName == "dist" && len(args) > 1 && args[1] == "list" {
83
84
85
86
87
88
89 if impersonateDistList(args[2:]) {
90
91
92 counter.Inc("go/subcommand:tool-dist")
93 return
94 }
95 }
96
97 tool := loadModTool(ctx, toolName)
98 if tool != "" {
99 buildAndRunModtool(ctx, tool, args[1:])
100 return
101 }
102
103 counter.Inc("go/subcommand:tool-unknown")
104
105
106 _ = base.Tool(toolName)
107 } else {
108
109 counter.Inc("go/subcommand:tool-" + toolName)
110 }
111
112 if toolN {
113 cmd := toolPath
114 if len(args) > 1 {
115 cmd += " " + strings.Join(args[1:], " ")
116 }
117 fmt.Printf("%s\n", cmd)
118 return
119 }
120 args[0] = toolPath
121 toolCmd := &exec.Cmd{
122 Path: toolPath,
123 Args: args,
124 Stdin: os.Stdin,
125 Stdout: os.Stdout,
126 Stderr: os.Stderr,
127 }
128 err = toolCmd.Start()
129 if err == nil {
130 c := make(chan os.Signal, 100)
131 signal.Notify(c)
132 go func() {
133 for sig := range c {
134 toolCmd.Process.Signal(sig)
135 }
136 }()
137 err = toolCmd.Wait()
138 signal.Stop(c)
139 close(c)
140 }
141 if err != nil {
142
143
144
145
146
147 if e, ok := err.(*exec.ExitError); !ok || !e.Exited() || cfg.BuildX {
148 fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
149 }
150 base.SetExitStatus(1)
151 return
152 }
153 }
154
155
156 func listTools(ctx context.Context) {
157 f, err := os.Open(build.ToolDir)
158 if err != nil {
159 fmt.Fprintf(os.Stderr, "go: no tool directory: %s\n", err)
160 base.SetExitStatus(2)
161 return
162 }
163 defer f.Close()
164 names, err := f.Readdirnames(-1)
165 if err != nil {
166 fmt.Fprintf(os.Stderr, "go: can't read tool directory: %s\n", err)
167 base.SetExitStatus(2)
168 return
169 }
170
171 sort.Strings(names)
172 for _, name := range names {
173
174
175 name = strings.TrimSuffix(strings.ToLower(name), cfg.ToolExeSuffix())
176
177
178
179 if cfg.BuildToolchainName == "gccgo" && !isGccgoTool(name) {
180 continue
181 }
182 fmt.Println(name)
183 }
184
185 modload.InitWorkfile()
186 modload.LoadModFile(ctx)
187 modTools := slices.Sorted(maps.Keys(modload.MainModules.Tools()))
188 for _, tool := range modTools {
189 fmt.Println(tool)
190 }
191 }
192
193 func impersonateDistList(args []string) (handled bool) {
194 fs := flag.NewFlagSet("go tool dist list", flag.ContinueOnError)
195 jsonFlag := fs.Bool("json", false, "produce JSON output")
196 brokenFlag := fs.Bool("broken", false, "include broken ports")
197
198
199
200
201 _ = fs.Bool("v", false, "emit extra information")
202
203 if err := fs.Parse(args); err != nil || len(fs.Args()) > 0 {
204
205
206 return false
207 }
208
209 if !*jsonFlag {
210 for _, p := range platform.List {
211 if !*brokenFlag && platform.Broken(p.GOOS, p.GOARCH) {
212 continue
213 }
214 fmt.Println(p)
215 }
216 return true
217 }
218
219 type jsonResult struct {
220 GOOS string
221 GOARCH string
222 CgoSupported bool
223 FirstClass bool
224 Broken bool `json:",omitempty"`
225 }
226
227 var results []jsonResult
228 for _, p := range platform.List {
229 broken := platform.Broken(p.GOOS, p.GOARCH)
230 if broken && !*brokenFlag {
231 continue
232 }
233 if *jsonFlag {
234 results = append(results, jsonResult{
235 GOOS: p.GOOS,
236 GOARCH: p.GOARCH,
237 CgoSupported: platform.CgoSupported(p.GOOS, p.GOARCH),
238 FirstClass: platform.FirstClass(p.GOOS, p.GOARCH),
239 Broken: broken,
240 })
241 }
242 }
243 out, err := json.MarshalIndent(results, "", "\t")
244 if err != nil {
245 return false
246 }
247
248 os.Stdout.Write(out)
249 return true
250 }
251
252 func defaultExecName(importPath string) string {
253 var p load.Package
254 p.ImportPath = importPath
255 return p.DefaultExecName()
256 }
257
258 func loadModTool(ctx context.Context, name string) string {
259 modload.InitWorkfile()
260 modload.LoadModFile(ctx)
261
262 matches := []string{}
263 for tool := range modload.MainModules.Tools() {
264 if tool == name || defaultExecName(tool) == name {
265 matches = append(matches, tool)
266 }
267 }
268
269 if len(matches) == 1 {
270 return matches[0]
271 }
272
273 if len(matches) > 1 {
274 message := fmt.Sprintf("tool %q is ambiguous; choose one of:\n\t", name)
275 for _, tool := range matches {
276 message += tool + "\n\t"
277 }
278 base.Fatal(errors.New(message))
279 }
280
281 return ""
282 }
283
284 func buildAndRunModtool(ctx context.Context, tool string, args []string) {
285 work.BuildInit()
286 b := work.NewBuilder("")
287 defer func() {
288 if err := b.Close(); err != nil {
289 base.Fatal(err)
290 }
291 }()
292
293 pkgOpts := load.PackageOpts{MainOnly: true}
294 p := load.PackagesAndErrors(ctx, pkgOpts, []string{tool})[0]
295 p.Internal.OmitDebug = true
296 p.Internal.ExeName = p.DefaultExecName()
297
298 a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p)
299 a1.CacheExecutable = true
300 a := &work.Action{Mode: "go tool", Actor: work.ActorFunc(runBuiltTool), Args: args, Deps: []*work.Action{a1}}
301 b.Do(ctx, a)
302 }
303
304 func runBuiltTool(b *work.Builder, ctx context.Context, a *work.Action) error {
305 cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].BuiltTarget(), a.Args)
306
307 if toolN {
308 fmt.Println(strings.Join(cmdline, " "))
309 return nil
310 }
311
312
313
314 env := slices.Clip(cfg.OrigEnv)
315 env = base.AppendPATH(env)
316
317 toolCmd := &exec.Cmd{
318 Path: cmdline[0],
319 Args: cmdline,
320 Stdin: os.Stdin,
321 Stdout: os.Stdout,
322 Stderr: os.Stderr,
323 Env: env,
324 }
325 err := toolCmd.Start()
326 if err == nil {
327 c := make(chan os.Signal, 100)
328 signal.Notify(c)
329 go func() {
330 for sig := range c {
331 toolCmd.Process.Signal(sig)
332 }
333 }()
334 err = toolCmd.Wait()
335 signal.Stop(c)
336 close(c)
337 }
338 if err != nil {
339
340
341
342
343 if e, ok := err.(*exec.ExitError); ok {
344 base.SetExitStatus(e.ExitCode())
345 } else {
346 fmt.Fprintf(os.Stderr, "go tool %s: %s\n", filepath.Base(a.Deps[0].Target), err)
347 base.SetExitStatus(1)
348 }
349 }
350
351 return nil
352 }
353
View as plain text