1
2
3
4
5
6
7 package base
8
9 import (
10 "context"
11 "flag"
12 "fmt"
13 "log"
14 "os"
15 "os/exec"
16 "reflect"
17 "slices"
18 "strings"
19 "sync"
20 "time"
21
22 "cmd/go/internal/cfg"
23 "cmd/go/internal/str"
24 )
25
26
27
28 type Command struct {
29
30
31 Run func(ctx context.Context, cmd *Command, args []string)
32
33
34
35 UsageLine string
36
37
38 Short string
39
40
41 Long string
42
43
44 Flag flag.FlagSet
45
46
47
48 CustomFlags bool
49
50
51
52
53 Commands []*Command
54 }
55
56 var Go = &Command{
57 UsageLine: "go",
58 Long: `Go is a tool for managing Go source code.`,
59
60 }
61
62
63
64
65
66
67 func (c *Command) Lookup(name string) *Command {
68 for _, sub := range c.Commands {
69 if sub.Name() == name && (len(sub.Commands) > 0 || sub.Runnable()) {
70 return sub
71 }
72 }
73 return nil
74 }
75
76
77
78 func hasFlag(c *Command, name string) bool {
79 if f := c.Flag.Lookup(name); f != nil {
80 return true
81 }
82 for _, sub := range c.Commands {
83 if hasFlag(sub, name) {
84 return true
85 }
86 }
87 return false
88 }
89
90
91 func (c *Command) LongName() string {
92 name := c.UsageLine
93 if i := strings.Index(name, " ["); i >= 0 {
94 name = name[:i]
95 }
96 if name == "go" {
97 return ""
98 }
99 return strings.TrimPrefix(name, "go ")
100 }
101
102
103 func (c *Command) Name() string {
104 name := c.LongName()
105 if i := strings.LastIndex(name, " "); i >= 0 {
106 name = name[i+1:]
107 }
108 return name
109 }
110
111 func (c *Command) Usage() {
112 fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
113 fmt.Fprintf(os.Stderr, "\nFlags:\n")
114 c.Flag.SetOutput(os.Stderr)
115 c.Flag.PrintDefaults()
116 fmt.Fprintf(os.Stderr, "\nRun 'go help %s' for details.\n", c.LongName())
117 SetExitStatus(2)
118 Exit()
119 }
120
121
122
123 func (c *Command) Runnable() bool {
124 return c.Run != nil
125 }
126
127 var atExitFuncs []func()
128
129 func AtExit(f func()) {
130 atExitFuncs = append(atExitFuncs, f)
131 }
132
133 func Exit() {
134 for _, f := range atExitFuncs {
135 f()
136 }
137 os.Exit(exitStatus)
138 }
139
140 func Fatalf(format string, args ...any) {
141 Errorf(format, args...)
142 Exit()
143 }
144
145 func Errorf(format string, args ...any) {
146 log.Printf(format, args...)
147 SetExitStatus(1)
148 }
149
150 func ExitIfErrors() {
151 if exitStatus != 0 {
152 Exit()
153 }
154 }
155
156 func Error(err error) {
157
158
159
160
161
162
163
164 if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
165 for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
166 Error(e)
167 }
168 return
169 }
170 Errorf("go: %v", err)
171 }
172
173 func Fatal(err error) {
174 Error(err)
175 Exit()
176 }
177
178 var (
179 exitStatus = 0
180 exitMu sync.Mutex
181 )
182
183
184
185 func SetExitStatus(n int) {
186 exitMu.Lock()
187 if exitStatus < n {
188 exitStatus = n
189 }
190 exitMu.Unlock()
191 }
192
193
194 func GetExitStatus() int {
195 return exitStatus
196 }
197
198
199
200
201 func Run(cmdargs ...any) {
202 if err := RunErr(cmdargs...); err != nil {
203 Errorf("%v", err)
204 }
205 }
206
207
208
209
210
211 func RunErr(cmdargs ...any) error {
212 cmdline := str.StringList(cmdargs...)
213 if cfg.BuildN || cfg.BuildX {
214 fmt.Printf("%s\n", strings.Join(cmdline, " "))
215 if cfg.BuildN {
216 return nil
217 }
218 }
219
220 cmd := exec.Command(cmdline[0], cmdline[1:]...)
221 cmd.Stdout = os.Stdout
222 cmd.Stderr = os.Stderr
223 return cmd.Run()
224 }
225
226
227 func RunStdin(cmdline []string) {
228 env := slices.Clip(cfg.OrigEnv)
229 env = AppendPATH(env)
230 for try := range 3 {
231 cmd := exec.Command(cmdline[0], cmdline[1:]...)
232 cmd.Stdin = os.Stdin
233 cmd.Stdout = os.Stdout
234 cmd.Stderr = os.Stderr
235 cmd.Env = env
236 StartSigHandlers()
237 err := cmd.Run()
238 if err == nil {
239 break
240 }
241
242 if !IsETXTBSY(err) {
243 Errorf("%v", err)
244 break
245 }
246
247
248
249
250
251
252
253 time.Sleep(100 * time.Millisecond << uint(try))
254 }
255 }
256
257
258
259 var Usage func()
260
View as plain text