1
2
3
4
5 package work
6
7 import (
8 "bytes"
9 "cmd/go/internal/base"
10 "cmd/go/internal/cache"
11 "cmd/go/internal/cfg"
12 "cmd/go/internal/load"
13 "cmd/go/internal/par"
14 "cmd/go/internal/str"
15 "errors"
16 "fmt"
17 "internal/lazyregexp"
18 "io"
19 "io/fs"
20 "os"
21 "os/exec"
22 "path/filepath"
23 "runtime"
24 "strconv"
25 "strings"
26 "sync"
27 "time"
28 )
29
30
31
32
33
34 type Shell struct {
35 action *Action
36 *shellShared
37 }
38
39
40
41 type shellShared struct {
42 workDir string
43
44 printLock sync.Mutex
45 printFunc func(args ...any) (int, error)
46 scriptDir string
47
48 mkdirCache par.Cache[string, error]
49 }
50
51
52
53
54
55 func NewShell(workDir string, print func(a ...any) (int, error)) *Shell {
56 if print == nil {
57 print = func(a ...any) (int, error) {
58 return fmt.Fprint(os.Stderr, a...)
59 }
60 }
61 shared := &shellShared{
62 workDir: workDir,
63 printFunc: print,
64 }
65 return &Shell{shellShared: shared}
66 }
67
68
69
70 func (sh *Shell) Print(a ...any) {
71 sh.printLock.Lock()
72 defer sh.printLock.Unlock()
73 sh.printFunc(a...)
74 }
75
76 func (sh *Shell) printLocked(a ...any) {
77 sh.printFunc(a...)
78 }
79
80
81 func (sh *Shell) WithAction(a *Action) *Shell {
82 sh2 := *sh
83 sh2.action = a
84 return &sh2
85 }
86
87
88 func (b *Builder) Shell(a *Action) *Shell {
89 if a == nil {
90
91
92 panic("nil Action")
93 }
94 if a.sh == nil {
95 a.sh = b.backgroundSh.WithAction(a)
96 }
97 return a.sh
98 }
99
100
101
102 func (b *Builder) BackgroundShell() *Shell {
103 return b.backgroundSh
104 }
105
106
107 func (sh *Shell) moveOrCopyFile(dst, src string, perm fs.FileMode, force bool) error {
108 if cfg.BuildN {
109 sh.ShowCmd("", "mv %s %s", src, dst)
110 return nil
111 }
112
113
114
115
116
117 if strings.HasPrefix(src, cache.DefaultDir()) {
118 return sh.CopyFile(dst, src, perm, force)
119 }
120
121
122
123
124
125 if runtime.GOOS == "windows" {
126 return sh.CopyFile(dst, src, perm, force)
127 }
128
129
130
131
132 if fi, err := os.Stat(filepath.Dir(dst)); err == nil {
133 if fi.IsDir() && (fi.Mode()&fs.ModeSetgid) != 0 {
134 return sh.CopyFile(dst, src, perm, force)
135 }
136 }
137
138
139
140
141
142
143 mode := perm
144 f, err := os.OpenFile(filepath.Clean(dst)+"-go-tmp-umask", os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
145 if err == nil {
146 fi, err := f.Stat()
147 if err == nil {
148 mode = fi.Mode() & 0777
149 }
150 name := f.Name()
151 f.Close()
152 os.Remove(name)
153 }
154
155 if err := os.Chmod(src, mode); err == nil {
156 if err := os.Rename(src, dst); err == nil {
157 if cfg.BuildX {
158 sh.ShowCmd("", "mv %s %s", src, dst)
159 }
160 return nil
161 }
162 }
163
164 return sh.CopyFile(dst, src, perm, force)
165 }
166
167
168 func (sh *Shell) CopyFile(dst, src string, perm fs.FileMode, force bool) error {
169 if cfg.BuildN || cfg.BuildX {
170 sh.ShowCmd("", "cp %s %s", src, dst)
171 if cfg.BuildN {
172 return nil
173 }
174 }
175
176 sf, err := os.Open(src)
177 if err != nil {
178 return err
179 }
180 defer sf.Close()
181
182
183
184
185 if fi, err := os.Stat(dst); err == nil {
186 if fi.IsDir() {
187 return fmt.Errorf("build output %q already exists and is a directory", dst)
188 }
189 if !force && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(dst) {
190 return fmt.Errorf("build output %q already exists and is not an object file", dst)
191 }
192 }
193
194
195 if runtime.GOOS == "windows" {
196 if _, err := os.Stat(dst + "~"); err == nil {
197 os.Remove(dst + "~")
198 }
199 }
200
201 mayberemovefile(dst)
202 df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
203 if err != nil && runtime.GOOS == "windows" {
204
205
206
207
208 if err := os.Rename(dst, dst+"~"); err == nil {
209 os.Remove(dst + "~")
210 }
211 df, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
212 }
213 if err != nil {
214 return fmt.Errorf("copying %s: %w", src, err)
215 }
216
217 _, err = io.Copy(df, sf)
218 df.Close()
219 if err != nil {
220 mayberemovefile(dst)
221 return fmt.Errorf("copying %s to %s: %v", src, dst, err)
222 }
223 return nil
224 }
225
226
227
228
229 func mayberemovefile(s string) {
230 if fi, err := os.Lstat(s); err == nil && !fi.Mode().IsRegular() {
231 return
232 }
233 os.Remove(s)
234 }
235
236
237 func (sh *Shell) writeFile(file string, text []byte) error {
238 if cfg.BuildN || cfg.BuildX {
239 switch {
240 case len(text) == 0:
241 sh.ShowCmd("", "echo -n > %s # internal", file)
242 case bytes.IndexByte(text, '\n') == len(text)-1:
243
244 sh.ShowCmd("", "echo '%s' > %s # internal", bytes.TrimSuffix(text, []byte("\n")), file)
245 default:
246
247 sh.ShowCmd("", "cat >%s << 'EOF' # internal\n%sEOF", file, text)
248 }
249 }
250 if cfg.BuildN {
251 return nil
252 }
253 return os.WriteFile(file, text, 0666)
254 }
255
256
257 func (sh *Shell) Mkdir(dir string) error {
258
259 if dir == "" {
260 return nil
261 }
262
263
264
265 return sh.mkdirCache.Do(dir, func() error {
266 if cfg.BuildN || cfg.BuildX {
267 sh.ShowCmd("", "mkdir -p %s", dir)
268 if cfg.BuildN {
269 return nil
270 }
271 }
272
273 return os.MkdirAll(dir, 0777)
274 })
275 }
276
277
278
279 func (sh *Shell) RemoveAll(paths ...string) error {
280 if cfg.BuildN || cfg.BuildX {
281
282 show := func() bool {
283 for _, path := range paths {
284 if _, ok := sh.mkdirCache.Get(path); ok {
285 return true
286 }
287 if _, err := os.Stat(path); !os.IsNotExist(err) {
288 return true
289 }
290 }
291 return false
292 }
293 if show() {
294 sh.ShowCmd("", "rm -rf %s", strings.Join(paths, " "))
295 }
296 }
297 if cfg.BuildN {
298 return nil
299 }
300
301 var err error
302 for _, path := range paths {
303 if err2 := os.RemoveAll(path); err2 != nil && err == nil {
304 err = err2
305 }
306 }
307 return err
308 }
309
310
311 func (sh *Shell) Symlink(oldname, newname string) error {
312
313 if link, err := os.Readlink(newname); err == nil && link == oldname {
314 return nil
315 }
316
317 if cfg.BuildN || cfg.BuildX {
318 sh.ShowCmd("", "ln -s %s %s", oldname, newname)
319 if cfg.BuildN {
320 return nil
321 }
322 }
323 return os.Symlink(oldname, newname)
324 }
325
326
327
328
329 func (sh *Shell) fmtCmd(dir string, format string, args ...any) string {
330 cmd := fmt.Sprintf(format, args...)
331 if sh.workDir != "" && !strings.HasPrefix(cmd, "cat ") {
332 cmd = strings.ReplaceAll(cmd, sh.workDir, "$WORK")
333 escaped := strconv.Quote(sh.workDir)
334 escaped = escaped[1 : len(escaped)-1]
335 if escaped != sh.workDir {
336 cmd = strings.ReplaceAll(cmd, escaped, "$WORK")
337 }
338 }
339 return cmd
340 }
341
342
343
344
345
346
347
348
349
350 func (sh *Shell) ShowCmd(dir string, format string, args ...any) {
351
352 sh.printLock.Lock()
353 defer sh.printLock.Unlock()
354
355 cmd := sh.fmtCmd(dir, format, args...)
356
357 if dir != "" && dir != "/" {
358 if dir != sh.scriptDir {
359
360 sh.printLocked(sh.fmtCmd("", "cd %s\n", dir))
361 sh.scriptDir = dir
362 }
363
364
365 dot := " ."
366 if dir[len(dir)-1] == filepath.Separator {
367 dot += string(filepath.Separator)
368 }
369 cmd = strings.ReplaceAll(" "+cmd, " "+dir, dot)[1:]
370 }
371
372 sh.printLocked(cmd + "\n")
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 func (sh *Shell) reportCmd(desc, dir string, cmdOut []byte, cmdErr error) error {
419 if len(cmdOut) == 0 && cmdErr == nil {
420
421 return nil
422 }
423 if len(cmdOut) == 0 && cmdErr != nil {
424
425
426
427
428
429
430
431 return cmdErr
432 }
433
434
435 var p *load.Package
436 a := sh.action
437 if a != nil {
438 p = a.Package
439 }
440 var importPath string
441 if p != nil {
442 importPath = p.ImportPath
443 if desc == "" {
444 desc = p.Desc()
445 }
446 if dir == "" {
447 dir = p.Dir
448 }
449 }
450
451 out := string(cmdOut)
452
453 if !strings.HasSuffix(out, "\n") {
454 out = out + "\n"
455 }
456
457
458 out = replacePrefix(out, sh.workDir, "$WORK")
459
460
461
462 for {
463
464
465
466
467
468
469
470
471
472 if reldir := base.ShortPath(dir); reldir != dir {
473 out = replacePrefix(out, dir, reldir)
474 if filepath.Separator == '\\' {
475
476 wdir := strings.ReplaceAll(dir, "\\", "/")
477 out = replacePrefix(out, wdir, reldir)
478 }
479 }
480 dirP := filepath.Dir(dir)
481 if dir == dirP {
482 break
483 }
484 dir = dirP
485 }
486
487
488
489
490
491 if !cfg.BuildX && cgoLine.MatchString(out) {
492 out = cgoLine.ReplaceAllString(out, "")
493 out = cgoTypeSigRe.ReplaceAllString(out, "C.")
494 }
495
496
497
498 needsPath := importPath != "" && p != nil && desc != p.Desc()
499
500 err := &cmdError{desc, out, importPath, needsPath}
501 if cmdErr != nil {
502
503 return err
504 }
505
506 if a != nil && a.output != nil {
507
508 a.output = append(a.output, err.Error()...)
509 } else {
510
511 sh.Print(err.Error())
512 }
513 return nil
514 }
515
516
517
518 func replacePrefix(s, old, new string) string {
519 n := strings.Count(s, old)
520 if n == 0 {
521 return s
522 }
523
524 s = strings.ReplaceAll(s, " "+old, " "+new)
525 s = strings.ReplaceAll(s, "\n"+old, "\n"+new)
526 s = strings.ReplaceAll(s, "\n\t"+old, "\n\t"+new)
527 if strings.HasPrefix(s, old) {
528 s = new + s[len(old):]
529 }
530 return s
531 }
532
533 type cmdError struct {
534 desc string
535 text string
536 importPath string
537 needsPath bool
538 }
539
540 func (e *cmdError) Error() string {
541 var msg string
542 if e.needsPath {
543
544
545 msg = fmt.Sprintf("# %s\n# [%s]\n", e.importPath, e.desc)
546 } else {
547 msg = "# " + e.desc + "\n"
548 }
549 return msg + e.text
550 }
551
552 func (e *cmdError) ImportPath() string {
553 return e.importPath
554 }
555
556 var cgoLine = lazyregexp.New(`\[[^\[\]]+\.(cgo1|cover)\.go:[0-9]+(:[0-9]+)?\]`)
557 var cgoTypeSigRe = lazyregexp.New(`\b_C2?(type|func|var|macro)_\B`)
558
559
560
561
562 func (sh *Shell) run(dir string, desc string, env []string, cmdargs ...any) error {
563 out, err := sh.runOut(dir, env, cmdargs...)
564 if desc == "" {
565 desc = sh.fmtCmd(dir, "%s", strings.Join(str.StringList(cmdargs...), " "))
566 }
567 return sh.reportCmd(desc, dir, out, err)
568 }
569
570
571
572
573 func (sh *Shell) runOut(dir string, env []string, cmdargs ...any) ([]byte, error) {
574 a := sh.action
575
576 cmdline := str.StringList(cmdargs...)
577
578 for _, arg := range cmdline {
579
580
581
582
583 if strings.HasPrefix(arg, "@") {
584 return nil, fmt.Errorf("invalid command-line argument %s in command: %s", arg, joinUnambiguously(cmdline))
585 }
586 }
587
588 if cfg.BuildN || cfg.BuildX {
589 var envcmdline string
590 for _, e := range env {
591 if j := strings.IndexByte(e, '='); j != -1 {
592 if strings.ContainsRune(e[j+1:], '\'') {
593 envcmdline += fmt.Sprintf("%s=%q", e[:j], e[j+1:])
594 } else {
595 envcmdline += fmt.Sprintf("%s='%s'", e[:j], e[j+1:])
596 }
597 envcmdline += " "
598 }
599 }
600 envcmdline += joinUnambiguously(cmdline)
601 sh.ShowCmd(dir, "%s", envcmdline)
602 if cfg.BuildN {
603 return nil, nil
604 }
605 }
606
607 var buf bytes.Buffer
608 path, err := cfg.LookPath(cmdline[0])
609 if err != nil {
610 return nil, err
611 }
612 cmd := exec.Command(path, cmdline[1:]...)
613 if cmd.Path != "" {
614 cmd.Args[0] = cmd.Path
615 }
616 cmd.Stdout = &buf
617 cmd.Stderr = &buf
618 cleanup := passLongArgsInResponseFiles(cmd)
619 defer cleanup()
620 if dir != "." {
621 cmd.Dir = dir
622 }
623 cmd.Env = cmd.Environ()
624
625
626
627
628
629
630 if a != nil && a.Package != nil {
631 cmd.Env = append(cmd.Env, "TOOLEXEC_IMPORTPATH="+a.Package.Desc())
632 }
633
634 cmd.Env = append(cmd.Env, env...)
635 start := time.Now()
636 err = cmd.Run()
637 if a != nil && a.json != nil {
638 aj := a.json
639 aj.Cmd = append(aj.Cmd, joinUnambiguously(cmdline))
640 aj.CmdReal += time.Since(start)
641 if ps := cmd.ProcessState; ps != nil {
642 aj.CmdUser += ps.UserTime()
643 aj.CmdSys += ps.SystemTime()
644 }
645 }
646
647
648
649
650
651
652 if err != nil {
653 err = errors.New(cmdline[0] + ": " + err.Error())
654 }
655 return buf.Bytes(), err
656 }
657
658
659
660
661 func joinUnambiguously(a []string) string {
662 var buf strings.Builder
663 for i, s := range a {
664 if i > 0 {
665 buf.WriteByte(' ')
666 }
667 q := strconv.Quote(s)
668
669
670
671 if s == "" || strings.ContainsAny(s, " ()>;") || len(q) > len(s)+2 {
672 buf.WriteString(q)
673 } else {
674 buf.WriteString(s)
675 }
676 }
677 return buf.String()
678 }
679
View as plain text