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