Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / cmd / toolstash / main.go
1 // Copyright 2015 The Go Authors.  All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Toolstash provides a way to save, run, and restore a known good copy of the Go toolchain
6 // and to compare the object files generated by two toolchains.
7 //
8 // Usage:
9 //
10 //      toolstash [-n] [-v] save [tool...]
11 //      toolstash [-n] [-v] restore [tool...]
12 //      toolstash [-n] [-v] [-t] go run x.go
13 //      toolstash [-n] [-v] [-t] [-cmp] compile x.go
14 //
15 // The toolstash command manages a ``stashed'' copy of the Go toolchain
16 // kept in $GOROOT/pkg/toolstash. In this case, the toolchain means the
17 // tools available with the 'go tool' command as well as the go, godoc, and gofmt
18 // binaries.
19 //
20 // The command ``toolstash save'', typically run when the toolchain is known to be working,
21 // copies the toolchain from its installed location to the toolstash directory.
22 // Its inverse, ``toolchain restore'', typically run when the toolchain is known to be broken,
23 // copies the toolchain from the toolstash directory back to the installed locations.
24 // If additional arguments are given, the save or restore applies only to the named tools.
25 // Otherwise, it applies to all tools.
26 //
27 // Otherwise, toolstash's arguments should be a command line beginning with the
28 // name of a toolchain binary, which may be a short name like compile or a complete path
29 // to an installed binary. Toolstash runs the command line using the stashed
30 // copy of the binary instead of the installed one.
31 //
32 // The -n flag causes toolstash to print the commands that would be executed
33 // but not execute them. The combination -n -cmp shows the two commands
34 // that would be compared and then exits successfully. A real -cmp run might
35 // run additional commands for diagnosis of an output mismatch.
36 //
37 // The -v flag causes toolstash to print the commands being executed.
38 //
39 // The -t flag causes toolstash to print the time elapsed during while the
40 // command ran.
41 //
42 // Comparing
43 //
44 // The -cmp flag causes toolstash to run both the installed and the stashed
45 // copy of an assembler or compiler and check that they produce identical
46 // object files. If not, toolstash reports the mismatch and exits with a failure status.
47 // As part of reporting the mismatch, toolstash reinvokes the command with
48 // the -S=2 flag and identifies the first divergence in the assembly output.
49 // If the command is a Go compiler, toolstash also determines whether the
50 // difference is triggered by optimization passes.
51 // On failure, toolstash leaves additional information in files named
52 // similarly to the default output file. If the compilation would normally
53 // produce a file x.6, the output from the stashed tool is left in x.6.stash
54 // and the debugging traces are left in x.6.log and x.6.stash.log.
55 //
56 // The -cmp flag is a no-op when the command line is not invoking an
57 // assembler or compiler.
58 //
59 // For example, when working on code cleanup that should not affect
60 // compiler output, toolstash can be used to compare the old and new
61 // compiler output:
62 //
63 //      toolstash save
64 //      <edit compiler sources>
65 //      go tool dist install cmd/compile # install compiler only
66 //      toolstash -cmp compile x.go
67 //
68 // Go Command Integration
69 //
70 // The go command accepts a -toolexec flag that specifies a program
71 // to use to run the build tools.
72 //
73 // To build with the stashed tools:
74 //
75 //      go build -toolexec toolstash x.go
76 //
77 // To build with the stashed go command and the stashed tools:
78 //
79 //      toolstash go build -toolexec toolstash x.go
80 //
81 // To verify that code cleanup in the compilers does not make any
82 // changes to the objects being generated for the entire tree:
83 //
84 //      # Build working tree and save tools.
85 //      ./make.bash
86 //      toolstash save
87 //
88 //      <edit compiler sources>
89 //
90 //      # Install new tools, but do not rebuild the rest of tree,
91 //      # since the compilers might generate buggy code.
92 //      go tool dist install cmd/compile
93 //
94 //      # Check that new tools behave identically to saved tools.
95 //      go build -toolexec 'toolstash -cmp' -a std
96 //
97 //      # If not, restore, in order to keep working on Go code.
98 //      toolstash restore
99 //
100 // Version Skew
101 //
102 // The Go tools write the current Go version to object files, and (outside
103 // release branches) that version includes the hash and time stamp
104 // of the most recent Git commit. Functionally equivalent
105 // compilers built at different Git versions may produce object files that
106 // differ only in the recorded version. Toolstash ignores version mismatches
107 // when comparing object files, but the standard tools will refuse to compile
108 // or link together packages with different object versions.
109 //
110 // For the full build in the final example above to work, both the stashed
111 // and the installed tools must use the same version string.
112 // One way to ensure this is not to commit any of the changes being
113 // tested, so that the Git HEAD hash is the same for both builds.
114 // A more robust way to force the tools to have the same version string
115 // is to write a $GOROOT/VERSION file, which overrides the Git-based version
116 // computation:
117 //
118 //      echo devel >$GOROOT/VERSION
119 //
120 // The version can be arbitrary text, but to pass all.bash's API check, it must
121 // contain the substring ``devel''. The VERSION file must be created before
122 // building either version of the toolchain.
123 //
124 package main // import "golang.org/x/tools/cmd/toolstash"
125
126 import (
127         "bufio"
128         "flag"
129         "fmt"
130         "io"
131         "io/ioutil"
132         "log"
133         "os"
134         "os/exec"
135         "path/filepath"
136         "runtime"
137         "strings"
138         "time"
139 )
140
141 var usageMessage = `usage: toolstash [-n] [-v] [-cmp] command line
142
143 Examples:
144         toolstash save
145         toolstash restore
146         toolstash go run x.go
147         toolstash compile x.go
148         toolstash -cmp compile x.go
149
150 For details, godoc golang.org/x/tools/cmd/toolstash
151 `
152
153 func usage() {
154         fmt.Fprint(os.Stderr, usageMessage)
155         os.Exit(2)
156 }
157
158 var (
159         goCmd   = flag.String("go", "go", "path to \"go\" command")
160         norun   = flag.Bool("n", false, "print but do not run commands")
161         verbose = flag.Bool("v", false, "print commands being run")
162         cmp     = flag.Bool("cmp", false, "compare tool object files")
163         timing  = flag.Bool("t", false, "print time commands take")
164 )
165
166 var (
167         cmd       []string
168         tool      string // name of tool: "go", "compile", etc
169         toolStash string // path to stashed tool
170
171         goroot   string
172         toolDir  string
173         stashDir string
174         binDir   string
175 )
176
177 func canCmp(name string, args []string) bool {
178         switch name {
179         case "asm", "compile", "link":
180                 if len(args) == 1 && (args[0] == "-V" || strings.HasPrefix(args[0], "-V=")) {
181                         // cmd/go uses "compile -V=full" to query the tool's build ID.
182                         return false
183                 }
184                 return true
185         }
186         return len(name) == 2 && '0' <= name[0] && name[0] <= '9' && (name[1] == 'a' || name[1] == 'g' || name[1] == 'l')
187 }
188
189 var binTools = []string{"go", "godoc", "gofmt"}
190
191 func isBinTool(name string) bool {
192         return strings.HasPrefix(name, "go")
193 }
194
195 func main() {
196         log.SetFlags(0)
197         log.SetPrefix("toolstash: ")
198
199         flag.Usage = usage
200         flag.Parse()
201         cmd = flag.Args()
202
203         if len(cmd) < 1 {
204                 usage()
205         }
206
207         s, err := exec.Command(*goCmd, "env", "GOROOT").CombinedOutput()
208         if err != nil {
209                 log.Fatalf("%s env GOROOT: %v", *goCmd, err)
210         }
211         goroot = strings.TrimSpace(string(s))
212         toolDir = filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
213         stashDir = filepath.Join(goroot, "pkg/toolstash")
214
215         binDir = os.Getenv("GOBIN")
216         if binDir == "" {
217                 binDir = filepath.Join(goroot, "bin")
218         }
219
220         switch cmd[0] {
221         case "save":
222                 save()
223                 return
224
225         case "restore":
226                 restore()
227                 return
228         }
229
230         tool = cmd[0]
231         if i := strings.LastIndexAny(tool, `/\`); i >= 0 {
232                 tool = tool[i+1:]
233         }
234
235         if !strings.HasPrefix(tool, "a.out") {
236                 toolStash = filepath.Join(stashDir, tool)
237                 if _, err := os.Stat(toolStash); err != nil {
238                         log.Print(err)
239                         os.Exit(2)
240                 }
241
242                 if *cmp && canCmp(tool, cmd[1:]) {
243                         compareTool()
244                         return
245                 }
246                 cmd[0] = toolStash
247         }
248
249         if *norun {
250                 fmt.Printf("%s\n", strings.Join(cmd, " "))
251                 return
252         }
253         if *verbose {
254                 log.Print(strings.Join(cmd, " "))
255         }
256         xcmd := exec.Command(cmd[0], cmd[1:]...)
257         xcmd.Stdin = os.Stdin
258         xcmd.Stdout = os.Stdout
259         xcmd.Stderr = os.Stderr
260         err = xcmd.Run()
261         if err != nil {
262                 log.Fatal(err)
263         }
264         os.Exit(0)
265 }
266
267 func compareTool() {
268         if !strings.Contains(cmd[0], "/") && !strings.Contains(cmd[0], `\`) {
269                 cmd[0] = filepath.Join(toolDir, tool)
270         }
271
272         outfile, ok := cmpRun(false, cmd)
273         if ok {
274                 os.Remove(outfile + ".stash")
275                 return
276         }
277
278         extra := "-S=2"
279         switch {
280         default:
281                 log.Fatalf("unknown tool %s", tool)
282
283         case tool == "compile" || strings.HasSuffix(tool, "g"): // compiler
284                 useDashN := true
285                 dashcIndex := -1
286                 for i, s := range cmd {
287                         if s == "-+" {
288                                 // Compiling runtime. Don't use -N.
289                                 useDashN = false
290                         }
291                         if strings.HasPrefix(s, "-c=") {
292                                 dashcIndex = i
293                         }
294                 }
295                 cmdN := injectflags(cmd, nil, useDashN)
296                 _, ok := cmpRun(false, cmdN)
297                 if !ok {
298                         if useDashN {
299                                 log.Printf("compiler output differs, with optimizers disabled (-N)")
300                         } else {
301                                 log.Printf("compiler output differs")
302                         }
303                         if dashcIndex >= 0 {
304                                 cmd[dashcIndex] = "-c=1"
305                         }
306                         cmd = injectflags(cmd, []string{"-v", "-m=2"}, useDashN)
307                         break
308                 }
309                 if dashcIndex >= 0 {
310                         cmd[dashcIndex] = "-c=1"
311                 }
312                 cmd = injectflags(cmd, []string{"-v", "-m=2"}, false)
313                 log.Printf("compiler output differs, only with optimizers enabled")
314
315         case tool == "asm" || strings.HasSuffix(tool, "a"): // assembler
316                 log.Printf("assembler output differs")
317
318         case tool == "link" || strings.HasSuffix(tool, "l"): // linker
319                 log.Printf("linker output differs")
320                 extra = "-v=2"
321         }
322
323         cmdS := injectflags(cmd, []string{extra}, false)
324         outfile, _ = cmpRun(true, cmdS)
325
326         fmt.Fprintf(os.Stderr, "\n%s\n", compareLogs(outfile))
327         os.Exit(2)
328 }
329
330 func injectflags(cmd []string, extra []string, addDashN bool) []string {
331         x := []string{cmd[0]}
332         if addDashN {
333                 x = append(x, "-N")
334         }
335         x = append(x, extra...)
336         x = append(x, cmd[1:]...)
337         return x
338 }
339
340 func cmpRun(keepLog bool, cmd []string) (outfile string, match bool) {
341         cmdStash := make([]string, len(cmd))
342         copy(cmdStash, cmd)
343         cmdStash[0] = toolStash
344         for i, arg := range cmdStash {
345                 if arg == "-o" {
346                         outfile = cmdStash[i+1]
347                         cmdStash[i+1] += ".stash"
348                         break
349                 }
350                 if strings.HasSuffix(arg, ".s") || strings.HasSuffix(arg, ".go") && '0' <= tool[0] && tool[0] <= '9' {
351                         outfile = filepath.Base(arg[:strings.LastIndex(arg, ".")] + "." + tool[:1])
352                         cmdStash = append([]string{cmdStash[0], "-o", outfile + ".stash"}, cmdStash[1:]...)
353                         break
354                 }
355         }
356
357         if outfile == "" {
358                 log.Fatalf("cannot determine output file for command: %s", strings.Join(cmd, " "))
359         }
360
361         if *norun {
362                 fmt.Printf("%s\n", strings.Join(cmd, " "))
363                 fmt.Printf("%s\n", strings.Join(cmdStash, " "))
364                 os.Exit(0)
365         }
366
367         out, err := runCmd(cmd, keepLog, outfile+".log")
368         if err != nil {
369                 log.Printf("running: %s", strings.Join(cmd, " "))
370                 os.Stderr.Write(out)
371                 log.Fatal(err)
372         }
373
374         outStash, err := runCmd(cmdStash, keepLog, outfile+".stash.log")
375         if err != nil {
376                 log.Printf("running: %s", strings.Join(cmdStash, " "))
377                 log.Printf("installed tool succeeded but stashed tool failed.\n")
378                 if len(out) > 0 {
379                         log.Printf("installed tool output:")
380                         os.Stderr.Write(out)
381                 }
382                 if len(outStash) > 0 {
383                         log.Printf("stashed tool output:")
384                         os.Stderr.Write(outStash)
385                 }
386                 log.Fatal(err)
387         }
388
389         return outfile, sameObject(outfile, outfile+".stash")
390 }
391
392 func sameObject(file1, file2 string) bool {
393         f1, err := os.Open(file1)
394         if err != nil {
395                 log.Fatal(err)
396         }
397         defer f1.Close()
398
399         f2, err := os.Open(file2)
400         if err != nil {
401                 log.Fatal(err)
402         }
403         defer f2.Close()
404
405         b1 := bufio.NewReader(f1)
406         b2 := bufio.NewReader(f2)
407
408         // Go object files and archives contain lines of the form
409         //      go object <goos> <goarch> <version>
410         // By default, the version on development branches includes
411         // the Git hash and time stamp for the most recent commit.
412         // We allow the versions to differ.
413         if !skipVersion(b1, b2, file1, file2) {
414                 return false
415         }
416
417         lastByte := byte(0)
418         for {
419                 c1, err1 := b1.ReadByte()
420                 c2, err2 := b2.ReadByte()
421                 if err1 == io.EOF && err2 == io.EOF {
422                         return true
423                 }
424                 if err1 != nil {
425                         log.Fatalf("reading %s: %v", file1, err1)
426                 }
427                 if err2 != nil {
428                         log.Fatalf("reading %s: %v", file2, err1)
429                 }
430                 if c1 != c2 {
431                         return false
432                 }
433                 if lastByte == '`' && c1 == '\n' {
434                         if !skipVersion(b1, b2, file1, file2) {
435                                 return false
436                         }
437                 }
438                 lastByte = c1
439         }
440 }
441
442 func skipVersion(b1, b2 *bufio.Reader, file1, file2 string) bool {
443         // Consume "go object " prefix, if there.
444         prefix := "go object "
445         for i := 0; i < len(prefix); i++ {
446                 c1, err1 := b1.ReadByte()
447                 c2, err2 := b2.ReadByte()
448                 if err1 == io.EOF && err2 == io.EOF {
449                         return true
450                 }
451                 if err1 != nil {
452                         log.Fatalf("reading %s: %v", file1, err1)
453                 }
454                 if err2 != nil {
455                         log.Fatalf("reading %s: %v", file2, err1)
456                 }
457                 if c1 != c2 {
458                         return false
459                 }
460                 if c1 != prefix[i] {
461                         return true // matching bytes, just not a version
462                 }
463         }
464
465         // Keep comparing until second space.
466         // Must continue to match.
467         // If we see a \n, it's not a version string after all.
468         for numSpace := 0; numSpace < 2; {
469                 c1, err1 := b1.ReadByte()
470                 c2, err2 := b2.ReadByte()
471                 if err1 == io.EOF && err2 == io.EOF {
472                         return true
473                 }
474                 if err1 != nil {
475                         log.Fatalf("reading %s: %v", file1, err1)
476                 }
477                 if err2 != nil {
478                         log.Fatalf("reading %s: %v", file2, err1)
479                 }
480                 if c1 != c2 {
481                         return false
482                 }
483                 if c1 == '\n' {
484                         return true
485                 }
486                 if c1 == ' ' {
487                         numSpace++
488                 }
489         }
490
491         // Have now seen 'go object goos goarch ' in both files.
492         // Now they're allowed to diverge, until the \n, which
493         // must be present.
494         for {
495                 c1, err1 := b1.ReadByte()
496                 if err1 == io.EOF {
497                         log.Fatalf("reading %s: unexpected EOF", file1)
498                 }
499                 if err1 != nil {
500                         log.Fatalf("reading %s: %v", file1, err1)
501                 }
502                 if c1 == '\n' {
503                         break
504                 }
505         }
506         for {
507                 c2, err2 := b2.ReadByte()
508                 if err2 == io.EOF {
509                         log.Fatalf("reading %s: unexpected EOF", file2)
510                 }
511                 if err2 != nil {
512                         log.Fatalf("reading %s: %v", file2, err2)
513                 }
514                 if c2 == '\n' {
515                         break
516                 }
517         }
518
519         // Consumed "matching" versions from both.
520         return true
521 }
522
523 func runCmd(cmd []string, keepLog bool, logName string) (output []byte, err error) {
524         if *verbose {
525                 log.Print(strings.Join(cmd, " "))
526         }
527
528         if *timing {
529                 t0 := time.Now()
530                 defer func() {
531                         log.Printf("%.3fs elapsed # %s\n", time.Since(t0).Seconds(), strings.Join(cmd, " "))
532                 }()
533         }
534
535         xcmd := exec.Command(cmd[0], cmd[1:]...)
536         if !keepLog {
537                 return xcmd.CombinedOutput()
538         }
539
540         f, err := os.Create(logName)
541         if err != nil {
542                 log.Fatal(err)
543         }
544         fmt.Fprintf(f, "GOOS=%s GOARCH=%s %s\n", os.Getenv("GOOS"), os.Getenv("GOARCH"), strings.Join(cmd, " "))
545         xcmd.Stdout = f
546         xcmd.Stderr = f
547         defer f.Close()
548         return nil, xcmd.Run()
549 }
550
551 func save() {
552         if err := os.MkdirAll(stashDir, 0777); err != nil {
553                 log.Fatal(err)
554         }
555
556         toolDir := filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
557         files, err := ioutil.ReadDir(toolDir)
558         if err != nil {
559                 log.Fatal(err)
560         }
561
562         for _, file := range files {
563                 if shouldSave(file.Name()) && file.Mode().IsRegular() {
564                         cp(filepath.Join(toolDir, file.Name()), filepath.Join(stashDir, file.Name()))
565                 }
566         }
567
568         for _, name := range binTools {
569                 if !shouldSave(name) {
570                         continue
571                 }
572                 src := filepath.Join(binDir, name)
573                 if _, err := os.Stat(src); err == nil {
574                         cp(src, filepath.Join(stashDir, name))
575                 }
576         }
577
578         checkShouldSave()
579 }
580
581 func restore() {
582         files, err := ioutil.ReadDir(stashDir)
583         if err != nil {
584                 log.Fatal(err)
585         }
586
587         for _, file := range files {
588                 if shouldSave(file.Name()) && file.Mode().IsRegular() {
589                         targ := toolDir
590                         if isBinTool(file.Name()) {
591                                 targ = binDir
592                         }
593                         cp(filepath.Join(stashDir, file.Name()), filepath.Join(targ, file.Name()))
594                 }
595         }
596
597         checkShouldSave()
598 }
599
600 func shouldSave(name string) bool {
601         if len(cmd) == 1 {
602                 return true
603         }
604         ok := false
605         for i, arg := range cmd {
606                 if i > 0 && name == arg {
607                         ok = true
608                         cmd[i] = "DONE"
609                 }
610         }
611         return ok
612 }
613
614 func checkShouldSave() {
615         var missing []string
616         for _, arg := range cmd[1:] {
617                 if arg != "DONE" {
618                         missing = append(missing, arg)
619                 }
620         }
621         if len(missing) > 0 {
622                 log.Fatalf("%s did not find tools: %s", cmd[0], strings.Join(missing, " "))
623         }
624 }
625
626 func cp(src, dst string) {
627         if *verbose {
628                 fmt.Printf("cp %s %s\n", src, dst)
629         }
630         data, err := ioutil.ReadFile(src)
631         if err != nil {
632                 log.Fatal(err)
633         }
634         if err := ioutil.WriteFile(dst, data, 0777); err != nil {
635                 log.Fatal(err)
636         }
637 }