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