blob: d83a6666e82a7f7f1bab0bcd6b798af9913f82f1 [file] [log] [blame]
// 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
}
// add import object files to list of Inputs
for _, pkg := range info.Imports {
t, p, err := FindTree(pkg)
if err != nil && err != ErrNotFound {
// FindTree should always be able to suggest an import
// path and tree. The path must be malformed
// (for example, an absolute or relative path).
return nil, os.NewError("build: invalid import: " + pkg)
}
s.addInput(filepath.Join(t.PkgDir(), p+".a"))
}
// .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
}