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