github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/base/base.go (about) 1 // Copyright 2017 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 base defines shared basic pieces of the go command, 6 // in particular logging and the Command structure. 7 package base 8 9 import ( 10 "context" 11 "flag" 12 "fmt" 13 "log" 14 "os" 15 "os/exec" 16 "reflect" 17 "strings" 18 "sync" 19 20 "github.com/go-asm/go/cmd/go/cfg" 21 "github.com/go-asm/go/cmd/go/str" 22 ) 23 24 // A Command is an implementation of a go command 25 // like go build or go fix. 26 type Command struct { 27 // Run runs the command. 28 // The args are the arguments after the command name. 29 Run func(ctx context.Context, cmd *Command, args []string) 30 31 // UsageLine is the one-line usage message. 32 // The words between "go" and the first flag or argument in the line are taken to be the command name. 33 UsageLine string 34 35 // Short is the short description shown in the 'go help' output. 36 Short string 37 38 // Long is the long message shown in the 'go help <this-command>' output. 39 Long string 40 41 // Flag is a set of flags specific to this command. 42 Flag flag.FlagSet 43 44 // CustomFlags indicates that the command will do its own 45 // flag parsing. 46 CustomFlags bool 47 48 // Commands lists the available commands and help topics. 49 // The order here is the order in which they are printed by 'go help'. 50 // Note that subcommands are in general best avoided. 51 Commands []*Command 52 } 53 54 var Go = &Command{ 55 UsageLine: "go", 56 Long: `Go is a tool for managing Go source code.`, 57 // Commands initialized in package main 58 } 59 60 // Lookup returns the subcommand with the given name, if any. 61 // Otherwise it returns nil. 62 // 63 // Lookup ignores subcommands that have len(c.Commands) == 0 and c.Run == nil. 64 // Such subcommands are only for use as arguments to "help". 65 func (c *Command) Lookup(name string) *Command { 66 for _, sub := range c.Commands { 67 if sub.Name() == name && (len(c.Commands) > 0 || c.Runnable()) { 68 return sub 69 } 70 } 71 return nil 72 } 73 74 // hasFlag reports whether a command or any of its subcommands contain the given 75 // flag. 76 func hasFlag(c *Command, name string) bool { 77 if f := c.Flag.Lookup(name); f != nil { 78 return true 79 } 80 for _, sub := range c.Commands { 81 if hasFlag(sub, name) { 82 return true 83 } 84 } 85 return false 86 } 87 88 // LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument, 89 func (c *Command) LongName() string { 90 name := c.UsageLine 91 if i := strings.Index(name, " ["); i >= 0 { 92 name = name[:i] 93 } 94 if name == "go" { 95 return "" 96 } 97 return strings.TrimPrefix(name, "go ") 98 } 99 100 // Name returns the command's short name: the last word in the usage line before a flag or argument. 101 func (c *Command) Name() string { 102 name := c.LongName() 103 if i := strings.LastIndex(name, " "); i >= 0 { 104 name = name[i+1:] 105 } 106 return name 107 } 108 109 func (c *Command) Usage() { 110 fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine) 111 fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName()) 112 SetExitStatus(2) 113 Exit() 114 } 115 116 // Runnable reports whether the command can be run; otherwise 117 // it is a documentation pseudo-command such as importpath. 118 func (c *Command) Runnable() bool { 119 return c.Run != nil 120 } 121 122 var atExitFuncs []func() 123 124 func AtExit(f func()) { 125 atExitFuncs = append(atExitFuncs, f) 126 } 127 128 func Exit() { 129 for _, f := range atExitFuncs { 130 f() 131 } 132 os.Exit(exitStatus) 133 } 134 135 func Fatalf(format string, args ...any) { 136 Errorf(format, args...) 137 Exit() 138 } 139 140 func Errorf(format string, args ...any) { 141 log.Printf(format, args...) 142 SetExitStatus(1) 143 } 144 145 func ExitIfErrors() { 146 if exitStatus != 0 { 147 Exit() 148 } 149 } 150 151 func Error(err error) { 152 // We use errors.Join to return multiple errors from various routines. 153 // If we receive multiple errors joined with a basic errors.Join, 154 // handle each one separately so that they all have the leading "go: " prefix. 155 // A plain interface check is not good enough because there might be 156 // other kinds of structured errors that are logically one unit and that 157 // add other context: only handling the wrapped errors would lose 158 // that context. 159 if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" { 160 for _, e := range err.(interface{ Unwrap() []error }).Unwrap() { 161 Error(e) 162 } 163 return 164 } 165 Errorf("go: %v", err) 166 } 167 168 func Fatal(err error) { 169 Error(err) 170 Exit() 171 } 172 173 var exitStatus = 0 174 var exitMu sync.Mutex 175 176 func SetExitStatus(n int) { 177 exitMu.Lock() 178 if exitStatus < n { 179 exitStatus = n 180 } 181 exitMu.Unlock() 182 } 183 184 func GetExitStatus() int { 185 return exitStatus 186 } 187 188 // Run runs the command, with stdout and stderr 189 // connected to the go command's own stdout and stderr. 190 // If the command fails, Run reports the error using Errorf. 191 func Run(cmdargs ...any) { 192 cmdline := str.StringList(cmdargs...) 193 if cfg.BuildN || cfg.BuildX { 194 fmt.Printf("%s\n", strings.Join(cmdline, " ")) 195 if cfg.BuildN { 196 return 197 } 198 } 199 200 cmd := exec.Command(cmdline[0], cmdline[1:]...) 201 cmd.Stdout = os.Stdout 202 cmd.Stderr = os.Stderr 203 if err := cmd.Run(); err != nil { 204 Errorf("%v", err) 205 } 206 } 207 208 // RunStdin is like run but connects Stdin. 209 func RunStdin(cmdline []string) { 210 cmd := exec.Command(cmdline[0], cmdline[1:]...) 211 cmd.Stdin = os.Stdin 212 cmd.Stdout = os.Stdout 213 cmd.Stderr = os.Stderr 214 cmd.Env = cfg.OrigEnv 215 StartSigHandlers() 216 if err := cmd.Run(); err != nil { 217 Errorf("%v", err) 218 } 219 } 220 221 // Usage is the usage-reporting function, filled in by package main 222 // but here for reference by other packages. 223 var Usage func()