| // Copyright 2011 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 build provides tools for building Go packages. |
| package build |
| |
| import ( |
| "bytes" |
| "exec" |
| "fmt" |
| "os" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| "strings" |
| ) |
| |
| // Build produces a build Script for the given package. |
| func Build(tree *Tree, pkg string, info *DirInfo) (*Script, os.Error) { |
| s := &Script{} |
| b := &build{ |
| script: s, |
| path: filepath.Join(tree.SrcDir(), pkg), |
| } |
| b.obj = b.abs("_obj") + string(filepath.Separator) |
| |
| b.goarch = runtime.GOARCH |
| if g := os.Getenv("GOARCH"); g != "" { |
| b.goarch = g |
| } |
| var err os.Error |
| b.arch, err = ArchChar(b.goarch) |
| if err != nil { |
| return nil, err |
| } |
| |
| // .go files to be built with gc |
| gofiles := b.abss(info.GoFiles...) |
| s.addInput(gofiles...) |
| |
| var ofiles []string // object files to be linked or packed |
| |
| // make build directory |
| b.mkdir(b.obj) |
| s.addIntermediate(b.obj) |
| |
| // cgo |
| if len(info.CgoFiles) > 0 { |
| cgoFiles := b.abss(info.CgoFiles...) |
| s.addInput(cgoFiles...) |
| outGo, outObj := b.cgo(cgoFiles) |
| gofiles = append(gofiles, outGo...) |
| ofiles = append(ofiles, outObj...) |
| s.addIntermediate(outGo...) |
| s.addIntermediate(outObj...) |
| } |
| |
| // compile |
| if len(gofiles) > 0 { |
| ofile := b.obj + "_go_." + b.arch |
| b.gc(ofile, gofiles...) |
| ofiles = append(ofiles, ofile) |
| s.addIntermediate(ofile) |
| } |
| |
| // assemble |
| for _, sfile := range info.SFiles { |
| ofile := b.obj + sfile[:len(sfile)-1] + b.arch |
| sfile = b.abs(sfile) |
| s.addInput(sfile) |
| b.asm(ofile, sfile) |
| ofiles = append(ofiles, ofile) |
| s.addIntermediate(ofile) |
| } |
| |
| if len(ofiles) == 0 { |
| return nil, os.NewError("make: no object files to build") |
| } |
| |
| // choose target file |
| var targ string |
| if info.IsCommand() { |
| // use the last part of the import path as binary name |
| _, bin := filepath.Split(pkg) |
| if runtime.GOOS == "windows" { |
| bin += ".exe" |
| } |
| targ = filepath.Join(tree.BinDir(), bin) |
| } else { |
| targ = filepath.Join(tree.PkgDir(), pkg+".a") |
| } |
| |
| // make target directory |
| targDir, _ := filepath.Split(targ) |
| b.mkdir(targDir) |
| |
| // link binary or pack object |
| if info.IsCommand() { |
| b.ld(targ, ofiles...) |
| } else { |
| b.gopack(targ, ofiles...) |
| } |
| s.Output = append(s.Output, targ) |
| |
| return b.script, nil |
| } |
| |
| // A Script describes the build process for a Go package. |
| // The Input, Intermediate, and Output fields are lists of absolute paths. |
| type Script struct { |
| Cmd []*Cmd |
| Input []string |
| Intermediate []string |
| Output []string |
| } |
| |
| func (s *Script) addInput(file ...string) { |
| s.Input = append(s.Input, file...) |
| } |
| |
| func (s *Script) addIntermediate(file ...string) { |
| s.Intermediate = append(s.Intermediate, file...) |
| } |
| |
| // Run runs the Script's Cmds in order. |
| func (s *Script) Run() os.Error { |
| for _, c := range s.Cmd { |
| if err := c.Run(); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // Stale returns true if the build's inputs are newer than its outputs. |
| func (s *Script) Stale() bool { |
| var latest int64 |
| // get latest mtime of outputs |
| for _, file := range s.Output { |
| fi, err := os.Stat(file) |
| if err != nil { |
| // any error reading output files means stale |
| return true |
| } |
| if m := fi.Mtime_ns; m > latest { |
| latest = m |
| } |
| } |
| for _, file := range s.Input { |
| fi, err := os.Stat(file) |
| if err != nil || fi.Mtime_ns > latest { |
| // any error reading input files means stale |
| // (attempt to rebuild to figure out why) |
| return true |
| } |
| } |
| return false |
| } |
| |
| // Clean removes the Script's Intermediate files. |
| // It tries to remove every file and returns the first error it encounters. |
| func (s *Script) Clean() (err os.Error) { |
| // Reverse order so that directories get removed after the files they contain. |
| for i := len(s.Intermediate) - 1; i >= 0; i-- { |
| if e := os.Remove(s.Intermediate[i]); err == nil { |
| err = e |
| } |
| } |
| return |
| } |
| |
| // Clean removes the Script's Intermediate and Output files. |
| // It tries to remove every file and returns the first error it encounters. |
| func (s *Script) Nuke() (err os.Error) { |
| // Reverse order so that directories get removed after the files they contain. |
| for i := len(s.Output) - 1; i >= 0; i-- { |
| if e := os.Remove(s.Output[i]); err == nil { |
| err = e |
| } |
| } |
| if e := s.Clean(); err == nil { |
| err = e |
| } |
| return |
| } |
| |
| // A Cmd describes an individual build command. |
| type Cmd struct { |
| Args []string // command-line |
| Stdout string // write standard output to this file, "" is passthrough |
| Dir string // working directory |
| Env []string // environment |
| Input []string // file paths (dependencies) |
| Output []string // file paths |
| } |
| |
| func (c *Cmd) String() string { |
| return strings.Join(c.Args, " ") |
| } |
| |
| // Run executes the Cmd. |
| func (c *Cmd) Run() os.Error { |
| out := new(bytes.Buffer) |
| cmd := exec.Command(c.Args[0], c.Args[1:]...) |
| cmd.Dir = c.Dir |
| cmd.Env = c.Env |
| cmd.Stdout = out |
| cmd.Stderr = out |
| if c.Stdout != "" { |
| f, err := os.Create(c.Stdout) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| cmd.Stdout = f |
| } |
| if err := cmd.Run(); err != nil { |
| return fmt.Errorf("command %q: %v\n%v", c, err, out) |
| } |
| return nil |
| } |
| |
| // ArchChar returns the architecture character for the given goarch. |
| // For example, ArchChar("amd64") returns "6". |
| func ArchChar(goarch string) (string, os.Error) { |
| switch goarch { |
| case "386": |
| return "8", nil |
| case "amd64": |
| return "6", nil |
| case "arm": |
| return "5", nil |
| } |
| return "", os.NewError("unsupported GOARCH " + goarch) |
| } |
| |
| type build struct { |
| script *Script |
| path string |
| obj string |
| goarch string |
| arch string |
| } |
| |
| func (b *build) abs(file string) string { |
| if filepath.IsAbs(file) { |
| return file |
| } |
| return filepath.Join(b.path, file) |
| } |
| |
| func (b *build) abss(file ...string) []string { |
| s := make([]string, len(file)) |
| for i, f := range file { |
| s[i] = b.abs(f) |
| } |
| return s |
| } |
| |
| func (b *build) add(c Cmd) { |
| b.script.Cmd = append(b.script.Cmd, &c) |
| } |
| |
| func (b *build) mkdir(name string) { |
| b.add(Cmd{ |
| Args: []string{"mkdir", "-p", name}, |
| Output: []string{name}, |
| }) |
| } |
| |
| func (b *build) gc(ofile string, gofiles ...string) { |
| gc := b.arch + "g" |
| args := append([]string{gc, "-o", ofile}, gcImportArgs...) |
| args = append(args, gofiles...) |
| b.add(Cmd{ |
| Args: args, |
| Input: gofiles, |
| Output: []string{ofile}, |
| }) |
| } |
| |
| func (b *build) asm(ofile string, sfile string) { |
| asm := b.arch + "a" |
| b.add(Cmd{ |
| Args: []string{asm, "-o", ofile, sfile}, |
| Input: []string{sfile}, |
| Output: []string{ofile}, |
| }) |
| } |
| |
| func (b *build) ld(targ string, ofiles ...string) { |
| ld := b.arch + "l" |
| args := append([]string{ld, "-o", targ}, ldImportArgs...) |
| args = append(args, ofiles...) |
| b.add(Cmd{ |
| Args: args, |
| Input: ofiles, |
| Output: []string{targ}, |
| }) |
| } |
| |
| func (b *build) gopack(targ string, ofiles ...string) { |
| b.add(Cmd{ |
| Args: append([]string{"gopack", "grc", targ}, ofiles...), |
| Input: ofiles, |
| Output: []string{targ}, |
| }) |
| } |
| |
| func (b *build) cc(ofile string, cfiles ...string) { |
| cc := b.arch + "c" |
| dir := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) |
| inc := filepath.Join(runtime.GOROOT(), "pkg", dir) |
| args := []string{cc, "-FVw", "-I", inc, "-o", ofile} |
| b.add(Cmd{ |
| Args: append(args, cfiles...), |
| Input: cfiles, |
| Output: []string{ofile}, |
| }) |
| } |
| |
| func (b *build) gccCompile(ofile, cfile string) { |
| b.add(Cmd{ |
| Args: b.gccArgs("-o", ofile, "-c", cfile), |
| Input: []string{cfile}, |
| Output: []string{ofile}, |
| }) |
| } |
| |
| func (b *build) gccLink(ofile string, ofiles ...string) { |
| b.add(Cmd{ |
| Args: append(b.gccArgs("-o", ofile), ofiles...), |
| Input: ofiles, |
| Output: []string{ofile}, |
| }) |
| } |
| |
| func (b *build) gccArgs(args ...string) []string { |
| // TODO(adg): HOST_CC |
| a := []string{"gcc", "-I", b.path, "-g", "-fPIC", "-O2"} |
| switch b.arch { |
| case "8": |
| a = append(a, "-m32") |
| case "6": |
| a = append(a, "-m64") |
| } |
| return append(a, args...) |
| } |
| |
| var cgoRe = regexp.MustCompile("[/\\:]") |
| |
| func (b *build) cgo(cgofiles []string) (outGo, outObj []string) { |
| // cgo |
| // TODO(adg): CGOPKGPATH |
| // TODO(adg): CGO_FLAGS |
| gofiles := []string{b.obj + "_cgo_gotypes.go"} |
| cfiles := []string{b.obj + "_cgo_main.c", b.obj + "_cgo_export.c"} |
| for _, fn := range cgofiles { |
| f := b.obj + cgoRe.ReplaceAllString(fn[:len(fn)-2], "_") |
| gofiles = append(gofiles, f+"cgo1.go") |
| cfiles = append(cfiles, f+"cgo2.c") |
| } |
| defunC := b.obj + "_cgo_defun.c" |
| output := append([]string{defunC}, cfiles...) |
| output = append(output, gofiles...) |
| b.add(Cmd{ |
| Args: append([]string{"cgo", "--"}, cgofiles...), |
| Dir: b.path, |
| Env: append(os.Environ(), "GOARCH="+b.goarch), |
| Input: cgofiles, |
| Output: output, |
| }) |
| outGo = append(outGo, gofiles...) |
| exportH := filepath.Join(b.path, "_cgo_export.h") |
| b.script.addIntermediate(defunC, exportH, b.obj+"_cgo_flags") |
| b.script.addIntermediate(cfiles...) |
| |
| // cc _cgo_defun.c |
| defunObj := b.obj + "_cgo_defun." + b.arch |
| b.cc(defunObj, defunC) |
| outObj = append(outObj, defunObj) |
| |
| // gcc |
| linkobj := make([]string, 0, len(cfiles)) |
| for _, cfile := range cfiles { |
| ofile := cfile[:len(cfile)-1] + "o" |
| b.gccCompile(ofile, cfile) |
| linkobj = append(linkobj, ofile) |
| if !strings.HasSuffix(ofile, "_cgo_main.o") { |
| outObj = append(outObj, ofile) |
| } else { |
| b.script.addIntermediate(ofile) |
| } |
| } |
| dynObj := b.obj + "_cgo_.o" |
| b.gccLink(dynObj, linkobj...) |
| b.script.addIntermediate(dynObj) |
| |
| // cgo -dynimport |
| importC := b.obj + "_cgo_import.c" |
| b.add(Cmd{ |
| Args: []string{"cgo", "-dynimport", dynObj}, |
| Stdout: importC, |
| Input: []string{dynObj}, |
| Output: []string{importC}, |
| }) |
| b.script.addIntermediate(importC) |
| |
| // cc _cgo_import.ARCH |
| importObj := b.obj + "_cgo_import." + b.arch |
| b.cc(importObj, importC) |
| outObj = append(outObj, importObj) |
| |
| return |
| } |