gcflags []string // additional flags for Go compiler
actionCache map[cacheKey]*action // a cache of already-constructed actions
mkdirCache map[string]bool // a cache of created directories
+ print func(args ...interface{}) (int, error)
output sync.Mutex
scriptDir string // current directory in printed script
func (b *builder) init() {
var err error
+ b.print = fmt.Print
b.actionCache = make(map[cacheKey]*action)
b.mkdirCache = make(map[string]bool)
b.goarch = buildContext.GOARCH
if err != nil {
if err == errPrintedOutput {
- exitStatus = 2
+ setExitStatus(2)
} else {
errorf("%s", err)
}
os.Remove(dst)
df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
- if runtime.GOOS != "windows" {
+ if !toolIsWindows {
return err
}
// Windows does not allow to replace binary file
func (b *builder) showcmd(dir string, format string, args ...interface{}) {
b.output.Lock()
defer b.output.Unlock()
- fmt.Println(b.fmtcmd(dir, format, args...))
+ b.print(b.fmtcmd(dir, format, args...) + "\n")
}
// showOutput prints "# desc" followed by the given output.
b.output.Lock()
defer b.output.Unlock()
- fmt.Print(prefix, suffix)
+ b.print(prefix, suffix)
}
// relPaths returns a copy of paths with absolute paths
gcargs = append(gcargs, "-+")
}
- binary := filepath.Join(goroot, "bin/go-tool/", b.arch+"g")
- args := stringList(binary, "-o", ofile, b.gcflags, gcargs, importArgs)
+ args := stringList(tool(b.arch+"g"), "-o", ofile, b.gcflags, gcargs, importArgs)
for _, f := range gofiles {
args = append(args, mkAbs(p.Dir, f))
}
func (goToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error {
sfile = mkAbs(p.Dir, sfile)
- binary := filepath.Join(goroot, "bin/go-tool/", b.arch+"a")
- return b.run(p.Dir, p.ImportPath, binary, "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
+ return b.run(p.Dir, p.ImportPath, tool(b.arch+"a"), "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
}
func (goToolchain) pkgpath(basedir string, p *Package) string {
for _, f := range ofiles {
absOfiles = append(absOfiles, mkAbs(objDir, f))
}
- return b.run(p.Dir, p.ImportPath, filepath.Join(goroot, "bin/go-tool/pack"), "grc", mkAbs(objDir, afile), absOfiles)
+ return b.run(p.Dir, p.ImportPath, tool("pack"), "grc", mkAbs(objDir, afile), absOfiles)
}
func (goToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error {
importArgs := b.includeArgs("-L", allactions)
- binary := filepath.Join(goroot, "bin/go-tool/", b.arch+"l")
- return b.run(p.Dir, p.ImportPath, binary, "-o", out, importArgs, mainpkg)
+ return b.run(p.Dir, p.ImportPath, tool(b.arch+"l"), "-o", out, importArgs, mainpkg)
}
func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {
inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch))
cfile = mkAbs(p.Dir, cfile)
- binary := filepath.Join(goroot, "bin/go-tool/", b.arch+"c")
- return b.run(p.Dir, p.ImportPath, binary, "-FVw",
+ return b.run(p.Dir, p.ImportPath, tool(b.arch+"c"), "-FVw",
"-I", objdir, "-I", inc, "-o", ofile,
"-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
}
var cgoRe = regexp.MustCompile(`[/\\:]`)
func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outObj []string, err error) {
- if b.goos != runtime.GOOS {
+ if b.goos != toolGOOS {
return nil, nil, errors.New("cannot use cgo when compiling for a different operating system")
}
// Use pkg.gofiles instead of pkg.Dir so that
// the command only applies to this package,
// not to packages in subdirectories.
- run(stringList("gofix", relPaths(pkg.gofiles)))
+ run(stringList(tool("fix"), relPaths(pkg.gofiles)))
}
}
}
if *getFix {
- run(stringList("gofix", relPaths(p.gofiles)))
+ run(stringList(tool("fix"), relPaths(p.gofiles)))
// The imports might have changed, so reload again.
p = reloadPackage(arg, stk)
"path/filepath"
"regexp"
"strings"
+ "sync"
"text/template"
"unicode"
"unicode/utf8"
}
var exitStatus = 0
+var exitMu sync.Mutex
+
+func setExitStatus(n int) {
+ exitMu.Lock()
+ if exitStatus < n {
+ exitStatus = n
+ }
+ exitMu.Unlock()
+}
func main() {
flag.Usage = usage
func errorf(format string, args ...interface{}) {
log.Printf(format, args...)
- exitStatus = 1
+ setExitStatus(1)
}
var logf = log.Printf
"go/build"
"os"
"path/filepath"
- "runtime"
"sort"
"strings"
"time"
if info.Package == "main" {
_, elem := filepath.Split(importPath)
+ if ctxt.GOOS != toolGOOS || ctxt.GOARCH != toolGOARCH {
+ // Install cross-compiled binaries to subdirectories of bin.
+ elem = ctxt.GOOS + "_" + ctxt.GOARCH + "/" + elem
+ }
if t.Goroot && isGoTool[p.ImportPath] {
p.target = filepath.Join(t.Path, "bin/go-tool", elem)
} else {
- if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
- // Install cross-compiled binaries to subdirectories of bin.
- elem = ctxt.GOOS + "_" + ctxt.GOARCH + "/" + elem
- }
p.target = filepath.Join(t.BinDir(), elem)
}
if ctxt.GOOS == "windows" {
package main
-import "strings"
+import (
+ "fmt"
+ "os"
+ "strings"
+)
var cmdRun = &Command{
UsageLine: "run [-a] [-n] [-x] gofiles... [arguments...]",
cmdRun.Flag.BoolVar(&buildX, "x", false, "")
}
+func printStderr(args ...interface{}) (int, error) {
+ return fmt.Fprint(os.Stderr, args...)
+}
+
func runRun(cmd *Command, args []string) {
var b builder
b.init()
+ b.print = printStderr
i := 0
for i < len(args) && strings.HasSuffix(args[i], ".go") {
i++
"os/exec"
"path"
"path/filepath"
+ "sort"
"strings"
"text/template"
"time"
-i
Install packages that are dependencies of the test.
+ Do not run the test.
-p n
Compile and test up to n packages in parallel.
}
var (
- testC bool // -c flag
- testP int // -p flag
- testX bool // -x flag
- testV bool // -v flag
- testFiles []string // -file flag(s) TODO: not respected
- testArgs []string
- testShowPass bool // whether to display passing output
- testBench bool
+ testC bool // -c flag
+ testI bool // -i flag
+ testP int // -p flag
+ testX bool // -x flag
+ testV bool // -v flag
+ testFiles []string // -file flag(s) TODO: not respected
+ testArgs []string
+ testBench bool
+ testStreamOutput bool // show output as it is generated
+ testShowPass bool // show passing output
)
func runTest(cmd *Command, args []string) {
var pkgArgs []string
pkgArgs, testArgs = testFlags(args)
- // show test PASS output when no packages
- // are listed (implicitly current directory: "go test")
- // or when the -v flag has been given.
- testShowPass = len(pkgArgs) == 0 || testV
-
pkgs := packagesForBuild(pkgArgs)
if len(pkgs) == 0 {
fatalf("no packages to test")
fatalf("cannot use -c flag with multiple packages")
}
+ // show passing test output (after buffering) with -v flag.
+ // must buffer because tests are running in parallel, and
+ // otherwise the output will get mixed.
+ testShowPass = testV
+
+ // stream test output (no buffering) when no package has
+ // been given on the command line (implicit current directory)
+ // or when benchmarking.
+ // Also stream if we're showing output anyway with a
+ // single package under test. In that case, streaming the
+ // output produces the same result as not streaming,
+ // just more immediately.
+ testStreamOutput = len(pkgArgs) == 0 || testBench ||
+ (len(pkgs) <= 1 && testShowPass)
+
buildX = testX
if testP > 0 {
buildP = testP
var b builder
b.init()
+ if testI {
+ buildV = testV
+
+ deps := map[string]bool{
+ // Dependencies for testmain.
+ "testing": true,
+ "regexp": true,
+ }
+ for _, p := range pkgs {
+ // Dependencies for each test.
+ for _, path := range p.info.Imports {
+ deps[path] = true
+ }
+ for _, path := range p.info.TestImports {
+ deps[path] = true
+ }
+ }
+
+ all := []string{}
+ for path := range deps {
+ all = append(all, path)
+ }
+ sort.Strings(all)
+
+ a := &action{}
+ for _, p := range packagesForBuild(all) {
+ a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
+ }
+ b.do(a)
+ return
+ }
+
var builds, runs, prints []*action
// Prepare build + run + print actions for all packages being tested.
}
}
if warned {
- fmt.Fprintf(os.Stderr, "installing these packages with 'go install' will speed future tests.\n\n")
+ fmt.Fprintf(os.Stderr, "installing these packages with 'go test -i' will speed future tests.\n\n")
}
b.do(root)
// We were unable to build the binary.
a.failed = false
fmt.Fprintf(a.testOutput, "FAIL\t%s [build failed]\n", a.p.ImportPath)
- exitStatus = 1
+ setExitStatus(1)
return nil
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = a.p.Dir
var buf bytes.Buffer
- cmd.Stdout = &buf
- cmd.Stderr = &buf
+ if testStreamOutput {
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ } else {
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ }
t0 := time.Now()
err := cmd.Start()
t1 := time.Now()
t := fmt.Sprintf("%.3fs", t1.Sub(t0).Seconds())
if err == nil {
- fmt.Fprintf(a.testOutput, "ok \t%s\t%s\n", a.p.ImportPath, t)
if testShowPass {
a.testOutput.Write(out)
}
+ fmt.Fprintf(a.testOutput, "ok \t%s\t%s\n", a.p.ImportPath, t)
return nil
}
- fmt.Fprintf(a.testOutput, "FAIL\t%s\t%s\n", a.p.ImportPath, t)
- exitStatus = 1
+ setExitStatus(1)
if len(out) > 0 {
a.testOutput.Write(out)
// assume printing the test binary's exit status is superfluous
} else {
fmt.Fprintf(a.testOutput, "%s\n", err)
}
+ fmt.Fprintf(a.testOutput, "FAIL\t%s\t%s\n", a.p.ImportPath, t)
return nil
}
// usage prints a usage message and exits.
func testUsage() {
fmt.Fprint(os.Stderr, usageMessage)
- exitStatus = 2
+ setExitStatus(2)
exit()
}
// local.
{name: "c", isBool: true},
{name: "file", multiOK: true},
+ {name: "i", isBool: true},
{name: "p"},
{name: "x", isBool: true},
switch f.name {
case "c":
setBoolFlag(&testC, value)
+ case "i":
+ setBoolFlag(&testI, value)
case "p":
setIntFlag(&testP, value)
case "x":
"os"
"os/exec"
"path/filepath"
+ "runtime"
"sort"
"strings"
)
}
var (
- toolGoos = build.DefaultContext.GOOS
- toolIsWindows = toolGoos == "windows"
- toolBinToolDir = filepath.Join(build.Path[0].Path, "bin", "go-tool")
+ toolGOOS = runtime.GOOS
+ toolGOARCH = runtime.GOARCH
+ toolIsWindows = toolGOOS == "windows"
+ toolDir = filepath.Join(build.Path[0].Path, "bin", "go-tool")
)
const toolWindowsExtension = ".exe"
+func tool(name string) string {
+ p := filepath.Join(toolDir, name)
+ if toolIsWindows {
+ p += toolWindowsExtension
+ }
+ return p
+}
+
func runTool(cmd *Command, args []string) {
if len(args) == 0 {
listTools()
return
}
- tool := args[0]
+ toolName := args[0]
// The tool name must be lower-case letters and numbers.
- for _, c := range tool {
+ for _, c := range toolName {
switch {
case 'a' <= c && c <= 'z', '0' <= c && c <= '9':
default:
fmt.Fprintf(os.Stderr, "go tool: bad tool name %q\n", tool)
- exitStatus = 2
+ setExitStatus(2)
return
}
}
- toolPath := toolBinToolDir + "/" + tool
- if toolIsWindows {
- toolPath += toolWindowsExtension
- }
+ toolPath := tool(toolName)
// Give a nice message if there is no tool with that name.
if _, err := os.Stat(toolPath); err != nil {
fmt.Fprintf(os.Stderr, "go tool: no such tool %q\n", tool)
- exitStatus = 3
+ setExitStatus(3)
return
}
toolCmd := &exec.Cmd{
err := toolCmd.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "go tool %s failed: %s\n", tool, err)
- exitStatus = 1
+ setExitStatus(1)
return
}
}
// listTools prints a list of the available tools in the go-tools directory.
func listTools() {
- toolDir, err := os.Open(toolBinToolDir)
+ f, err := os.Open(toolDir)
if err != nil {
fmt.Fprintf(os.Stderr, "go tool: no tool directory: %s\n", err)
- exitStatus = 2
+ setExitStatus(2)
return
}
- names, err := toolDir.Readdirnames(-1)
+ defer f.Close()
+ names, err := f.Readdirnames(-1)
if err != nil {
fmt.Fprintf(os.Stderr, "go tool: can't read directory: %s\n", err)
- exitStatus = 2
+ setExitStatus(2)
return
}
sort.Strings(names)
// Use pkg.gofiles instead of pkg.Dir so that
// the command only applies to this package,
// not to packages in subdirectories.
- run("govet", relPaths(pkg.gofiles))
+ run(tool("vet"), relPaths(pkg.gofiles))
}
}