Commit fd1c1b96 authored by Russ Cox's avatar Russ Cox

cmd/go: work toward build script

The commands in the standard tree are now named
by the pseudo-import paths cmd/gofmt etc.
This avoids ambiguity between cmd/go's directory
and go/token's parent directory.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5503050
parent 87201057
......@@ -6,6 +6,8 @@
// to update builtin.c.boot. This is not done automatically
// to avoid depending on having a working compiler binary.
// +build ignore
package PACKAGE
// emitted by compiler, not referred to by go programs
......
......@@ -6,6 +6,8 @@
// to update builtin.c.boot. This is not done automatically
// to avoid depending on having a working compiler binary.
// +build ignore
package PACKAGE
type Pointer uintptr // not really; filled in by compiler
......
......@@ -6,6 +6,7 @@ package main
import (
"bytes"
"errors"
"fmt"
"go/build"
"io"
......@@ -14,8 +15,8 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
)
// Break init cycles
......@@ -108,7 +109,11 @@ type builder struct {
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.
......@@ -148,9 +153,10 @@ func (b *builder) init(aflag, nflag, xflag bool) {
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 {
......@@ -312,7 +318,11 @@ func (b *builder) do(a *action) {
}
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
}
}
......@@ -321,6 +331,14 @@ func (b *builder) do(a *action) {
// 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"))
......@@ -362,7 +380,7 @@ func (b *builder) build(a *action) error {
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
}
......@@ -405,7 +423,7 @@ func (b *builder) build(a *action) error {
// 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)
......@@ -439,7 +457,7 @@ func (b *builder) build(a *action) error {
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)
......@@ -448,14 +466,14 @@ func (b *builder) build(a *action) error {
// 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
}
......@@ -467,7 +485,7 @@ func (b *builder) build(a *action) error {
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
}
}
......@@ -503,7 +521,7 @@ func (b *builder) install(a *action) error {
// 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
}
......@@ -528,27 +546,91 @@ func (b *builder) copyFile(dst, src string, perm uint32) error {
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
}
......@@ -562,8 +644,17 @@ func (b *builder) run(dir string, cmdline ...string) error {
// 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
}
......@@ -571,7 +662,7 @@ func (b *builder) run(dir string, cmdline ...string) error {
// 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
}
......@@ -583,50 +674,75 @@ func (b *builder) mkdir(dir string) error {
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
......@@ -645,7 +761,7 @@ func (b *builder) gccCmd(objdir string, flags []string, args ...string) []string
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"}
......@@ -664,14 +780,14 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
}
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)
......@@ -680,7 +796,7 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
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)
......@@ -688,28 +804,28 @@ func (b *builder) cgo(dir, obj string, csfiles []string, p *Package) (outGo, out
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)
......
......@@ -14,7 +14,7 @@ var cmdList = &Command{
UsageLine: "list [-f format] [-json] [importpath...]",
Short: "list packages",
Long: `
List lists the packages named by the import paths.
List lists the packages named by the import paths, one per line.
The default output shows the package name and file system location:
......@@ -24,7 +24,7 @@ The default output shows the package name and file system location:
The -f flag specifies an alternate format for the list,
using the syntax of package template. The default output
is equivalent to -f '{{.Name}} {{.Dir}}'. The struct
is equivalent to -f '{{.ImportPath}}'. The struct
being passed to the template is:
type Package struct {
......@@ -57,7 +57,7 @@ func init() {
cmdList.Run = runList // break init cycle
}
var listFmt = cmdList.Flag.String("f", "{{.Name}} {{.Dir}}", "")
var listFmt = cmdList.Flag.String("f", "{{.ImportPath}}", "")
var listJson = cmdList.Flag.Bool("json", false, "")
var nl = []byte{'\n'}
......
......@@ -237,8 +237,38 @@ func run(cmdline ...string) {
// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT.
func allPackages() []string {
have := make(map[string]bool)
have := map[string]bool{
"builtin": true, // ignore pseudo-package that exists only for documentation
}
var pkgs []string
// Commands
goroot := build.Path[0].Path
cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
if err != nil || !fi.IsDir() {
return nil
}
name := path[len(cmd):]
// Commands are all in cmd/, not in subdirectories.
if strings.Contains(name, string(filepath.Separator)) {
return filepath.SkipDir
}
_, err = build.ScanDir(path)
if err != nil {
return nil
}
// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
name = "cmd/" + name
if !have[name] {
have[name] = true
pkgs = append(pkgs, name)
}
return nil
})
for _, t := range build.Path {
src := t.SrcDir() + string(filepath.Separator)
filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
......@@ -256,21 +286,19 @@ func allPackages() []string {
return nil
}
name := path[len(src):]
if have[name] {
return nil
if !have[name] {
pkgs = append(pkgs, name)
have[name] = true
}
pkgs = append(pkgs, name)
have[name] = true
// Avoid go/build test data.
// TODO: Move it into a testdata directory.
if path == filepath.Join(build.Path[0].SrcDir(), "go/build") {
return filepath.SkipDir
}
return nil
})
// TODO: Commands.
}
return pkgs
}
......@@ -70,13 +70,15 @@ func loadPackage(arg string) (*Package, error) {
// Find basic information about package path.
t, importPath, err := build.FindTree(arg)
dir := ""
// Maybe it is a standard command.
if err != nil && !filepath.IsAbs(arg) && !strings.HasPrefix(arg, ".") {
if err != nil && !filepath.IsAbs(arg) && strings.HasPrefix(arg, "cmd/") {
goroot := build.Path[0]
p := filepath.Join(goroot.Path, "src/cmd", arg)
p := filepath.Join(goroot.Path, "src", arg)
if st, err1 := os.Stat(p); err1 == nil && st.IsDir() {
t = goroot
importPath = "../cmd/" + arg
importPath = arg
dir = p
err = nil
}
}
......@@ -84,7 +86,9 @@ func loadPackage(arg string) (*Package, error) {
return nil, err
}
dir := filepath.Join(t.SrcDir(), filepath.FromSlash(importPath))
if dir == "" {
dir = filepath.Join(t.SrcDir(), filepath.FromSlash(importPath))
}
// Maybe we know the package by its directory.
if p := packageCache[dir]; p != nil {
......@@ -140,6 +144,13 @@ func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string
}
sort.Strings(p.gofiles)
// Packages that use cgo import runtime/cgo implicitly,
// except runtime/cgo itself.
if len(info.CgoFiles) > 0 && (!p.Standard || p.ImportPath != "runtime/cgo") {
p.Imports = append(p.Imports, "runtime/cgo")
sort.Strings(p.Imports)
}
// Record package under both import path and full directory name.
packageCache[dir] = p
packageCache[importPath] = p
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment