// 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 }