import (
"bytes"
+ "errors"
"fmt"
"go/build"
"io"
"os/exec"
"path/filepath"
"regexp"
- "runtime"
"strings"
+ "sync"
)
// Break init cycles
goroot string // the $GOROOT
goarch string // the $GOARCH
goos string // the $GOOS
+ gobin string // the $GOBIN
actionCache map[cacheKey]*action // a cache of already-constructed actions
+
+ output sync.Mutex
+ scriptDir string // current directory in printed script
}
// An action represents a single action in the action graph.
b.nflag = nflag
b.xflag = xflag
b.actionCache = make(map[cacheKey]*action)
- b.goroot = runtime.GOROOT()
b.goarch = build.DefaultContext.GOARCH
b.goos = build.DefaultContext.GOOS
+ b.goroot = build.Path[0].Path
+ b.gobin = build.Path[0].BinDir()
b.arch, err = build.ArchChar(b.goarch)
if err != nil {
}
if a.f != nil {
if err := a.f(b, a); err != nil {
- errorf("%s", err)
+ if err == errPrintedOutput {
+ exitStatus = 2
+ } else {
+ errorf("%s", err)
+ }
a.failed = true
}
}
// build is the action for building a single package or command.
func (b *builder) build(a *action) error {
+ if b.nflag {
+ // In -n mode, print a banner between packages.
+ // The banner is five lines so that when changes to
+ // different sections of the bootstrap script have to
+ // be merged, the banners give patch something
+ // to use to find its context.
+ fmt.Printf("\n#\n# %s\n#\n\n", a.p.ImportPath)
+ }
obj := filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator)
if a.pkgobj == "" {
a.pkgobj = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a"))
sfiles = nil
}
- outGo, outObj, err := b.cgo(a.p.Dir, obj, gccfiles, a.p)
+ outGo, outObj, err := b.cgo(a.p, obj, gccfiles)
if err != nil {
return err
}
// additional reflect type data.
gcargs = append(gcargs, "-+")
}
- if err := b.gc(a.p.Dir, obj+out, gcargs, inc, gofiles); err != nil {
+ if err := b.gc(a.p, obj+out, gcargs, inc, gofiles); err != nil {
return err
}
objects = append(objects, out)
for _, file := range cfiles {
out := file[:len(file)-len(".c")] + "." + b.arch
- if err := b.cc(a.p.Dir, obj, obj+out, file); err != nil {
+ if err := b.cc(a.p, obj, obj+out, file); err != nil {
return err
}
objects = append(objects, out)
// assemble .s files
for _, file := range sfiles {
out := file[:len(file)-len(".s")] + "." + b.arch
- if err := b.asm(a.p.Dir, obj, obj+out, file); err != nil {
+ if err := b.asm(a.p, obj, obj+out, file); err != nil {
return err
}
objects = append(objects, out)
}
// pack into archive
- if err := b.gopack(obj, a.pkgobj, objects); err != nil {
+ if err := b.gopack(a.p, obj, a.pkgobj, objects); err != nil {
return err
}
inc[i] = "-L"
}
a.pkgbin = obj + "a.out"
- if err := b.ld(a.p.Dir, a.pkgbin, inc, a.pkgobj); err != nil {
+ if err := b.ld(a.p, a.pkgbin, inc, a.pkgobj); err != nil {
return err
}
}
// copyFile is like 'cp src dst'.
func (b *builder) copyFile(dst, src string, perm uint32) error {
if b.nflag || b.xflag {
- b.showcmd("cp %s %s", src, dst)
+ b.showcmd("", "cp %s %s", src, dst)
if b.nflag {
return nil
}
return nil
}
-// fmtcmd is like fmt.Sprintf but replaces references to the
-// work directory (a temporary directory with a clumsy name)
-// with $WORK.
-func (b *builder) fmtcmd(format string, args ...interface{}) string {
- s := fmt.Sprintf(format, args...)
- s = strings.Replace(s, b.work, "$WORK", -1)
- return s
+// fmtcmd formats a command in the manner of fmt.Sprintf but also:
+//
+// If dir is non-empty and the script is not in dir right now,
+// fmtcmd inserts "cd dir\n" before the command.
+//
+// fmtcmd replaces the value of b.work with $WORK.
+// fmtcmd replaces the value of b.goroot with $GOROOT.
+// fmtcmd replaces the value of b.gobin with $GOBIN.
+//
+// fmtcmd replaces the name of the current directory with dot (.)
+// but only when it is at the beginning of a space-separated token.
+//
+func (b *builder) fmtcmd(dir string, format string, args ...interface{}) string {
+ cmd := fmt.Sprintf(format, args...)
+ if dir != "" {
+ cmd = strings.Replace(" "+cmd, " "+dir, " .", -1)[1:]
+ if b.scriptDir != dir {
+ b.scriptDir = dir
+ cmd = " cd " + dir + "\n" + cmd
+ }
+ }
+ cmd = strings.Replace(cmd, b.work, "$WORK", -1)
+ cmd = strings.Replace(cmd, b.gobin, "$GOBIN", -1)
+ cmd = strings.Replace(cmd, b.goroot, "$GOROOT", -1)
+ return cmd
}
// showcmd prints the given command to standard output
// for the implementation of -n or -x.
-func (b *builder) showcmd(format string, args ...interface{}) {
- fmt.Println(b.fmtcmd(format, args...))
+func (b *builder) showcmd(dir string, format string, args ...interface{}) {
+ b.output.Lock()
+ defer b.output.Unlock()
+ fmt.Println(b.fmtcmd(dir, format, args...))
+}
+
+// showOutput prints "# desc" followed by the given output.
+// The output is expected to contain references to 'dir', usually
+// the source directory for the package that has failed to build.
+// showOutput rewrites mentions of dir with a relative path to dir.
+// This is usually shorter and more pleasant than the absolute path.
+// For example, if fmt doesn't compile and we are in src/pkg/html,
+// the output is
+//
+// $ go build
+// # fmt
+// ../fmt/print.go:1090: undefined: asdf
+// $
+//
+// instead of
+//
+// $ go build
+// # fmt
+// /usr/gopher/go/src/pkg/fmt/print.go:1090: undefined: asdf
+// $
+//
+// showOutput also replaces references to the work directory with $WORK.
+//
+func (b *builder) showOutput(dir, desc, out string) {
+ prefix := "# " + desc
+ suffix := "\n" + out
+ pwd, _ := os.Getwd()
+ if reldir, err := filepath.Rel(pwd, dir); err == nil {
+ suffix = strings.Replace(suffix, " "+dir, " "+reldir, -1)
+ suffix = strings.Replace(suffix, "\n"+dir, "\n"+reldir, -1)
+ }
+ suffix = strings.Replace(suffix, " "+b.work, " $WORK", -1)
+
+ b.output.Lock()
+ defer b.output.Unlock()
+ fmt.Print(prefix, suffix)
}
+// errPrintedOutput is a special error indicating that a command failed
+// but that it generated output as well, and that output has already
+// been printed, so there's no point showing 'exit status 1' or whatever
+// the wait status was. The main executor, builder.do, knows not to
+// print this error.
+var errPrintedOutput = errors.New("already printed output - no need to show error")
+
// run runs the command given by cmdline in the directory dir.
// If the commnd fails, run prints information about the failure
// and returns a non-nil error.
-func (b *builder) run(dir string, cmdline ...string) error {
+func (b *builder) run(dir string, desc string, cmdline ...string) error {
if b.nflag || b.xflag {
- b.showcmd("cd %s; %s", dir, strings.Join(cmdline, " "))
+ b.showcmd(dir, "%s", strings.Join(cmdline, " "))
if b.nflag {
return nil
}
// TODO: cmd.Env
err := cmd.Run()
if buf.Len() > 0 {
- fmt.Fprintf(os.Stderr, "# cd %s; %s\n", dir, strings.Join(cmdline, " "))
- fmt.Fprintf(os.Stderr, "%s\n", buf.Bytes())
+ out := buf.Bytes()
+ if out[len(out)-1] != '\n' {
+ out = append(out, '\n')
+ }
+ if desc == "" {
+ desc = b.fmtcmd(dir, "%s", strings.Join(cmdline, " "))
+ }
+ b.showOutput(dir, desc, string(out))
+ if err != nil {
+ err = errPrintedOutput
+ }
}
return err
}
// mkdir makes the named directory.
func (b *builder) mkdir(dir string) error {
if b.nflag || b.xflag {
- b.showcmd("mkdir -p %s", dir)
+ b.showcmd("", "mkdir -p %s", dir)
if b.nflag {
return nil
}
return nil
}
+// mkAbs returns an absolute path corresponding to
+// evaluating f in the directory dir.
+// We always pass absolute paths of source files so that
+// the error messages will include the full path to a file
+// in need of attention.
+func mkAbs(dir, f string) string {
+ // Leave absolute paths alone.
+ // Also, during -n mode we use the pseudo-directory $WORK
+ // instead of creating an actual work directory that won't be used.
+ // Leave paths beginning with $WORK alone too.
+ if filepath.IsAbs(f) || strings.HasPrefix(f, "$WORK") {
+ return f
+ }
+ return filepath.Join(dir, f)
+}
+
// gc runs the Go compiler in a specific directory on a set of files
// to generate the named output file.
-func (b *builder) gc(dir, ofile string, gcargs, importArgs []string, gofiles []string) error {
+func (b *builder) gc(p *Package, ofile string, gcargs, importArgs []string, gofiles []string) error {
args := []string{b.arch + "g", "-o", ofile}
args = append(args, gcargs...)
args = append(args, importArgs...)
- args = append(args, gofiles...)
- return b.run(dir, args...)
+ for _, f := range gofiles {
+ args = append(args, mkAbs(p.Dir, f))
+ }
+ return b.run(p.Dir, p.ImportPath, args...)
}
// asm runs the assembler in a specific directory on a specific file
// to generate the named output file.
-func (b *builder) asm(dir, obj, ofile, sfile string) error {
- return b.run(dir, b.arch+"a", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
+func (b *builder) asm(p *Package, obj, ofile, sfile string) error {
+ sfile = mkAbs(p.Dir, sfile)
+ return b.run(p.Dir, p.ImportPath, b.arch+"a", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile)
}
// gopack runs the assembler in a specific directory to create
// an archive from a set of object files.
// typically it is run in the object directory.
-func (b *builder) gopack(objDir, afile string, ofiles []string) error {
- return b.run(objDir, append([]string{"gopack", "grc", afile}, ofiles...)...)
+func (b *builder) gopack(p *Package, objDir, afile string, ofiles []string) error {
+ cmd := []string{"gopack", "grc"}
+ cmd = append(cmd, mkAbs(objDir, afile))
+ for _, f := range ofiles {
+ cmd = append(cmd, mkAbs(objDir, f))
+ }
+ return b.run(p.Dir, p.ImportPath, cmd...)
}
// ld runs the linker to create a package starting at mainpkg.
-func (b *builder) ld(dir, out string, importArgs []string, mainpkg string) error {
- return b.run(dir, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)
+func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string) error {
+ return b.run(p.Dir, p.ImportPath, append(append([]string{b.arch + "l", "-o", out}, importArgs...), mainpkg)...)
}
// cc runs the gc-toolchain C compiler in a directory on a C file
// to produce an output file.
-func (b *builder) cc(dir, objdir, ofile, cfile string) error {
- inc := filepath.Join(runtime.GOROOT(), "pkg",
- fmt.Sprintf("%s_%s", b.goos, b.goarch))
- return b.run(dir, b.arch+"c", "-FVw", "-I", objdir, "-I", inc, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
+func (b *builder) cc(p *Package, objdir, ofile, cfile string) error {
+ inc := filepath.Join(b.goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch))
+ cfile = mkAbs(p.Dir, cfile)
+ return b.run(p.Dir, p.ImportPath, b.arch+"c", "-FVw", "-I", objdir, "-I", inc, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile)
}
// gcc runs the gcc C compiler to create an object from a single C file.
-func (b *builder) gcc(dir, out string, flags []string, cfile string) error {
- return b.run(dir, b.gccCmd(dir, flags, "-o", out, "-c", cfile)...)
+func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error {
+ cfile = mkAbs(p.Dir, cfile)
+ return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir, flags, "-o", out, "-c", cfile)...)
}
// gccld runs the gcc linker to create an executable from a set of object files
-func (b *builder) gccld(dir, out string, flags []string, obj []string) error {
- return b.run(dir, append(b.gccCmd(dir, flags, "-o", out), obj...)...)
+func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error {
+ return b.run(p.Dir, p.ImportPath, append(b.gccCmd(p.Dir, flags, "-o", out), obj...)...)
}
// gccCmd returns a gcc command line ending with args
var cgoRe = regexp.MustCompile(`[/\\:]`)
-func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, outObj []string, err error) {
+func (b *builder) cgo(p *Package, obj string, gccfiles []string) (outGo, outObj []string, err error) {
// cgo
// TODO: CGOPKGPATH, CGO_FLAGS?
gofiles := []string{obj + "_cgo_gotypes.go"}
}
cgoArgs = append(cgoArgs, "--")
cgoArgs = append(cgoArgs, p.CgoFiles...)
- if err := b.run(dir, cgoArgs...); err != nil {
+ if err := b.run(p.Dir, p.ImportPath, cgoArgs...); err != nil {
return nil, nil, err
}
outGo = append(outGo, gofiles...)
// cc _cgo_defun.c
defunObj := obj + "_cgo_defun." + b.arch
- if err := b.cc(dir, obj, defunObj, defunC); err != nil {
+ if err := b.cc(p, obj, defunObj, defunC); err != nil {
return nil, nil, err
}
outObj = append(outObj, defunObj)
var linkobj []string
for _, cfile := range cfiles {
ofile := obj + cfile[:len(cfile)-1] + "o"
- if err := b.gcc(dir, ofile, p.info.CgoCFLAGS, obj+cfile); err != nil {
+ if err := b.gcc(p, ofile, p.info.CgoCFLAGS, obj+cfile); err != nil {
return nil, nil, err
}
linkobj = append(linkobj, ofile)
outObj = append(outObj, ofile)
}
}
- for _, file := range csfiles {
+ for _, file := range gccfiles {
ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o"
- if err := b.gcc(dir, ofile, p.info.CgoCFLAGS, file); err != nil {
+ if err := b.gcc(p, ofile, p.info.CgoCFLAGS, file); err != nil {
return nil, nil, err
}
linkobj = append(linkobj, ofile)
outObj = append(outObj, ofile)
}
dynobj := obj + "_cgo_.o"
- if err := b.gccld(dir, dynobj, p.info.CgoLDFLAGS, linkobj); err != nil {
+ if err := b.gccld(p, dynobj, p.info.CgoLDFLAGS, linkobj); err != nil {
return nil, nil, err
}
// cgo -dynimport
importC := obj + "_cgo_import.c"
- if err := b.run(dir, "cgo", "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
+ if err := b.run(p.Dir, p.ImportPath, "cgo", "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil {
return nil, nil, err
}
// cc _cgo_import.ARCH
importObj := obj + "_cgo_import." + b.arch
- if err := b.cc(dir, obj, importObj, importC); err != nil {
+ if err := b.cc(p, obj, importObj, importC); err != nil {
return nil, nil, err
}
outObj = append(outObj, importObj)