@@ -18,14 +18,15 @@ import (
18
18
"os"
19
19
"os/exec"
20
20
"os/signal"
21
- "path/filepath "
21
+ "path"
22
22
"slices"
23
23
"sort"
24
24
"strings"
25
25
26
26
"cmd/go/internal/base"
27
27
"cmd/go/internal/cfg"
28
28
"cmd/go/internal/load"
29
+ "cmd/go/internal/modindex"
29
30
"cmd/go/internal/modload"
30
31
"cmd/go/internal/str"
31
32
"cmd/go/internal/work"
@@ -101,9 +102,20 @@ func runTool(ctx context.Context, cmd *base.Command, args []string) {
101
102
}
102
103
}
103
104
105
+ // See if tool can be a builtin tool. If so, try to build and run it.
106
+ // buildAndRunBuiltinTool will fail if the install target of the loaded package is not
107
+ // the tool directory.
108
+ if tool := loadBuiltinTool (toolName ); tool != "" {
109
+ // Increment a counter for the tool subcommand with the tool name.
110
+ counter .Inc ("go/subcommand:tool-" + toolName )
111
+ buildAndRunBuiltinTool (ctx , toolName , tool , args [1 :])
112
+ return
113
+ }
114
+
115
+ // Try to build and run mod tool.
104
116
tool := loadModTool (ctx , toolName )
105
117
if tool != "" {
106
- buildAndRunModtool (ctx , tool , args [1 :])
118
+ buildAndRunModtool (ctx , toolName , tool , args [1 :])
107
119
return
108
120
}
109
121
@@ -116,47 +128,7 @@ func runTool(ctx context.Context, cmd *base.Command, args []string) {
116
128
counter .Inc ("go/subcommand:tool-" + toolName )
117
129
}
118
130
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 // in case the tool wants to re-exec itself, e.g. cmd/dist
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
- // Only print about the exit status if the command
150
- // didn't even run (not an ExitError) or it didn't exit cleanly
151
- // or we're printing command lines too (-x mode).
152
- // Assume if command exited cleanly (even with non-zero status)
153
- // it printed any messages it wanted to print.
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
- }
131
+ runBuiltTool (toolName , nil , append ([]string {toolPath }, args [1 :]... ))
160
132
}
161
133
162
134
// listTools prints a list of the available tools in the tools directory.
@@ -262,6 +234,23 @@ func defaultExecName(importPath string) string {
262
234
return p .DefaultExecName ()
263
235
}
264
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
+ // Create a fake package and check to see if it would be installed to the tool directory.
246
+ // If not, it's not a builtin tool.
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
+
265
254
func loadModTool (ctx context.Context , name string ) string {
266
255
modload .InitWorkfile ()
267
256
modload .LoadModFile (ctx )
@@ -288,7 +277,42 @@ func loadModTool(ctx context.Context, name string) string {
288
277
return ""
289
278
}
290
279
291
- func buildAndRunModtool (ctx context.Context , tool string , args []string ) {
280
+ func buildAndRunBuiltinTool (ctx context.Context , toolName , tool string , args []string ) {
281
+ // Override GOOS and GOARCH for the build to build the tool using
282
+ // the same GOOS and GOARCH as this go command.
283
+ cfg .ForceHost ()
284
+
285
+ // Ignore go.mod and go.work: we don't need them, and we want to be able
286
+ // to run the tool even if there's an issue with the module or workspace the
287
+ // user happens to be in.
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
+ // Use the ExecCmd to run the binary, as go run does. ExecCmd allows users
301
+ // to provide a runner to run the binary, for example a simulator for binaries
302
+ // that are cross-compiled to a different platform.
303
+ cmdline := str .StringList (work .FindExecCmd (), a .Deps [0 ].BuiltTarget (), a .Args )
304
+ // Use same environment go run uses to start the executable:
305
+ // the original environment with cfg.GOROOTbin added to the path.
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 ) {
292
316
work .BuildInit ()
293
317
b := work .NewBuilder ("" )
294
318
defer func () {
@@ -304,23 +328,16 @@ func buildAndRunModtool(ctx context.Context, tool string, args []string) {
304
328
305
329
a1 := b .LinkAction (work .ModeBuild , work .ModeBuild , p )
306
330
a1 .CacheExecutable = true
307
- a := & work.Action {Mode : "go tool" , Actor : work . ActorFunc ( runBuiltTool ) , Args : args , Deps : []* work.Action {a1 }}
331
+ a := & work.Action {Mode : "go tool" , Actor : runTool , Args : args , Deps : []* work.Action {a1 }}
308
332
b .Do (ctx , a )
309
333
}
310
334
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
-
335
+ func runBuiltTool (toolName string , env , cmdline []string ) error {
314
336
if toolN {
315
337
fmt .Println (strings .Join (cmdline , " " ))
316
338
return nil
317
339
}
318
340
319
- // Use same environment go run uses to start the executable:
320
- // the original environment with cfg.GOROOTbin added to the path.
321
- env := slices .Clip (cfg .OrigEnv )
322
- env = base .AppendPATH (env )
323
-
324
341
toolCmd := & exec.Cmd {
325
342
Path : cmdline [0 ],
326
343
Args : cmdline ,
@@ -344,13 +361,17 @@ func runBuiltTool(b *work.Builder, ctx context.Context, a *work.Action) error {
344
361
}
345
362
if err != nil {
346
363
// Only print about the exit status if the command
347
- // didn't even run (not an ExitError)
364
+ // didn't even run (not an ExitError) or if it didn't exit cleanly
365
+ // or we're printing command lines too (-x mode).
348
366
// Assume if command exited cleanly (even with non-zero status)
349
367
// it printed any messages it wanted to print.
350
- if e , ok := err .(* exec.ExitError ); ok {
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 {
351
373
base .SetExitStatus (e .ExitCode ())
352
374
} else {
353
- fmt .Fprintf (os .Stderr , "go tool %s: %s\n " , filepath .Base (a .Deps [0 ].Target ), err )
354
375
base .SetExitStatus (1 )
355
376
}
356
377
}
0 commit comments