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(c.Commands) > 0 || c.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, "Run 'go help %s' for details.\n", c.LongName())
114 SetExitStatus(2)
115 Exit()
116 }
117
118
119
120 func (c *Command) Runnable() bool {
121 return c.Run != nil
122 }
123
124 var atExitFuncs []func()
125
126 func AtExit(f func()) {
127 atExitFuncs = append(atExitFuncs, f)
128 }
129
130 func Exit() {
131 for _, f := range atExitFuncs {
132 f()
133 }
134 os.Exit(exitStatus)
135 }
136
137 func Fatalf(format string, args ...any) {
138 Errorf(format, args...)
139 Exit()
140 }
141
142 func Errorf(format string, args ...any) {
143 log.Printf(format, args...)
144 SetExitStatus(1)
145 }
146
147 func ExitIfErrors() {
148 if exitStatus != 0 {
149 Exit()
150 }
151 }
152
153 func Error(err error) {
154
155
156
157
158
159
160
161 if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
162 for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
163 Error(e)
164 }
165 return
166 }
167 Errorf("go: %v", err)
168 }
169
170 func Fatal(err error) {
171 Error(err)
172 Exit()
173 }
174
175 var exitStatus = 0
176 var exitMu sync.Mutex
177
178 func SetExitStatus(n int) {
179 exitMu.Lock()
180 if exitStatus < n {
181 exitStatus = n
182 }
183 exitMu.Unlock()
184 }
185
186 func GetExitStatus() int {
187 return exitStatus
188 }
189
190
191
192
193 func Run(cmdargs ...any) {
194 cmdline := str.StringList(cmdargs...)
195 if cfg.BuildN || cfg.BuildX {
196 fmt.Printf("%s\n", strings.Join(cmdline, " "))
197 if cfg.BuildN {
198 return
199 }
200 }
201
202 cmd := exec.Command(cmdline[0], cmdline[1:]...)
203 cmd.Stdout = os.Stdout
204 cmd.Stderr = os.Stderr
205 if err := cmd.Run(); err != nil {
206 Errorf("%v", err)
207 }
208 }
209
210
211 func RunStdin(cmdline []string) {
212 env := slices.Clip(cfg.OrigEnv)
213 env = AppendPATH(env)
214 for try := range 3 {
215 cmd := exec.Command(cmdline[0], cmdline[1:]...)
216 cmd.Stdin = os.Stdin
217 cmd.Stdout = os.Stdout
218 cmd.Stderr = os.Stderr
219 cmd.Env = env
220 StartSigHandlers()
221 err := cmd.Run()
222 if err == nil {
223 break
224 }
225
226 if !IsETXTBSY(err) {
227 Errorf("%v", err)
228 break
229 }
230
231
232
233
234
235
236
237 time.Sleep(100 * time.Millisecond << uint(try))
238 }
239 }
240
241
242
243 var Usage func()
244
View as plain text