.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / cmd / toolstash / main.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/cmd/toolstash/main.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/cmd/toolstash/main.go
new file mode 100644 (file)
index 0000000..4c34942
--- /dev/null
@@ -0,0 +1,637 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Toolstash provides a way to save, run, and restore a known good copy of the Go toolchain
+// and to compare the object files generated by two toolchains.
+//
+// Usage:
+//
+//     toolstash [-n] [-v] save [tool...]
+//     toolstash [-n] [-v] restore [tool...]
+//     toolstash [-n] [-v] [-t] go run x.go
+//     toolstash [-n] [-v] [-t] [-cmp] compile x.go
+//
+// The toolstash command manages a ``stashed'' copy of the Go toolchain
+// kept in $GOROOT/pkg/toolstash. In this case, the toolchain means the
+// tools available with the 'go tool' command as well as the go, godoc, and gofmt
+// binaries.
+//
+// The command ``toolstash save'', typically run when the toolchain is known to be working,
+// copies the toolchain from its installed location to the toolstash directory.
+// Its inverse, ``toolchain restore'', typically run when the toolchain is known to be broken,
+// copies the toolchain from the toolstash directory back to the installed locations.
+// If additional arguments are given, the save or restore applies only to the named tools.
+// Otherwise, it applies to all tools.
+//
+// Otherwise, toolstash's arguments should be a command line beginning with the
+// name of a toolchain binary, which may be a short name like compile or a complete path
+// to an installed binary. Toolstash runs the command line using the stashed
+// copy of the binary instead of the installed one.
+//
+// The -n flag causes toolstash to print the commands that would be executed
+// but not execute them. The combination -n -cmp shows the two commands
+// that would be compared and then exits successfully. A real -cmp run might
+// run additional commands for diagnosis of an output mismatch.
+//
+// The -v flag causes toolstash to print the commands being executed.
+//
+// The -t flag causes toolstash to print the time elapsed during while the
+// command ran.
+//
+// Comparing
+//
+// The -cmp flag causes toolstash to run both the installed and the stashed
+// copy of an assembler or compiler and check that they produce identical
+// object files. If not, toolstash reports the mismatch and exits with a failure status.
+// As part of reporting the mismatch, toolstash reinvokes the command with
+// the -S=2 flag and identifies the first divergence in the assembly output.
+// If the command is a Go compiler, toolstash also determines whether the
+// difference is triggered by optimization passes.
+// On failure, toolstash leaves additional information in files named
+// similarly to the default output file. If the compilation would normally
+// produce a file x.6, the output from the stashed tool is left in x.6.stash
+// and the debugging traces are left in x.6.log and x.6.stash.log.
+//
+// The -cmp flag is a no-op when the command line is not invoking an
+// assembler or compiler.
+//
+// For example, when working on code cleanup that should not affect
+// compiler output, toolstash can be used to compare the old and new
+// compiler output:
+//
+//     toolstash save
+//     <edit compiler sources>
+//     go tool dist install cmd/compile # install compiler only
+//     toolstash -cmp compile x.go
+//
+// Go Command Integration
+//
+// The go command accepts a -toolexec flag that specifies a program
+// to use to run the build tools.
+//
+// To build with the stashed tools:
+//
+//     go build -toolexec toolstash x.go
+//
+// To build with the stashed go command and the stashed tools:
+//
+//     toolstash go build -toolexec toolstash x.go
+//
+// To verify that code cleanup in the compilers does not make any
+// changes to the objects being generated for the entire tree:
+//
+//     # Build working tree and save tools.
+//     ./make.bash
+//     toolstash save
+//
+//     <edit compiler sources>
+//
+//     # Install new tools, but do not rebuild the rest of tree,
+//     # since the compilers might generate buggy code.
+//     go tool dist install cmd/compile
+//
+//     # Check that new tools behave identically to saved tools.
+//     go build -toolexec 'toolstash -cmp' -a std
+//
+//     # If not, restore, in order to keep working on Go code.
+//     toolstash restore
+//
+// Version Skew
+//
+// The Go tools write the current Go version to object files, and (outside
+// release branches) that version includes the hash and time stamp
+// of the most recent Git commit. Functionally equivalent
+// compilers built at different Git versions may produce object files that
+// differ only in the recorded version. Toolstash ignores version mismatches
+// when comparing object files, but the standard tools will refuse to compile
+// or link together packages with different object versions.
+//
+// For the full build in the final example above to work, both the stashed
+// and the installed tools must use the same version string.
+// One way to ensure this is not to commit any of the changes being
+// tested, so that the Git HEAD hash is the same for both builds.
+// A more robust way to force the tools to have the same version string
+// is to write a $GOROOT/VERSION file, which overrides the Git-based version
+// computation:
+//
+//     echo devel >$GOROOT/VERSION
+//
+// The version can be arbitrary text, but to pass all.bash's API check, it must
+// contain the substring ``devel''. The VERSION file must be created before
+// building either version of the toolchain.
+//
+package main // import "golang.org/x/tools/cmd/toolstash"
+
+import (
+       "bufio"
+       "flag"
+       "fmt"
+       exec "golang.org/x/sys/execabs"
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "path/filepath"
+       "runtime"
+       "strings"
+       "time"
+)
+
+var usageMessage = `usage: toolstash [-n] [-v] [-cmp] command line
+
+Examples:
+       toolstash save
+       toolstash restore
+       toolstash go run x.go
+       toolstash compile x.go
+       toolstash -cmp compile x.go
+
+For details, godoc golang.org/x/tools/cmd/toolstash
+`
+
+func usage() {
+       fmt.Fprint(os.Stderr, usageMessage)
+       os.Exit(2)
+}
+
+var (
+       goCmd   = flag.String("go", "go", "path to \"go\" command")
+       norun   = flag.Bool("n", false, "print but do not run commands")
+       verbose = flag.Bool("v", false, "print commands being run")
+       cmp     = flag.Bool("cmp", false, "compare tool object files")
+       timing  = flag.Bool("t", false, "print time commands take")
+)
+
+var (
+       cmd       []string
+       tool      string // name of tool: "go", "compile", etc
+       toolStash string // path to stashed tool
+
+       goroot   string
+       toolDir  string
+       stashDir string
+       binDir   string
+)
+
+func canCmp(name string, args []string) bool {
+       switch name {
+       case "asm", "compile", "link":
+               if len(args) == 1 && (args[0] == "-V" || strings.HasPrefix(args[0], "-V=")) {
+                       // cmd/go uses "compile -V=full" to query the tool's build ID.
+                       return false
+               }
+               return true
+       }
+       return len(name) == 2 && '0' <= name[0] && name[0] <= '9' && (name[1] == 'a' || name[1] == 'g' || name[1] == 'l')
+}
+
+var binTools = []string{"go", "godoc", "gofmt"}
+
+func isBinTool(name string) bool {
+       return strings.HasPrefix(name, "go")
+}
+
+func main() {
+       log.SetFlags(0)
+       log.SetPrefix("toolstash: ")
+
+       flag.Usage = usage
+       flag.Parse()
+       cmd = flag.Args()
+
+       if len(cmd) < 1 {
+               usage()
+       }
+
+       s, err := exec.Command(*goCmd, "env", "GOROOT").CombinedOutput()
+       if err != nil {
+               log.Fatalf("%s env GOROOT: %v", *goCmd, err)
+       }
+       goroot = strings.TrimSpace(string(s))
+       toolDir = filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
+       stashDir = filepath.Join(goroot, "pkg/toolstash")
+
+       binDir = os.Getenv("GOBIN")
+       if binDir == "" {
+               binDir = filepath.Join(goroot, "bin")
+       }
+
+       switch cmd[0] {
+       case "save":
+               save()
+               return
+
+       case "restore":
+               restore()
+               return
+       }
+
+       tool = cmd[0]
+       if i := strings.LastIndexAny(tool, `/\`); i >= 0 {
+               tool = tool[i+1:]
+       }
+
+       if !strings.HasPrefix(tool, "a.out") {
+               toolStash = filepath.Join(stashDir, tool)
+               if _, err := os.Stat(toolStash); err != nil {
+                       log.Print(err)
+                       os.Exit(2)
+               }
+
+               if *cmp && canCmp(tool, cmd[1:]) {
+                       compareTool()
+                       return
+               }
+               cmd[0] = toolStash
+       }
+
+       if *norun {
+               fmt.Printf("%s\n", strings.Join(cmd, " "))
+               return
+       }
+       if *verbose {
+               log.Print(strings.Join(cmd, " "))
+       }
+       xcmd := exec.Command(cmd[0], cmd[1:]...)
+       xcmd.Stdin = os.Stdin
+       xcmd.Stdout = os.Stdout
+       xcmd.Stderr = os.Stderr
+       err = xcmd.Run()
+       if err != nil {
+               log.Fatal(err)
+       }
+       os.Exit(0)
+}
+
+func compareTool() {
+       if !strings.Contains(cmd[0], "/") && !strings.Contains(cmd[0], `\`) {
+               cmd[0] = filepath.Join(toolDir, tool)
+       }
+
+       outfile, ok := cmpRun(false, cmd)
+       if ok {
+               os.Remove(outfile + ".stash")
+               return
+       }
+
+       extra := "-S=2"
+       switch {
+       default:
+               log.Fatalf("unknown tool %s", tool)
+
+       case tool == "compile" || strings.HasSuffix(tool, "g"): // compiler
+               useDashN := true
+               dashcIndex := -1
+               for i, s := range cmd {
+                       if s == "-+" {
+                               // Compiling runtime. Don't use -N.
+                               useDashN = false
+                       }
+                       if strings.HasPrefix(s, "-c=") {
+                               dashcIndex = i
+                       }
+               }
+               cmdN := injectflags(cmd, nil, useDashN)
+               _, ok := cmpRun(false, cmdN)
+               if !ok {
+                       if useDashN {
+                               log.Printf("compiler output differs, with optimizers disabled (-N)")
+                       } else {
+                               log.Printf("compiler output differs")
+                       }
+                       if dashcIndex >= 0 {
+                               cmd[dashcIndex] = "-c=1"
+                       }
+                       cmd = injectflags(cmd, []string{"-v", "-m=2"}, useDashN)
+                       break
+               }
+               if dashcIndex >= 0 {
+                       cmd[dashcIndex] = "-c=1"
+               }
+               cmd = injectflags(cmd, []string{"-v", "-m=2"}, false)
+               log.Printf("compiler output differs, only with optimizers enabled")
+
+       case tool == "asm" || strings.HasSuffix(tool, "a"): // assembler
+               log.Printf("assembler output differs")
+
+       case tool == "link" || strings.HasSuffix(tool, "l"): // linker
+               log.Printf("linker output differs")
+               extra = "-v=2"
+       }
+
+       cmdS := injectflags(cmd, []string{extra}, false)
+       outfile, _ = cmpRun(true, cmdS)
+
+       fmt.Fprintf(os.Stderr, "\n%s\n", compareLogs(outfile))
+       os.Exit(2)
+}
+
+func injectflags(cmd []string, extra []string, addDashN bool) []string {
+       x := []string{cmd[0]}
+       if addDashN {
+               x = append(x, "-N")
+       }
+       x = append(x, extra...)
+       x = append(x, cmd[1:]...)
+       return x
+}
+
+func cmpRun(keepLog bool, cmd []string) (outfile string, match bool) {
+       cmdStash := make([]string, len(cmd))
+       copy(cmdStash, cmd)
+       cmdStash[0] = toolStash
+       for i, arg := range cmdStash {
+               if arg == "-o" {
+                       outfile = cmdStash[i+1]
+                       cmdStash[i+1] += ".stash"
+                       break
+               }
+               if strings.HasSuffix(arg, ".s") || strings.HasSuffix(arg, ".go") && '0' <= tool[0] && tool[0] <= '9' {
+                       outfile = filepath.Base(arg[:strings.LastIndex(arg, ".")] + "." + tool[:1])
+                       cmdStash = append([]string{cmdStash[0], "-o", outfile + ".stash"}, cmdStash[1:]...)
+                       break
+               }
+       }
+
+       if outfile == "" {
+               log.Fatalf("cannot determine output file for command: %s", strings.Join(cmd, " "))
+       }
+
+       if *norun {
+               fmt.Printf("%s\n", strings.Join(cmd, " "))
+               fmt.Printf("%s\n", strings.Join(cmdStash, " "))
+               os.Exit(0)
+       }
+
+       out, err := runCmd(cmd, keepLog, outfile+".log")
+       if err != nil {
+               log.Printf("running: %s", strings.Join(cmd, " "))
+               os.Stderr.Write(out)
+               log.Fatal(err)
+       }
+
+       outStash, err := runCmd(cmdStash, keepLog, outfile+".stash.log")
+       if err != nil {
+               log.Printf("running: %s", strings.Join(cmdStash, " "))
+               log.Printf("installed tool succeeded but stashed tool failed.\n")
+               if len(out) > 0 {
+                       log.Printf("installed tool output:")
+                       os.Stderr.Write(out)
+               }
+               if len(outStash) > 0 {
+                       log.Printf("stashed tool output:")
+                       os.Stderr.Write(outStash)
+               }
+               log.Fatal(err)
+       }
+
+       return outfile, sameObject(outfile, outfile+".stash")
+}
+
+func sameObject(file1, file2 string) bool {
+       f1, err := os.Open(file1)
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer f1.Close()
+
+       f2, err := os.Open(file2)
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer f2.Close()
+
+       b1 := bufio.NewReader(f1)
+       b2 := bufio.NewReader(f2)
+
+       // Go object files and archives contain lines of the form
+       //      go object <goos> <goarch> <version>
+       // By default, the version on development branches includes
+       // the Git hash and time stamp for the most recent commit.
+       // We allow the versions to differ.
+       if !skipVersion(b1, b2, file1, file2) {
+               return false
+       }
+
+       lastByte := byte(0)
+       for {
+               c1, err1 := b1.ReadByte()
+               c2, err2 := b2.ReadByte()
+               if err1 == io.EOF && err2 == io.EOF {
+                       return true
+               }
+               if err1 != nil {
+                       log.Fatalf("reading %s: %v", file1, err1)
+               }
+               if err2 != nil {
+                       log.Fatalf("reading %s: %v", file2, err1)
+               }
+               if c1 != c2 {
+                       return false
+               }
+               if lastByte == '`' && c1 == '\n' {
+                       if !skipVersion(b1, b2, file1, file2) {
+                               return false
+                       }
+               }
+               lastByte = c1
+       }
+}
+
+func skipVersion(b1, b2 *bufio.Reader, file1, file2 string) bool {
+       // Consume "go object " prefix, if there.
+       prefix := "go object "
+       for i := 0; i < len(prefix); i++ {
+               c1, err1 := b1.ReadByte()
+               c2, err2 := b2.ReadByte()
+               if err1 == io.EOF && err2 == io.EOF {
+                       return true
+               }
+               if err1 != nil {
+                       log.Fatalf("reading %s: %v", file1, err1)
+               }
+               if err2 != nil {
+                       log.Fatalf("reading %s: %v", file2, err1)
+               }
+               if c1 != c2 {
+                       return false
+               }
+               if c1 != prefix[i] {
+                       return true // matching bytes, just not a version
+               }
+       }
+
+       // Keep comparing until second space.
+       // Must continue to match.
+       // If we see a \n, it's not a version string after all.
+       for numSpace := 0; numSpace < 2; {
+               c1, err1 := b1.ReadByte()
+               c2, err2 := b2.ReadByte()
+               if err1 == io.EOF && err2 == io.EOF {
+                       return true
+               }
+               if err1 != nil {
+                       log.Fatalf("reading %s: %v", file1, err1)
+               }
+               if err2 != nil {
+                       log.Fatalf("reading %s: %v", file2, err1)
+               }
+               if c1 != c2 {
+                       return false
+               }
+               if c1 == '\n' {
+                       return true
+               }
+               if c1 == ' ' {
+                       numSpace++
+               }
+       }
+
+       // Have now seen 'go object goos goarch ' in both files.
+       // Now they're allowed to diverge, until the \n, which
+       // must be present.
+       for {
+               c1, err1 := b1.ReadByte()
+               if err1 == io.EOF {
+                       log.Fatalf("reading %s: unexpected EOF", file1)
+               }
+               if err1 != nil {
+                       log.Fatalf("reading %s: %v", file1, err1)
+               }
+               if c1 == '\n' {
+                       break
+               }
+       }
+       for {
+               c2, err2 := b2.ReadByte()
+               if err2 == io.EOF {
+                       log.Fatalf("reading %s: unexpected EOF", file2)
+               }
+               if err2 != nil {
+                       log.Fatalf("reading %s: %v", file2, err2)
+               }
+               if c2 == '\n' {
+                       break
+               }
+       }
+
+       // Consumed "matching" versions from both.
+       return true
+}
+
+func runCmd(cmd []string, keepLog bool, logName string) (output []byte, err error) {
+       if *verbose {
+               log.Print(strings.Join(cmd, " "))
+       }
+
+       if *timing {
+               t0 := time.Now()
+               defer func() {
+                       log.Printf("%.3fs elapsed # %s\n", time.Since(t0).Seconds(), strings.Join(cmd, " "))
+               }()
+       }
+
+       xcmd := exec.Command(cmd[0], cmd[1:]...)
+       if !keepLog {
+               return xcmd.CombinedOutput()
+       }
+
+       f, err := os.Create(logName)
+       if err != nil {
+               log.Fatal(err)
+       }
+       fmt.Fprintf(f, "GOOS=%s GOARCH=%s %s\n", os.Getenv("GOOS"), os.Getenv("GOARCH"), strings.Join(cmd, " "))
+       xcmd.Stdout = f
+       xcmd.Stderr = f
+       defer f.Close()
+       return nil, xcmd.Run()
+}
+
+func save() {
+       if err := os.MkdirAll(stashDir, 0777); err != nil {
+               log.Fatal(err)
+       }
+
+       toolDir := filepath.Join(goroot, fmt.Sprintf("pkg/tool/%s_%s", runtime.GOOS, runtime.GOARCH))
+       files, err := ioutil.ReadDir(toolDir)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       for _, file := range files {
+               if shouldSave(file.Name()) && file.Mode().IsRegular() {
+                       cp(filepath.Join(toolDir, file.Name()), filepath.Join(stashDir, file.Name()))
+               }
+       }
+
+       for _, name := range binTools {
+               if !shouldSave(name) {
+                       continue
+               }
+               src := filepath.Join(binDir, name)
+               if _, err := os.Stat(src); err == nil {
+                       cp(src, filepath.Join(stashDir, name))
+               }
+       }
+
+       checkShouldSave()
+}
+
+func restore() {
+       files, err := ioutil.ReadDir(stashDir)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       for _, file := range files {
+               if shouldSave(file.Name()) && file.Mode().IsRegular() {
+                       targ := toolDir
+                       if isBinTool(file.Name()) {
+                               targ = binDir
+                       }
+                       cp(filepath.Join(stashDir, file.Name()), filepath.Join(targ, file.Name()))
+               }
+       }
+
+       checkShouldSave()
+}
+
+func shouldSave(name string) bool {
+       if len(cmd) == 1 {
+               return true
+       }
+       ok := false
+       for i, arg := range cmd {
+               if i > 0 && name == arg {
+                       ok = true
+                       cmd[i] = "DONE"
+               }
+       }
+       return ok
+}
+
+func checkShouldSave() {
+       var missing []string
+       for _, arg := range cmd[1:] {
+               if arg != "DONE" {
+                       missing = append(missing, arg)
+               }
+       }
+       if len(missing) > 0 {
+               log.Fatalf("%s did not find tools: %s", cmd[0], strings.Join(missing, " "))
+       }
+}
+
+func cp(src, dst string) {
+       if *verbose {
+               fmt.Printf("cp %s %s\n", src, dst)
+       }
+       data, err := ioutil.ReadFile(src)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err := ioutil.WriteFile(dst, data, 0777); err != nil {
+               log.Fatal(err)
+       }
+}