1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package tool implements the “go tool” command. 6package tool 7 8import ( 9 "cmd/internal/telemetry/counter" 10 "context" 11 "encoding/json" 12 "flag" 13 "fmt" 14 "go/build" 15 "internal/platform" 16 "os" 17 "os/exec" 18 "os/signal" 19 "sort" 20 "strings" 21 22 "cmd/go/internal/base" 23 "cmd/go/internal/cfg" 24) 25 26var CmdTool = &base.Command{ 27 Run: runTool, 28 UsageLine: "go tool [-n] command [args...]", 29 Short: "run specified go tool", 30 Long: ` 31Tool runs the go tool command identified by the arguments. 32With no arguments it prints the list of known tools. 33 34The -n flag causes tool to print the command that would be 35executed but not execute it. 36 37For more about each tool command, see 'go doc cmd/<command>'. 38`, 39} 40 41var toolN bool 42 43// Return whether tool can be expected in the gccgo tool directory. 44// Other binaries could be in the same directory so don't 45// show those with the 'go tool' command. 46func isGccgoTool(tool string) bool { 47 switch tool { 48 case "cgo", "fix", "cover", "godoc", "vet": 49 return true 50 } 51 return false 52} 53 54func init() { 55 base.AddChdirFlag(&CmdTool.Flag) 56 CmdTool.Flag.BoolVar(&toolN, "n", false, "") 57} 58 59func runTool(ctx context.Context, cmd *base.Command, args []string) { 60 if len(args) == 0 { 61 counter.Inc("go/subcommand:tool") 62 listTools() 63 return 64 } 65 toolName := args[0] 66 // The tool name must be lower-case letters, numbers or underscores. 67 for _, c := range toolName { 68 switch { 69 case 'a' <= c && c <= 'z', '0' <= c && c <= '9', c == '_': 70 default: 71 fmt.Fprintf(os.Stderr, "go: bad tool name %q\n", toolName) 72 base.SetExitStatus(2) 73 return 74 } 75 } 76 77 toolPath, err := base.ToolPath(toolName) 78 if err != nil { 79 if toolName == "dist" && len(args) > 1 && args[1] == "list" { 80 // cmd/distpack removes the 'dist' tool from the toolchain to save space, 81 // since it is normally only used for building the toolchain in the first 82 // place. However, 'go tool dist list' is useful for listing all supported 83 // platforms. 84 // 85 // If the dist tool does not exist, impersonate this command. 86 if impersonateDistList(args[2:]) { 87 // If it becomes necessary, we could increment an additional counter to indicate 88 // that we're impersonating dist list if knowing that becomes important? 89 counter.Inc("go/subcommand:tool-dist") 90 return 91 } 92 } 93 94 counter.Inc("go/subcommand:tool-unknown") 95 // Emit the usual error for the missing tool. 96 _ = base.Tool(toolName) 97 } else { 98 // Increment a counter for the tool subcommand with the tool name. 99 counter.Inc("go/subcommand:tool-" + toolName) 100 } 101 102 if toolN { 103 cmd := toolPath 104 if len(args) > 1 { 105 cmd += " " + strings.Join(args[1:], " ") 106 } 107 fmt.Printf("%s\n", cmd) 108 return 109 } 110 args[0] = toolPath // in case the tool wants to re-exec itself, e.g. cmd/dist 111 toolCmd := &exec.Cmd{ 112 Path: toolPath, 113 Args: args, 114 Stdin: os.Stdin, 115 Stdout: os.Stdout, 116 Stderr: os.Stderr, 117 } 118 err = toolCmd.Start() 119 if err == nil { 120 c := make(chan os.Signal, 100) 121 signal.Notify(c) 122 go func() { 123 for sig := range c { 124 toolCmd.Process.Signal(sig) 125 } 126 }() 127 err = toolCmd.Wait() 128 signal.Stop(c) 129 close(c) 130 } 131 if err != nil { 132 // Only print about the exit status if the command 133 // didn't even run (not an ExitError) or it didn't exit cleanly 134 // or we're printing command lines too (-x mode). 135 // Assume if command exited cleanly (even with non-zero status) 136 // it printed any messages it wanted to print. 137 if e, ok := err.(*exec.ExitError); !ok || !e.Exited() || cfg.BuildX { 138 fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err) 139 } 140 base.SetExitStatus(1) 141 return 142 } 143} 144 145// listTools prints a list of the available tools in the tools directory. 146func listTools() { 147 f, err := os.Open(build.ToolDir) 148 if err != nil { 149 fmt.Fprintf(os.Stderr, "go: no tool directory: %s\n", err) 150 base.SetExitStatus(2) 151 return 152 } 153 defer f.Close() 154 names, err := f.Readdirnames(-1) 155 if err != nil { 156 fmt.Fprintf(os.Stderr, "go: can't read tool directory: %s\n", err) 157 base.SetExitStatus(2) 158 return 159 } 160 161 sort.Strings(names) 162 for _, name := range names { 163 // Unify presentation by going to lower case. 164 // If it's windows, don't show the .exe suffix. 165 name = strings.TrimSuffix(strings.ToLower(name), cfg.ToolExeSuffix()) 166 167 // The tool directory used by gccgo will have other binaries 168 // in addition to go tools. Only display go tools here. 169 if cfg.BuildToolchainName == "gccgo" && !isGccgoTool(name) { 170 continue 171 } 172 fmt.Println(name) 173 } 174} 175 176func impersonateDistList(args []string) (handled bool) { 177 fs := flag.NewFlagSet("go tool dist list", flag.ContinueOnError) 178 jsonFlag := fs.Bool("json", false, "produce JSON output") 179 brokenFlag := fs.Bool("broken", false, "include broken ports") 180 181 // The usage for 'go tool dist' claims that 182 // “All commands take -v flags to emit extra information”, 183 // but list -v appears not to have any effect. 184 _ = fs.Bool("v", false, "emit extra information") 185 186 if err := fs.Parse(args); err != nil || len(fs.Args()) > 0 { 187 // Unrecognized flag or argument. 188 // Force fallback to the real 'go tool dist'. 189 return false 190 } 191 192 if !*jsonFlag { 193 for _, p := range platform.List { 194 if !*brokenFlag && platform.Broken(p.GOOS, p.GOARCH) { 195 continue 196 } 197 fmt.Println(p) 198 } 199 return true 200 } 201 202 type jsonResult struct { 203 GOOS string 204 GOARCH string 205 CgoSupported bool 206 FirstClass bool 207 Broken bool `json:",omitempty"` 208 } 209 210 var results []jsonResult 211 for _, p := range platform.List { 212 broken := platform.Broken(p.GOOS, p.GOARCH) 213 if broken && !*brokenFlag { 214 continue 215 } 216 if *jsonFlag { 217 results = append(results, jsonResult{ 218 GOOS: p.GOOS, 219 GOARCH: p.GOARCH, 220 CgoSupported: platform.CgoSupported(p.GOOS, p.GOARCH), 221 FirstClass: platform.FirstClass(p.GOOS, p.GOARCH), 222 Broken: broken, 223 }) 224 } 225 } 226 out, err := json.MarshalIndent(results, "", "\t") 227 if err != nil { 228 return false 229 } 230 231 os.Stdout.Write(out) 232 return true 233} 234