package main
+import (
+ "bytes"
+ "fmt"
+ "go/build"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+// Break init cycles
+func init() {
+ cmdBuild.Run = runBuild
+ cmdInstall.Run = runInstall
+}
+
var cmdBuild = &Command{
- Run: runBuild,
- UsageLine: "build [-n] [-v] [importpath...]",
- Short: "compile and install packages and dependencies",
+ UsageLine: "build [-a] [-n] [-v] [importpath... | gofiles...]",
+ Short: "compile packages and dependencies",
Long: `
Build compiles the packages named by the import paths,
along with their dependencies, but it does not install the results.
+If the arguments are a list of .go files, build compiles them into
+a package object or command executable named for the first
+source file.
+
+The -a flag forces rebuilding of packages that are already up-to-date.
The -n flag prints the commands but does not run them.
The -v flag prints the commands.
`,
}
+var buildA = cmdBuild.Flag.Bool("a", false, "")
var buildN = cmdBuild.Flag.Bool("n", false, "")
var buildV = cmdBuild.Flag.Bool("v", false, "")
func runBuild(cmd *Command, args []string) {
- args = importPaths(args)
- _ = args
- panic("build not implemented")
+ var b builder
+ b.init(*buildA, *buildN, *buildV)
+
+ if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
+ b.do(b.action(modeInstall, modeBuild, goFilesPackage(args, "")))
+ return
+ }
+
+ a := &action{f: (*builder).nop}
+ for _, p := range packages(args) {
+ a.deps = append(a.deps, b.action(modeBuild, modeBuild, p))
+ }
+ b.do(a)
}
var cmdInstall = &Command{
- Run: runInstall,
- UsageLine: "install [-n] [-v] [importpath...]",
- Short: "install packages and dependencies",
+ UsageLine: "install [-a] [-n] [-v] [importpath...]",
+ Short: "compile and install packages and dependencies",
Long: `
Install compiles and installs the packages named by the import paths,
along with their dependencies.
+The -a flag forces reinstallation of packages that are already up-to-date.
The -n flag prints the commands but does not run them.
The -v flag prints the commands.
`,
}
+var installA = cmdInstall.Flag.Bool("a", false, "")
var installN = cmdInstall.Flag.Bool("n", false, "")
var installV = cmdInstall.Flag.Bool("v", false, "")
func runInstall(cmd *Command, args []string) {
- args = importPaths(args)
- _ = args
- panic("install not implemented")
+ var b builder
+ b.init(*installA, *installN, *installV)
+ a := &action{f: (*builder).nop}
+ for _, p := range packages(args) {
+ a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
+ }
+ b.do(a)
+}
+
+// A builder holds global state about a build.
+// It does not hold per-package state, because eventually we will
+// build packages in parallel, and the builder will be shared.
+type builder struct {
+ work string // the temporary work directory (ends in filepath.Separator)
+ aflag bool // the -a flag
+ nflag bool // the -n flag
+ vflag bool // the -v flag
+ arch string // e.g., "6"
+ actionCache map[cacheKey]*action // a cache of already-constructed actions
+}
+
+// An action represents a single action in the action graph.
+type action struct {
+ f func(*builder, *action) error // the action itself
+
+ p *Package // the package this action works on
+ deps []*action // actions that must happen before this one
+ done bool // whether the action is done (might have failed)
+ failed bool // whether the action failed
+
+ // Results left for communication with other code.
+ pkgobj string // the built .a file
+ pkgbin string // the built a.out file, if one exists
+}
+
+// cacheKey is the key for the action cache.
+type cacheKey struct {
+ mode buildMode
+ p *Package
+}
+
+// buildMode specifies the build mode:
+// are we just building things or also installing the results?
+type buildMode int
+
+const (
+ modeBuild buildMode = iota
+ modeInstall
+)
+
+func (b *builder) init(aflag, nflag, vflag bool) {
+ var err error
+ b.aflag = aflag
+ b.nflag = nflag
+ b.vflag = vflag
+ b.actionCache = make(map[cacheKey]*action)
+
+ b.arch, err = build.ArchChar(build.DefaultContext.GOARCH)
+ if err != nil {
+ fatalf("%s", err)
+ }
+
+ if nflag {
+ b.work = "$WORK"
+ } else {
+ b.work, err = ioutil.TempDir("", "go-build")
+ if err != nil {
+ fatalf("%s", err)
+ }
+ if vflag {
+ fmt.Printf("WORK=%s\n", b.work)
+ }
+ atexit(func() { os.RemoveAll(b.work) })
+ }
+}
+
+// goFilesPackage creates a package for building a collection of Go files
+// (typically named on the command line). If target is given, the package
+// target is target. Otherwise, the target is named p.a for
+// package p or named after the first Go file for package main.
+func goFilesPackage(gofiles []string, target string) *Package {
+ // Synthesize fake "directory" that only shows those two files,
+ // to make it look like this is a standard package or
+ // command directory.
+ var dir []os.FileInfo
+ for _, file := range gofiles {
+ fi, err := os.Stat(file)
+ if err != nil {
+ fatalf("%s", err)
+ }
+ if fi.IsDir() {
+ fatalf("%s is a directory, should be a Go file", file)
+ }
+ dir = append(dir, fi)
+ }
+ ctxt := build.DefaultContext
+ ctxt.ReadDir = func(string) ([]os.FileInfo, error) { return dir, nil }
+ pwd, _ := os.Getwd()
+ pkg, err := scanPackage(&ctxt, &build.Tree{Path: "."}, "<command line>", "<command line>", pwd)
+ if err != nil {
+ fatalf("%s", err)
+ }
+ if target != "" {
+ pkg.targ = target
+ } else if pkg.Name == "main" {
+ pkg.targ = gofiles[0][:len(gofiles[0])-len(".go")]
+ } else {
+ pkg.targ = pkg.Name + ".a"
+ }
+ pkg.ImportPath = "_/" + pkg.targ
+ return pkg
+}
+
+// action returns the action for applying the given operation (mode) to the package.
+// depMode is the action to use when building dependencies.
+func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action {
+ key := cacheKey{mode, p}
+ a := b.actionCache[key]
+ if a != nil {
+ return a
+ }
+
+ a = &action{p: p}
+ b.actionCache[key] = a
+
+ switch mode {
+ case modeBuild, modeInstall:
+ if !needInstall(p) && !b.aflag {
+ a.f = (*builder).nop
+ return a
+ }
+ if p.Standard {
+ switch p.ImportPath {
+ case "runtime", "runtime/cgo":
+ // Too complex - can't build.
+ a.f = (*builder).nop
+ return a
+ case "builtin", "unsafe":
+ // Fake packages - nothing to build.
+ a.f = (*builder).nop
+ return a
+ }
+ }
+
+ if mode == modeInstall {
+ a.f = (*builder).install
+ a.deps = []*action{b.action(modeBuild, depMode, p)}
+ return a
+ }
+
+ a.f = (*builder).build
+ for _, p1 := range p.imports {
+ a.deps = append(a.deps, b.action(depMode, depMode, p1))
+ }
+ }
+
+ return a
+}
+
+// needInstall reports whether p needs to be built and installed.
+// That is only true if some source file is newer than the installed package binary.
+func needInstall(p *Package) bool {
+ if p.targ == "" {
+ return true
+ }
+ fi, err := os.Stat(p.targ)
+ if err != nil {
+ return true
+ }
+ t := fi.ModTime()
+
+ srcss := [][]string{
+ p.GoFiles,
+ p.CFiles,
+ p.SFiles,
+ p.CgoFiles,
+ }
+ for _, srcs := range srcss {
+ for _, src := range srcs {
+ fi, err := os.Stat(filepath.Join(p.Dir, src))
+ if err != nil {
+ return true
+ }
+ if fi.ModTime().After(t) {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// do runs the action graph rooted at a.
+func (b *builder) do(a *action) {
+ if a.done {
+ return
+ }
+ for _, a1 := range a.deps {
+ b.do(a1)
+ if a1.failed {
+ a.failed = true
+ a.done = true
+ return
+ }
+ }
+ if err := a.f(b, a); err != nil {
+ errorf("%s", err)
+ a.failed = true
+ }
+ a.done = true
+}
+
+func (b *builder) nop(a *action) error {
+ return nil
+}
+
+// build is the action for building a single package.
+func (b *builder) build(a *action) error {
+ obj := filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator)
+ a.pkgobj = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a"))
+
+ // make build directory
+ if err := b.mkdir(obj); err != nil {
+ return err
+ }
+
+ var objects []string
+ var gofiles []string
+ gofiles = append(gofiles, a.p.GoFiles...)
+
+ // run cgo
+ if len(a.p.CgoFiles) > 0 {
+ outGo, outObj, err := b.cgo(a.p.Dir, obj, a.p.info)
+ if err != nil {
+ return err
+ }
+ objects = append(objects, outObj...)
+ gofiles = append(gofiles, outGo...)
+ }
+
+ // prepare Go import path list
+ var inc []string
+ inc = append(inc, "-I", b.work)
+ incMap := map[string]bool{}
+ for _, a1 := range a.deps {
+ p1 := a1.p
+ if p1.t.Goroot {
+ continue
+ }
+ pkgdir := p1.t.PkgDir()
+ if !incMap[pkgdir] {
+ incMap[pkgdir] = true
+ inc = append(inc, "-I", pkgdir)
+ }
+ }
+
+ // compile Go
+ if len(gofiles) > 0 {
+ out := "_go_.6"
+ if err := b.gc(a.p.Dir, obj+out, a.p.ImportPath, inc, gofiles); err != nil {
+ return err
+ }
+ objects = append(objects, out)
+ }
+
+ // assemble .s files
+ if len(a.p.SFiles) > 0 {
+ for _, sfile := range a.p.SFiles {
+ out := sfile[:len(sfile)-len(".s")] + "." + b.arch
+ if err := b.asm(a.p.Dir, obj+out, sfile); err != nil {
+ return err
+ }
+ objects = append(objects, out)
+ }
+ }
+
+ // pack into archive
+ if err := b.gopack(obj, a.pkgobj, objects); err != nil {
+ return err
+ }
+
+ if a.p.Name == "main" {
+ // command.
+ // import paths for compiler are introduced by -I.
+ // for linker, they are introduced by -L.
+ for i := 0; i < len(inc); i += 2 {
+ inc[i] = "-L"
+ }
+ a.pkgbin = obj + "a.out"
+ if err := b.ld(a.p.Dir, a.pkgbin, inc, a.pkgobj); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// install is the action for installing a single package.
+func (b *builder) install(a *action) error {
+ if err := b.build(a); err != nil {
+ return err
+ }
+
+ var src string
+ var perm uint32
+ if a.pkgbin != "" {
+ src = a.pkgbin
+ perm = 0777
+ } else {
+ src = a.pkgobj
+ perm = 0666
+ }
+
+ // make target directory
+ dst := a.p.targ
+ dir, _ := filepath.Split(dst)
+ if dir != "" {
+ if err := b.mkdir(dir); err != nil {
+ return err
+ }
+ }
+
+ return b.copyFile(dst, src, perm)
+}
+
+// copyFile is like 'cp src dst'.
+func (b *builder) copyFile(dst, src string, perm uint32) error {
+ if b.nflag || b.vflag {
+ b.showcmd("cp %s %s", src, dst)
+ if b.nflag {
+ return nil
+ }
+ }
+
+ sf, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer sf.Close()
+ os.Remove(dst)
+ df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(df, sf)
+ df.Close()
+ if err != nil {
+ os.Remove(dst)
+ return err
+ }
+ 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
+}
+
+// showcmd prints the given command to standard output
+// for the implementation of -n or -v.
+func (b *builder) showcmd(format string, args ...interface{}) {
+ fmt.Println(b.fmtcmd(format, args...))
+}
+
+// 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 {
+ if b.nflag || b.vflag {
+ b.showcmd("cd %s; %s", dir, strings.Join(cmdline, " "))
+ if b.nflag {
+ return nil
+ }
+ }
+
+ var buf bytes.Buffer
+ cmd := exec.Command(cmdline[0], cmdline[1:]...)
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.Dir = dir
+ // 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())
+ }
+ return err
+}
+
+// mkdir makes the named directory.
+func (b *builder) mkdir(dir string) error {
+ if b.nflag || b.vflag {
+ b.showcmd("mkdir -p %s", dir)
+ if b.nflag {
+ return nil
+ }
+ }
+
+ if err := os.MkdirAll(dir, 0777); err != nil {
+ return err
+ }
+ return nil
+}
+
+// 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, importPath string, importArgs []string, gofiles []string) error {
+ args := append([]string{b.arch + "g", "-o", ofile, "-p", importPath}, importArgs...)
+ args = append(args, gofiles...)
+ return b.run(dir, args...)
+}
+
+// asm runs the assembler in a specific directory on a specific file
+// to generate the named output file.
+func (b *builder) asm(dir, ofile, sfile string) error {
+ return b.run(dir, b.arch+"a", "-o", ofile, 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...)...)
+}
+
+// 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)...)
+}
+
+// cc runs the gc-toolchain C compiler in a directory on a C file
+// to produce an output file.
+func (b *builder) cc(dir, ofile, cfile string) error {
+ inc := filepath.Join(runtime.GOROOT(), "pkg",
+ fmt.Sprintf("%s_%s", build.DefaultContext.GOOS, build.DefaultContext.GOARCH))
+ return b.run(dir, b.arch+"c", "-FVW", "-I", inc, "-o", ofile, 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)...)
+}
+
+// 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...)...)
+}
+
+// gccCmd returns a gcc command line ending with args
+func (b *builder) gccCmd(objdir string, flags []string, args ...string) []string {
+ // TODO: HOST_CC?
+ a := []string{"gcc", "-I", objdir, "-g", "-fPIC", "-O2"}
+ switch b.arch {
+ case "8":
+ a = append(a, "-m32")
+ case "6":
+ a = append(a, "-m64")
+ }
+ a = append(a, flags...)
+ return append(a, args...)
+}
+
+var cgoRe = regexp.MustCompile(`[/\\:]`)
+
+func (b *builder) cgo(dir, obj string, info *build.DirInfo) (outGo, outObj []string, err error) {
+ // cgo
+ // TODO: CGOPKGPATH, CGO_FLAGS?
+ gofiles := []string{obj + "_cgo_gotypes.go"}
+ cfiles := []string{"_cgo_main.c", "_cgo_export.c"}
+ for _, fn := range info.CgoFiles {
+ f := cgoRe.ReplaceAllString(fn[:len(fn)-2], "_")
+ gofiles = append(gofiles, obj+f+"cgo1.go")
+ cfiles = append(cfiles, f+"cgo2.c")
+ }
+ defunC := obj + "_cgo_defun.c"
+ // TODO: make cgo not depend on $GOARCH?
+ // TODO: make cgo write to obj
+ if err := b.run(dir, append([]string{"cgo", "-objdir", obj, "--"}, info.CgoFiles...)...); 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, defunObj, defunC); err != nil {
+ return nil, nil, err
+ }
+ outObj = append(outObj, defunObj)
+
+ // gcc
+ var linkobj []string
+ for _, cfile := range cfiles {
+ ofile := obj + cfile[:len(cfile)-1] + "o"
+ if err := b.gcc(dir, ofile, info.CgoCFLAGS, obj+cfile); err != nil {
+ return nil, nil, err
+ }
+ linkobj = append(linkobj, ofile)
+ if !strings.HasSuffix(ofile, "_cgo_main.o") {
+ outObj = append(outObj, ofile)
+ }
+ }
+ for _, cfile := range info.CFiles {
+ ofile := obj + cgoRe.ReplaceAllString(cfile[:len(cfile)-1], "_") + "o"
+ if err := b.gcc(dir, ofile, info.CgoCFLAGS, cfile); err != nil {
+ return nil, nil, err
+ }
+ linkobj = append(linkobj, ofile)
+ outObj = append(outObj, ofile)
+ }
+ dynobj := obj + "_cgo_.o"
+ if err := b.gccld(dir, dynobj, 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 {
+ return nil, nil, err
+ }
+
+ // cc _cgo_import.ARCH
+ importObj := obj + "_cgo_import." + b.arch
+ if err := b.cc(dir, importObj, importC); err != nil {
+ return nil, nil, err
+ }
+ outObj = append(outObj, importObj)
+
+ return outGo, outObj, nil
}