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