mirror of
https://github.com/golang/go
synced 2024-11-18 09:04:49 -07:00
cmd/toolstash: import from rsc.io
This CL copies rsc.io/toolstash verbatim at commit 7508e1dd47d11b2fc45f544415e014e4f25d3f95 to golang.org/x/tools/cmd/toolstash. There are no code changes to adapt it to its new home; those will happen in a follow-up CL. rsc.io/toolstash will be updated to contain only a README and a doc.go redirecting readers. Change-Id: Icbef4d72215a8b124f857587905b45902d6cdece Reviewed-on: https://go-review.googlesource.com/32681 Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
55f54c0061
commit
133d2e05a6
27
cmd/toolstash/LICENSE
Normal file
27
cmd/toolstash/LICENSE
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
cmd/toolstash/README.md
Normal file
3
cmd/toolstash/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
go get rsc.io/toolstash
|
||||||
|
|
||||||
|
http://godoc.org/rsc.io/toolstash
|
61
cmd/toolstash/buildall
Executable file
61
cmd/toolstash/buildall
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Usage: buildall [-e] [-nocmp] [-work]
|
||||||
|
#
|
||||||
|
# Builds everything (std) for every GOOS/GOARCH combination but installs nothing.
|
||||||
|
#
|
||||||
|
# By default, runs the builds with -toolexec 'toolstash -cmp', to test that the
|
||||||
|
# toolchain is producing bit identical output to a previous known good toolchain.
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# -e: stop at first failure
|
||||||
|
# -nocmp: turn off toolstash -cmp; just check that ordinary builds succeed
|
||||||
|
# -work: pass -work to go command
|
||||||
|
|
||||||
|
sete=false
|
||||||
|
if [ "$1" = "-e" ]; then
|
||||||
|
sete=true
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmp=true
|
||||||
|
if [ "$1" = "-nocmp" ]; then
|
||||||
|
cmp=false
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
work=""
|
||||||
|
if [ "$1" = "-work" ]; then
|
||||||
|
work="-work"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $(go env GOROOT)/src
|
||||||
|
go install cmd/compile cmd/link cmd/asm || exit 1
|
||||||
|
pattern="$1"
|
||||||
|
if [ "$pattern" = "" ]; then
|
||||||
|
pattern=.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# put linux, nacl first in the target list to get all the architectures up front.
|
||||||
|
targets="$((ls runtime | 9 sed -n 's/^rt0_(.*)_(.*)\.s/\1-\2/p'; echo linux-386-387) | sort | egrep -v android-arm | egrep "$pattern" | egrep 'linux|nacl')
|
||||||
|
$(ls runtime | 9 sed -n 's/^rt0_(.*)_(.*)\.s/\1-\2/p' | egrep -v 'android-arm|darwin-arm' | egrep "$pattern" | egrep -v 'linux|nacl')"
|
||||||
|
if [ "$sete" = true ]; then
|
||||||
|
set -e
|
||||||
|
fi
|
||||||
|
for target in $targets
|
||||||
|
do
|
||||||
|
echo $target
|
||||||
|
export GOOS=$(echo $target | sed 's/-.*//')
|
||||||
|
export GOARCH=$(echo $target | sed 's/.*-//')
|
||||||
|
unset GO386
|
||||||
|
if [ "$GOARCH" = "387" ]; then
|
||||||
|
export GOARCH=386
|
||||||
|
export GO386=387
|
||||||
|
fi
|
||||||
|
if $cmp; then
|
||||||
|
go build $work -a -toolexec 'toolstash -cmp' std cmd
|
||||||
|
else
|
||||||
|
go build $work -a std
|
||||||
|
fi
|
||||||
|
done
|
157
cmd/toolstash/cmp.go
Normal file
157
cmd/toolstash/cmp.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hexDumpRE = regexp.MustCompile(`^\t(0x[0-9a-f]{4,})(( ([0-9a-f]{2}| )){16}) [ -\x7F]{1,16}\n`)
|
||||||
|
listingRE = regexp.MustCompile(`^\t(0x[0-9a-f]{4,}) ([0-9]{4,}) \(.*:[0-9]+\)\t`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// okdiffs lists regular expressions for lines to consider minor mismatches.
|
||||||
|
// If one of these regexps matches both of a pair of unequal lines, the mismatch
|
||||||
|
// is reported but not treated as the one worth looking for.
|
||||||
|
// For example, differences in the TEXT line are typically frame size
|
||||||
|
// changes due to optimization decisions made in the body of the function.
|
||||||
|
// Better to keep looking for the actual difference.
|
||||||
|
// Similarly, forward jumps might have different offsets due to a
|
||||||
|
// change in instruction encoding later on.
|
||||||
|
// Better to find that change.
|
||||||
|
var okdiffs = []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`\) TEXT[ ].*,\$`),
|
||||||
|
regexp.MustCompile(`\) WORD[ ].*,\$`),
|
||||||
|
regexp.MustCompile(`\) (B|BR|JMP) `),
|
||||||
|
regexp.MustCompile(`\) FUNCDATA `),
|
||||||
|
regexp.MustCompile(`\\(z|x00)`),
|
||||||
|
regexp.MustCompile(`\$\([0-9]\.[0-9]+e[+\-][0-9]+\)`),
|
||||||
|
regexp.MustCompile(`size=.*value=.*args=.*locals=`),
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareLogs(outfile string) string {
|
||||||
|
f1, err := os.Open(outfile + ".log")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f1.Close()
|
||||||
|
|
||||||
|
f2, err := os.Open(outfile + ".stash.log")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
|
||||||
|
b1 := bufio.NewReader(f1)
|
||||||
|
b2 := bufio.NewReader(f2)
|
||||||
|
|
||||||
|
offset := int64(0)
|
||||||
|
textOffset := offset
|
||||||
|
textLineno := 0
|
||||||
|
lineno := 0
|
||||||
|
var line1, line2 string
|
||||||
|
var prefix bytes.Buffer
|
||||||
|
Reading:
|
||||||
|
for {
|
||||||
|
var err1, err2 error
|
||||||
|
line1, err1 = b1.ReadString('\n')
|
||||||
|
line2, err2 = b2.ReadString('\n')
|
||||||
|
if strings.Contains(line1, ")\tTEXT\t") {
|
||||||
|
textOffset = offset
|
||||||
|
textLineno = lineno
|
||||||
|
}
|
||||||
|
offset += int64(len(line1))
|
||||||
|
lineno++
|
||||||
|
if err1 == io.EOF && err2 == io.EOF {
|
||||||
|
return "no differences in debugging output"
|
||||||
|
}
|
||||||
|
|
||||||
|
if lineno == 1 || line1 == line2 && err1 == nil && err2 == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Lines are inconsistent. Worth stopping?
|
||||||
|
for _, re := range okdiffs {
|
||||||
|
if re.MatchString(line1) && re.MatchString(line2) {
|
||||||
|
fmt.Fprintf(&prefix, "inconsistent log line:\n%s:%d:\n\t%s\n%s:%d:\n\t%s\n\n",
|
||||||
|
f1.Name(), lineno, strings.TrimSuffix(line1, "\n"),
|
||||||
|
f2.Name(), lineno, strings.TrimSuffix(line2, "\n"))
|
||||||
|
continue Reading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1 != nil {
|
||||||
|
line1 = err1.Error()
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
line2 = err2.Error()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("inconsistent log line:\n%s:%d:\n\t%s\n%s:%d:\n\t%s",
|
||||||
|
f1.Name(), lineno, strings.TrimSuffix(line1, "\n"),
|
||||||
|
f2.Name(), lineno, strings.TrimSuffix(line2, "\n"))
|
||||||
|
|
||||||
|
if m := hexDumpRE.FindStringSubmatch(line1); m != nil {
|
||||||
|
target, err := strconv.ParseUint(m[1], 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
goto Skip
|
||||||
|
}
|
||||||
|
|
||||||
|
m2 := hexDumpRE.FindStringSubmatch(line2)
|
||||||
|
if m2 == nil {
|
||||||
|
goto Skip
|
||||||
|
}
|
||||||
|
|
||||||
|
fields1 := strings.Fields(m[2])
|
||||||
|
fields2 := strings.Fields(m2[2])
|
||||||
|
i := 0
|
||||||
|
for i < len(fields1) && i < len(fields2) && fields1[i] == fields2[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
target += uint64(i)
|
||||||
|
|
||||||
|
f1.Seek(textOffset, 0)
|
||||||
|
b1 = bufio.NewReader(f1)
|
||||||
|
last := ""
|
||||||
|
lineno := textLineno
|
||||||
|
limitAddr := uint64(0)
|
||||||
|
lastAddr := uint64(0)
|
||||||
|
for {
|
||||||
|
line1, err1 := b1.ReadString('\n')
|
||||||
|
if err1 != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lineno++
|
||||||
|
if m := listingRE.FindStringSubmatch(line1); m != nil {
|
||||||
|
addr, _ := strconv.ParseUint(m[1], 0, 64)
|
||||||
|
if addr > target {
|
||||||
|
limitAddr = addr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
last = line1
|
||||||
|
lastAddr = addr
|
||||||
|
} else if hexDumpRE.FindStringSubmatch(line1) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last != "" {
|
||||||
|
msg = fmt.Sprintf("assembly instruction at %#04x-%#04x:\n%s:%d\n\t%s\n\n%s",
|
||||||
|
lastAddr, limitAddr, f1.Name(), lineno-1, strings.TrimSuffix(last, "\n"), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Skip:
|
||||||
|
|
||||||
|
return prefix.String() + msg
|
||||||
|
}
|
600
cmd/toolstash/main.go
Normal file
600
cmd/toolstash/main.go
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
// 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 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 "rsc.io/toolstash"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"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 rsc.io/toolstash
|
||||||
|
`
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprint(os.Stderr, usageMessage)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
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) bool {
|
||||||
|
switch name {
|
||||||
|
case "compile", "link", "asm":
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
goroot = runtime.GOROOT()
|
||||||
|
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.LastIndex(tool, "/"); i >= 0 {
|
||||||
|
tool = tool[i+1:]
|
||||||
|
}
|
||||||
|
if i := strings.LastIndex(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) {
|
||||||
|
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"
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
log.Fatalf("unknown tool %s", tool)
|
||||||
|
|
||||||
|
case tool == "compile" || strings.HasSuffix(tool, "g"): // compiler
|
||||||
|
cmdN := append([]string{cmd[0], "-N"}, cmd[1:]...)
|
||||||
|
_, ok := cmpRun(false, cmdN)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("compiler output differs, even with optimizers disabled (-N)")
|
||||||
|
cmd = append([]string{cmd[0], "-v", "-N", "-m=2"}, cmd[1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmd = append([]string{cmd[0], "-v", "-m=2"}, cmd[1:]...)
|
||||||
|
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 := append([]string{cmd[0], extra}, cmd[1:]...)
|
||||||
|
outfile, ok = cmpRun(true, cmdS)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "\n%s\n", compareLogs(outfile))
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, err1)
|
||||||
|
}
|
||||||
|
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, err2)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user