| // 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 main |
| |
| import ( |
| "bytes" |
| "container/heap" |
| "errors" |
| "fmt" |
| "go/build" |
| "io" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "path" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| "strings" |
| "sync" |
| ) |
| |
| var cmdBuild = &Command{ |
| UsageLine: "build [-a] [-n] [-o output] [-p n] [-v] [-x] [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 treats them as a list |
| of source files specifying a single package. |
| |
| When the command line specifies a single main package, |
| build writes the resulting executable to output (default a.out). |
| Otherwise build compiles the packages but discards the results, |
| serving only as a check that the packages can be built. |
| |
| 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 names of packages as they are compiled. |
| The -x flag prints the commands. |
| |
| The -o flag specifies the output file name. |
| It is an error to use -o when the command line specifies multiple packages. |
| |
| The -p flag specifies the number of builds that can be run in parallel. |
| The default is the number of CPUs available. |
| |
| For more about import paths, see 'go help importpath'. |
| |
| See also: go install, go get, go clean. |
| `, |
| } |
| |
| func init() { |
| // break init cycle |
| cmdBuild.Run = runBuild |
| cmdInstall.Run = runInstall |
| |
| addBuildFlags(cmdBuild) |
| addBuildFlags(cmdInstall) |
| } |
| |
| // Flags set by multiple commands. |
| var buildA bool // -a flag |
| var buildN bool // -n flag |
| var buildP = runtime.NumCPU() // -p flag |
| var buildV bool // -v flag |
| var buildX bool // -x flag |
| var buildO = cmdBuild.Flag.String("o", "", "output file") |
| |
| var buildContext = build.DefaultContext |
| |
| // addBuildFlags adds the flags common to the build and install commands. |
| func addBuildFlags(cmd *Command) { |
| cmd.Flag.BoolVar(&buildA, "a", false, "") |
| cmd.Flag.BoolVar(&buildN, "n", false, "") |
| cmd.Flag.IntVar(&buildP, "p", buildP, "") |
| cmd.Flag.BoolVar(&buildV, "v", false, "") |
| cmd.Flag.BoolVar(&buildX, "x", false, "") |
| |
| // TODO(rsc): This -t flag is used by buildscript.sh but |
| // not documented. Should be documented but the |
| // usage lines are getting too long. Probably need to say |
| // that these flags are applicable to every command and |
| // document them in one help message instead of on every |
| // command's help message. |
| cmd.Flag.Var((*stringsFlag)(&buildContext.BuildTags), "t", "") |
| } |
| |
| type stringsFlag []string |
| |
| func (v *stringsFlag) Set(s string) error { |
| *v = append(*v, s) |
| return nil |
| } |
| |
| func (v *stringsFlag) String() string { |
| return "<stringsFlag>" |
| } |
| |
| func runBuild(cmd *Command, args []string) { |
| var b builder |
| b.init() |
| |
| var pkgs []*Package |
| if len(args) > 0 && strings.HasSuffix(args[0], ".go") { |
| pkg := goFilesPackage(args, "") |
| pkgs = append(pkgs, pkg) |
| } else { |
| pkgs = packagesForBuild(args) |
| } |
| |
| if len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == "" { |
| _, *buildO = path.Split(pkgs[0].ImportPath) |
| if b.goos == "windows" { |
| *buildO += ".exe" |
| } |
| } |
| |
| if *buildO != "" { |
| if len(pkgs) > 1 { |
| fatalf("go build: cannot use -o with multiple packages") |
| } |
| p := pkgs[0] |
| p.target = "" // must build - not up to date |
| a := b.action(modeInstall, modeBuild, p) |
| a.target = *buildO |
| b.do(a) |
| return |
| } |
| |
| a := &action{} |
| for _, p := range packages(args) { |
| a.deps = append(a.deps, b.action(modeBuild, modeBuild, p)) |
| } |
| b.do(a) |
| } |
| |
| var cmdInstall = &Command{ |
| UsageLine: "install [-a] [-n] [-p n] [-v] [-x] [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 names of packages as they are compiled. |
| The -x flag prints the commands. |
| |
| The -p flag specifies the number of builds that can be run in parallel. |
| The default is the number of CPUs available. |
| |
| For more about import paths, see 'go help importpath'. |
| |
| See also: go build, go get, go clean. |
| `, |
| } |
| |
| func runInstall(cmd *Command, args []string) { |
| pkgs := packagesForBuild(args) |
| |
| var b builder |
| b.init() |
| a := &action{} |
| for _, p := range pkgs { |
| 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) |
| arch string // e.g., "6" |
| goarch string // the $GOARCH |
| goos string // the $GOOS |
| exe string // the executable suffix - "" or ".exe" |
| gcflags []string // additional flags for Go compiler |
| actionCache map[cacheKey]*action // a cache of already-constructed actions |
| mkdirCache map[string]bool // a cache of created directories |
| print func(args ...interface{}) (int, error) |
| |
| output sync.Mutex |
| scriptDir string // current directory in printed script |
| |
| exec sync.Mutex |
| readySema chan bool |
| ready actionQueue |
| } |
| |
| // An action represents a single action in the action graph. |
| type action struct { |
| p *Package // the package this action works on |
| deps []*action // actions that must happen before this one |
| triggers []*action // inverse of deps |
| cgo *action // action for cgo binary if needed |
| args []string // additional args for runProgram |
| testOutput *bytes.Buffer // test output buffer |
| |
| f func(*builder, *action) error // the action itself (nil = no-op) |
| ignoreFail bool // whether to run f even if dependencies fail |
| |
| // Generated files, directories. |
| link bool // target is executable, not just package |
| pkgdir string // the -I or -L argument to use when importing this package |
| objdir string // directory for intermediate objects |
| objpkg string // the intermediate package .a file created during the action |
| target string // goal of the action: the created package or executable |
| |
| // Execution state. |
| pending int // number of deps yet to complete |
| priority int // relative execution priority |
| failed bool // whether the action failed |
| } |
| |
| // 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 |
| ) |
| |
| var ( |
| gobin = build.Path[0].BinDir() |
| goroot = build.Path[0].Path |
| ) |
| |
| func (b *builder) init() { |
| var err error |
| b.print = fmt.Print |
| b.actionCache = make(map[cacheKey]*action) |
| b.mkdirCache = make(map[string]bool) |
| b.goarch = buildContext.GOARCH |
| b.goos = buildContext.GOOS |
| if b.goos == "windows" { |
| b.exe = ".exe" |
| } |
| b.gcflags = strings.Fields(os.Getenv("GCFLAGS")) |
| |
| b.arch, err = build.ArchChar(b.goarch) |
| if err != nil { |
| fatalf("%s", err) |
| } |
| |
| if buildN { |
| b.work = "$WORK" |
| } else { |
| b.work, err = ioutil.TempDir("", "go-build") |
| if err != nil { |
| fatalf("%s", err) |
| } |
| if buildX { |
| 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 { |
| // TODO: Remove this restriction. |
| for _, f := range gofiles { |
| if !strings.HasSuffix(f, ".go") || strings.Contains(f, "/") || strings.Contains(f, string(filepath.Separator)) { |
| fatalf("named files must be in current directory and .go files") |
| } |
| } |
| |
| // 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 := buildContext |
| ctxt.ReadDir = func(string) ([]os.FileInfo, error) { return dir, nil } |
| pwd, _ := os.Getwd() |
| var stk importStack |
| pkg := scanPackage(&ctxt, &build.Tree{Path: "."}, "<command line>", "<command line>", pwd+"/.", &stk) |
| if pkg.Error != nil { |
| fatalf("%s", pkg.Error) |
| } |
| printed := map[error]bool{} |
| for _, err := range pkg.DepsErrors { |
| // Since these are errors in dependencies, |
| // the same error might show up multiple times, |
| // once in each package that depends on it. |
| // Only print each once. |
| if !printed[err] { |
| printed[err] = true |
| errorf("%s", err) |
| } |
| } |
| if target != "" { |
| pkg.target = target |
| } else if pkg.Name == "main" { |
| pkg.target = gofiles[0][:len(gofiles[0])-len(".go")] |
| } else { |
| pkg.target = pkg.Name + ".a" |
| } |
| pkg.ImportPath = "_/" + pkg.target |
| exitIfErrors() |
| 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, pkgdir: p.t.PkgDir()} |
| if p.pkgdir != "" { // overrides p.t |
| a.pkgdir = p.pkgdir |
| } |
| |
| b.actionCache[key] = a |
| |
| for _, p1 := range p.imports { |
| a.deps = append(a.deps, b.action(depMode, depMode, p1)) |
| } |
| |
| if len(p.CgoFiles) > 0 { |
| var stk importStack |
| p1 := loadPackage("cmd/cgo", &stk) |
| if p1.Error != nil { |
| fatalf("load cmd/cgo: %v", p1.Error) |
| } |
| a.cgo = b.action(depMode, depMode, p1) |
| a.deps = append(a.deps, a.cgo) |
| } |
| |
| if p.Standard { |
| switch p.ImportPath { |
| case "builtin", "unsafe": |
| // Fake packages - nothing to build. |
| return a |
| } |
| // gccgo standard library is "fake" too. |
| if _, ok := buildToolchain.(gccgoToolchain); ok { |
| // the target name is needed for cgo. |
| a.target = p.target |
| return a |
| } |
| } |
| |
| if !p.Stale && !buildA && p.target != "" { |
| // p.Stale==false implies that p.target is up-to-date. |
| // Record target name for use by actions depending on this one. |
| a.target = p.target |
| return a |
| } |
| |
| a.objdir = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator) |
| a.objpkg = buildToolchain.pkgpath(b.work, a.p) |
| a.link = p.Name == "main" |
| |
| switch mode { |
| case modeInstall: |
| a.f = (*builder).install |
| a.deps = []*action{b.action(modeBuild, depMode, p)} |
| a.target = a.p.target |
| case modeBuild: |
| a.f = (*builder).build |
| a.target = a.objpkg |
| if a.link { |
| // An executable file. |
| // (This is the name of a temporary file.) |
| a.target = a.objdir + "a.out" + b.exe |
| } |
| } |
| |
| return a |
| } |
| |
| // actionList returns the list of actions in the dag rooted at root |
| // as visited in a depth-first post-order traversal. |
| func actionList(root *action) []*action { |
| seen := map[*action]bool{} |
| all := []*action{} |
| var walk func(*action) |
| walk = func(a *action) { |
| if seen[a] { |
| return |
| } |
| seen[a] = true |
| for _, a1 := range a.deps { |
| walk(a1) |
| } |
| all = append(all, a) |
| } |
| walk(root) |
| return all |
| } |
| |
| // do runs the action graph rooted at root. |
| func (b *builder) do(root *action) { |
| // Build list of all actions, assigning depth-first post-order priority. |
| // The original implementation here was a true queue |
| // (using a channel) but it had the effect of getting |
| // distracted by low-level leaf actions to the detriment |
| // of completing higher-level actions. The order of |
| // work does not matter much to overall execution time, |
| // but when running "go test std" it is nice to see each test |
| // results as soon as possible. The priorities assigned |
| // ensure that, all else being equal, the execution prefers |
| // to do what it would have done first in a simple depth-first |
| // dependency order traversal. |
| all := actionList(root) |
| for i, a := range all { |
| a.priority = i |
| } |
| |
| b.readySema = make(chan bool, len(all)) |
| done := make(chan bool) |
| |
| // Initialize per-action execution state. |
| for _, a := range all { |
| for _, a1 := range a.deps { |
| a1.triggers = append(a1.triggers, a) |
| } |
| a.pending = len(a.deps) |
| if a.pending == 0 { |
| b.ready.push(a) |
| b.readySema <- true |
| } |
| } |
| |
| // Handle runs a single action and takes care of triggering |
| // any actions that are runnable as a result. |
| handle := func(a *action) { |
| var err error |
| if a.f != nil && (!a.failed || a.ignoreFail) { |
| err = a.f(b, a) |
| } |
| |
| // The actions run in parallel but all the updates to the |
| // shared work state are serialized through b.exec. |
| b.exec.Lock() |
| defer b.exec.Unlock() |
| |
| if err != nil { |
| if err == errPrintedOutput { |
| setExitStatus(2) |
| } else { |
| errorf("%s", err) |
| } |
| a.failed = true |
| } |
| |
| for _, a0 := range a.triggers { |
| if a.failed { |
| a0.failed = true |
| } |
| if a0.pending--; a0.pending == 0 { |
| b.ready.push(a0) |
| b.readySema <- true |
| } |
| } |
| |
| if a == root { |
| close(b.readySema) |
| done <- true |
| } |
| } |
| |
| // Kick off goroutines according to parallelism. |
| // If we are using the -n flag (just printing commands) |
| // drop the parallelism to 1, both to make the output |
| // deterministic and because there is no real work anyway. |
| par := buildP |
| if buildN { |
| par = 1 |
| } |
| for i := 0; i < par; i++ { |
| go func() { |
| for _ = range b.readySema { |
| // Receiving a value from b.sema entitles |
| // us to take from the ready queue. |
| b.exec.Lock() |
| a := b.ready.pop() |
| b.exec.Unlock() |
| handle(a) |
| } |
| }() |
| } |
| |
| <-done |
| } |
| |
| // build is the action for building a single package or command. |
| func (b *builder) build(a *action) error { |
| if buildN { |
| // 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) |
| } |
| |
| if buildV { |
| fmt.Fprintf(os.Stderr, "%s\n", a.p.ImportPath) |
| } |
| |
| // Make build directory. |
| obj := a.objdir |
| if err := b.mkdir(obj); err != nil { |
| return err |
| } |
| |
| var gofiles, cfiles, sfiles, objects, cgoObjects []string |
| gofiles = append(gofiles, a.p.GoFiles...) |
| cfiles = append(cfiles, a.p.CFiles...) |
| sfiles = append(sfiles, a.p.SFiles...) |
| |
| // Run cgo. |
| if len(a.p.CgoFiles) > 0 { |
| // In a package using cgo, cgo compiles the C and assembly files with gcc. |
| // There is one exception: runtime/cgo's job is to bridge the |
| // cgo and non-cgo worlds, so it necessarily has files in both. |
| // In that case gcc only gets the gcc_* files. |
| var gccfiles []string |
| if a.p.Standard && a.p.ImportPath == "runtime/cgo" { |
| filter := func(files, nongcc, gcc []string) ([]string, []string) { |
| for _, f := range files { |
| if strings.HasPrefix(f, "gcc_") { |
| gcc = append(gcc, f) |
| } else { |
| nongcc = append(nongcc, f) |
| } |
| } |
| return nongcc, gcc |
| } |
| cfiles, gccfiles = filter(cfiles, cfiles[:0], gccfiles) |
| sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles) |
| } else { |
| gccfiles = append(cfiles, sfiles...) |
| cfiles = nil |
| sfiles = nil |
| } |
| |
| outGo, outObj, err := b.cgo(a.p, a.cgo.target, obj, gccfiles) |
| if err != nil { |
| return err |
| } |
| cgoObjects = append(cgoObjects, outObj...) |
| gofiles = append(gofiles, outGo...) |
| } |
| |
| // Prepare Go import path list. |
| inc := b.includeArgs("-I", a.deps) |
| |
| // Compile Go. |
| if len(gofiles) > 0 { |
| if out, err := buildToolchain.gc(b, a.p, obj, inc, gofiles); err != nil { |
| return err |
| } else { |
| objects = append(objects, out) |
| } |
| } |
| |
| // Copy .h files named for goos or goarch or goos_goarch |
| // to names using GOOS and GOARCH. |
| // For example, defs_linux_amd64.h becomes defs_GOOS_GOARCH.h. |
| _goos_goarch := "_" + b.goos + "_" + b.goarch + ".h" |
| _goos := "_" + b.goos + ".h" |
| _goarch := "_" + b.goarch + ".h" |
| for _, file := range a.p.HFiles { |
| switch { |
| case strings.HasSuffix(file, _goos_goarch): |
| targ := file[:len(file)-len(_goos_goarch)] + "_GOOS_GOARCH.h" |
| if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { |
| return err |
| } |
| case strings.HasSuffix(file, _goarch): |
| targ := file[:len(file)-len(_goarch)] + "_GOARCH.h" |
| if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { |
| return err |
| } |
| case strings.HasSuffix(file, _goos): |
| targ := file[:len(file)-len(_goos)] + "_GOOS.h" |
| if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { |
| return err |
| } |
| } |
| } |
| |
| for _, file := range cfiles { |
| out := file[:len(file)-len(".c")] + "." + b.arch |
| if err := buildToolchain.cc(b, a.p, obj, obj+out, file); err != nil { |
| return err |
| } |
| objects = append(objects, out) |
| } |
| |
| // Assemble .s files. |
| for _, file := range sfiles { |
| out := file[:len(file)-len(".s")] + "." + b.arch |
| if err := buildToolchain.asm(b, a.p, obj, obj+out, file); err != nil { |
| return err |
| } |
| objects = append(objects, out) |
| } |
| |
| // NOTE(rsc): On Windows, it is critically important that the |
| // gcc-compiled objects (cgoObjects) be listed after the ordinary |
| // objects in the archive. I do not know why this is. |
| // http://golang.org/issue/2601 |
| objects = append(objects, cgoObjects...) |
| |
| // Pack into archive in obj directory |
| if err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil { |
| return err |
| } |
| |
| // Link if needed. |
| if a.link { |
| // The compiler only cares about direct imports, but the |
| // linker needs the whole dependency tree. |
| all := actionList(a) |
| all = all[:len(all)-1] // drop a |
| if err := buildToolchain.ld(b, a.p, a.target, all, a.objpkg, objects); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // install is the action for installing a single package or executable. |
| func (b *builder) install(a *action) error { |
| a1 := a.deps[0] |
| perm := os.FileMode(0666) |
| if a1.link { |
| perm = 0777 |
| } |
| |
| // make target directory |
| dir, _ := filepath.Split(a.target) |
| if dir != "" { |
| if err := b.mkdir(dir); err != nil { |
| return err |
| } |
| } |
| |
| return b.copyFile(a.target, a1.target, perm) |
| } |
| |
| // includeArgs returns the -I or -L directory list for access |
| // to the results of the list of actions. |
| func (b *builder) includeArgs(flag string, all []*action) []string { |
| inc := []string{} |
| incMap := map[string]bool{ |
| b.work: true, // handled later |
| build.Path[0].PkgDir(): true, // goroot |
| "": true, // ignore empty strings |
| } |
| |
| // Look in the temporary space for results of test-specific actions. |
| // This is the $WORK/my/package/_test directory for the |
| // package being built, so there are few of these. |
| for _, a1 := range all { |
| if dir := a1.pkgdir; dir != a1.p.t.PkgDir() && !incMap[dir] { |
| incMap[dir] = true |
| inc = append(inc, flag, dir) |
| } |
| } |
| |
| // Also look in $WORK for any non-test packages that have |
| // been built but not installed. |
| inc = append(inc, flag, b.work) |
| |
| // Finally, look in the installed package directories for each action. |
| for _, a1 := range all { |
| if dir := a1.pkgdir; dir == a1.p.t.PkgDir() && !incMap[dir] { |
| if _, ok := buildToolchain.(gccgoToolchain); ok { |
| dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir)) |
| } |
| incMap[dir] = true |
| inc = append(inc, flag, dir) |
| } |
| } |
| |
| return inc |
| } |
| |
| // copyFile is like 'cp src dst'. |
| func (b *builder) copyFile(dst, src string, perm os.FileMode) error { |
| if buildN || buildX { |
| b.showcmd("", "cp %s %s", src, dst) |
| if buildN { |
| return nil |
| } |
| } |
| |
| sf, err := os.Open(src) |
| if err != nil { |
| return err |
| } |
| defer sf.Close() |
| |
| // On Windows, remove lingering ~ file from last attempt. |
| if toolIsWindows { |
| if _, err := os.Stat(dst + "~"); err == nil { |
| os.Remove(dst + "~") |
| } |
| } |
| |
| os.Remove(dst) |
| df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) |
| if err != nil && toolIsWindows { |
| // Windows does not allow deletion of a binary file |
| // while it is executing. Try to move it out of the way. |
| // If the remove fails, which is likely, we'll try again the |
| // next time we do an install of this binary. |
| if err := os.Rename(dst, dst+"~"); err == nil { |
| 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 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 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 |
| } |
| } |
| if b.work != "" { |
| cmd = strings.Replace(cmd, b.work, "$WORK", -1) |
| } |
| cmd = strings.Replace(cmd, gobin, "$GOBIN", -1) |
| cmd = strings.Replace(cmd, goroot, "$GOROOT", -1) |
| return cmd |
| } |
| |
| // showcmd prints the given command to standard output |
| // for the implementation of -n or -x. |
| func (b *builder) showcmd(dir string, format string, args ...interface{}) { |
| b.output.Lock() |
| defer b.output.Unlock() |
| b.print(b.fmtcmd(dir, format, args...) + "\n") |
| } |
| |
| // 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 |
| // when the relative path is shorter. This is usually more pleasant. |
| // 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 && len(reldir) < len(dir) { |
| 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() |
| b.print(prefix, suffix) |
| } |
| |
| // relPaths returns a copy of paths with absolute paths |
| // made relative to the current directory if they would be shorter. |
| func relPaths(paths []string) []string { |
| var out []string |
| pwd, _ := os.Getwd() |
| for _, p := range paths { |
| rel, err := filepath.Rel(pwd, p) |
| if err == nil && len(rel) < len(p) { |
| p = rel |
| } |
| out = append(out, p) |
| } |
| return out |
| } |
| |
| // 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, desc string, cmdargs ...interface{}) error { |
| out, err := b.runOut(dir, desc, cmdargs...) |
| if len(out) > 0 { |
| if out[len(out)-1] != '\n' { |
| out = append(out, '\n') |
| } |
| if desc == "" { |
| desc = b.fmtcmd(dir, "%s", strings.Join(stringList(cmdargs...), " ")) |
| } |
| b.showOutput(dir, desc, string(out)) |
| if err != nil { |
| err = errPrintedOutput |
| } |
| } |
| return err |
| } |
| |
| // runOut runs the command given by cmdline in the directory dir. |
| // It returns the command output and any errors that occurred. |
| func (b *builder) runOut(dir string, desc string, cmdargs ...interface{}) ([]byte, error) { |
| cmdline := stringList(cmdargs...) |
| if buildN || buildX { |
| b.showcmd(dir, "%s", strings.Join(cmdline, " ")) |
| if buildN { |
| return nil, 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() |
| return buf.Bytes(), err |
| } |
| |
| // mkdir makes the named directory. |
| func (b *builder) mkdir(dir string) error { |
| b.exec.Lock() |
| defer b.exec.Unlock() |
| // We can be a little aggressive about being |
| // sure directories exist. Skip repeated calls. |
| if b.mkdirCache[dir] { |
| return nil |
| } |
| b.mkdirCache[dir] = true |
| |
| if buildN || buildX { |
| b.showcmd("", "mkdir -p %s", dir) |
| if buildN { |
| return nil |
| } |
| } |
| |
| if err := os.MkdirAll(dir, 0777); err != nil { |
| return err |
| } |
| 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) |
| } |
| |
| type toolchain interface { |
| // gc runs the compiler in a specific directory on a set of files |
| // and returns the name of the generated output file. |
| gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) |
| // cc runs the toolchain's C compiler in a directory on a C file |
| // to produce an output file. |
| cc(b *builder, p *Package, objdir, ofile, cfile string) error |
| // asm runs the assembler in a specific directory on a specific file |
| // to generate the named output file. |
| asm(b *builder, p *Package, obj, ofile, sfile string) error |
| // pkgpath creates the appropriate destination path for a package file. |
| pkgpath(basedir string, p *Package) string |
| // pack runs the archive packer in a specific directory to create |
| // an archive from a set of object files. |
| // typically it is run in the object directory. |
| pack(b *builder, p *Package, objDir, afile string, ofiles []string) error |
| // ld runs the linker to create a package starting at mainpkg. |
| ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error |
| } |
| |
| type goToolchain struct{} |
| type gccgoToolchain struct{} |
| |
| var buildToolchain toolchain |
| |
| func init() { |
| if os.Getenv("GC") == "gccgo" { |
| buildToolchain = gccgoToolchain{} |
| } else { |
| buildToolchain = goToolchain{} |
| } |
| } |
| |
| // The Go toolchain. |
| |
| func (goToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) { |
| out := "_go_." + b.arch |
| ofile = obj + out |
| gcargs := []string{"-p", p.ImportPath} |
| if p.Standard && p.ImportPath == "runtime" { |
| // runtime compiles with a special 6g flag to emit |
| // additional reflect type data. |
| gcargs = append(gcargs, "-+") |
| } |
| |
| args := stringList(tool(b.arch+"g"), "-o", ofile, b.gcflags, gcargs, importArgs) |
| for _, f := range gofiles { |
| args = append(args, mkAbs(p.Dir, f)) |
| } |
| return ofile, b.run(p.Dir, p.ImportPath, args) |
| } |
| |
| func (goToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error { |
| sfile = mkAbs(p.Dir, sfile) |
| return b.run(p.Dir, p.ImportPath, tool(b.arch+"a"), "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile) |
| } |
| |
| func (goToolchain) pkgpath(basedir string, p *Package) string { |
| return filepath.Join(basedir, filepath.FromSlash(p.ImportPath+".a")) |
| } |
| |
| func (goToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error { |
| var absOfiles []string |
| for _, f := range ofiles { |
| absOfiles = append(absOfiles, mkAbs(objDir, f)) |
| } |
| return b.run(p.Dir, p.ImportPath, tool("pack"), "grc", mkAbs(objDir, afile), absOfiles) |
| } |
| |
| func (goToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error { |
| importArgs := b.includeArgs("-L", allactions) |
| return b.run(p.Dir, p.ImportPath, tool(b.arch+"l"), "-o", out, importArgs, mainpkg) |
| } |
| |
| func (goToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error { |
| inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch)) |
| cfile = mkAbs(p.Dir, cfile) |
| return b.run(p.Dir, p.ImportPath, tool(b.arch+"c"), "-FVw", |
| "-I", objdir, "-I", inc, "-o", ofile, |
| "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile) |
| } |
| |
| // The Gccgo toolchain. |
| |
| func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, err error) { |
| out := p.Name + ".o" |
| ofile = obj + out |
| gcargs := []string{"-g"} |
| if p.Name != "main" { |
| if p.fake { |
| gcargs = append(gcargs, "-fgo-prefix=fake_"+p.ImportPath) |
| } else { |
| gcargs = append(gcargs, "-fgo-prefix=go_"+p.ImportPath) |
| } |
| } |
| args := stringList("gccgo", importArgs, "-c", b.gcflags, gcargs, "-o", ofile) |
| for _, f := range gofiles { |
| args = append(args, mkAbs(p.Dir, f)) |
| } |
| return ofile, b.run(p.Dir, p.ImportPath, args) |
| } |
| |
| func (gccgoToolchain) asm(b *builder, p *Package, obj, ofile, sfile string) error { |
| sfile = mkAbs(p.Dir, sfile) |
| return b.run(p.Dir, p.ImportPath, "gccgo", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile) |
| } |
| |
| func (gccgoToolchain) pkgpath(basedir string, p *Package) string { |
| afile := filepath.Join(basedir, filepath.FromSlash(p.ImportPath+".a")) |
| // prepend "lib" to the basename |
| return filepath.Join(filepath.Dir(afile), "lib"+filepath.Base(afile)) |
| } |
| |
| func (gccgoToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []string) error { |
| var absOfiles []string |
| for _, f := range ofiles { |
| absOfiles = append(absOfiles, mkAbs(objDir, f)) |
| } |
| return b.run(p.Dir, p.ImportPath, "ar", "cru", mkAbs(objDir, afile), absOfiles) |
| } |
| |
| func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []*action, mainpkg string, ofiles []string) error { |
| // gccgo needs explicit linking with all package dependencies, |
| // and all LDFLAGS from cgo dependencies |
| afiles := []string{} |
| ldflags := []string{} |
| seen := map[*Package]bool{} |
| for _, a := range allactions { |
| if a.p != nil && !seen[a.p] { |
| seen[a.p] = true |
| if !a.p.Standard { |
| afiles = append(afiles, a.target) |
| } |
| ldflags = append(ldflags, a.p.CgoLDFLAGS...) |
| } |
| } |
| return b.run(p.Dir, p.ImportPath, "gccgo", "-o", out, ofiles, "-Wl,-(", afiles, ldflags, "-Wl,-)") |
| } |
| |
| func (gccgoToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error { |
| inc := filepath.Join(goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch)) |
| cfile = mkAbs(p.Dir, cfile) |
| return b.run(p.Dir, p.ImportPath, "gcc", "-Wall", "-g", |
| "-I", objdir, "-I", inc, "-o", ofile, |
| "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, "-c", cfile) |
| } |
| |
| // gcc runs the gcc C compiler to create an object from a single C file. |
| 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(p *Package, out string, flags []string, obj []string) error { |
| return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-o", out, obj, flags) |
| } |
| |
| // gccCmd returns a gcc command line prefix |
| func (b *builder) gccCmd(objdir string) []string { |
| // TODO: HOST_CC? |
| a := []string{"gcc", "-I", objdir, "-g", "-O2"} |
| |
| // Definitely want -fPIC but on Windows gcc complains |
| // "-fPIC ignored for target (all code is position independent)" |
| if b.goos != "windows" { |
| a = append(a, "-fPIC") |
| } |
| switch b.arch { |
| case "8": |
| a = append(a, "-m32") |
| case "6": |
| a = append(a, "-m64") |
| } |
| // gcc-4.5 and beyond require explicit "-pthread" flag |
| // for multithreading with pthread library. |
| if buildContext.CgoEnabled { |
| switch b.goos { |
| case "windows": |
| a = append(a, "-mthreads") |
| default: |
| a = append(a, "-pthread") |
| } |
| } |
| return a |
| } |
| |
| func envList(key string) []string { |
| return strings.Fields(os.Getenv(key)) |
| } |
| |
| var cgoRe = regexp.MustCompile(`[/\\:]`) |
| |
| func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outObj []string, err error) { |
| if b.goos != toolGOOS { |
| return nil, nil, errors.New("cannot use cgo when compiling for a different operating system") |
| } |
| |
| outObj = append(outObj, "") // for importObj, at end of function |
| |
| cgoCFLAGS := stringList(envList("CGO_CFLAGS"), p.info.CgoCFLAGS) |
| cgoLDFLAGS := stringList(envList("CGO_LDFLAGS"), p.info.CgoLDFLAGS) |
| |
| if pkgs := p.info.CgoPkgConfig; len(pkgs) > 0 { |
| out, err := b.runOut(p.Dir, p.ImportPath, "pkg-config", "--cflags", pkgs) |
| if err != nil { |
| b.showOutput(p.Dir, "pkg-config --cflags "+strings.Join(pkgs, " "), string(out)) |
| b.print(err.Error() + "\n") |
| return nil, nil, errPrintedOutput |
| } |
| if len(out) > 0 { |
| cgoCFLAGS = append(cgoCFLAGS, strings.Fields(string(out))...) |
| } |
| out, err = b.runOut(p.Dir, p.ImportPath, "pkg-config", "--libs", pkgs) |
| if err != nil { |
| b.showOutput(p.Dir, "pkg-config --libs "+strings.Join(pkgs, " "), string(out)) |
| b.print(err.Error() + "\n") |
| return nil, nil, errPrintedOutput |
| } |
| if len(out) > 0 { |
| cgoLDFLAGS = append(cgoLDFLAGS, strings.Fields(string(out))...) |
| } |
| } |
| |
| // Allows including _cgo_export.h from .[ch] files in the package. |
| cgoCFLAGS = append(cgoCFLAGS, "-I", obj) |
| |
| // cgo |
| // TODO: CGOPKGPATH, CGO_FLAGS? |
| gofiles := []string{obj + "_cgo_gotypes.go"} |
| cfiles := []string{"_cgo_main.c", "_cgo_export.c"} |
| for _, fn := range p.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" |
| |
| cgoflags := []string{} |
| // TODO: make cgo not depend on $GOARCH? |
| |
| if p.Standard && p.ImportPath == "runtime/cgo" { |
| cgoflags = append(cgoflags, "-import_runtime_cgo=false") |
| } |
| if _, ok := buildToolchain.(gccgoToolchain); ok { |
| cgoflags = append(cgoflags, "-gccgo") |
| } |
| if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, cgoflags, "--", cgoCFLAGS, p.CgoFiles); err != nil { |
| return nil, nil, err |
| } |
| outGo = append(outGo, gofiles...) |
| |
| // cc _cgo_defun.c |
| defunObj := obj + "_cgo_defun." + b.arch |
| if err := buildToolchain.cc(b, p, obj, 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(p, ofile, 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 _, file := range gccfiles { |
| ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o" |
| if err := b.gcc(p, ofile, cgoCFLAGS, file); err != nil { |
| return nil, nil, err |
| } |
| linkobj = append(linkobj, ofile) |
| outObj = append(outObj, ofile) |
| } |
| dynobj := obj + "_cgo_.o" |
| if err := b.gccld(p, dynobj, cgoLDFLAGS, linkobj); err != nil { |
| return nil, nil, err |
| } |
| |
| // cgo -dynimport |
| importC := obj + "_cgo_import.c" |
| if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil { |
| return nil, nil, err |
| } |
| |
| // cc _cgo_import.ARCH |
| importObj := obj + "_cgo_import." + b.arch |
| if err := buildToolchain.cc(b, p, obj, importObj, importC); err != nil { |
| return nil, nil, err |
| } |
| |
| // NOTE(rsc): The importObj is a 5c/6c/8c object and on Windows |
| // must be processed before the gcc-generated objects. |
| // Put it first. We left room above. http://golang.org/issue/2601 |
| outObj[0] = importObj |
| |
| return outGo, outObj, nil |
| } |
| |
| // An actionQueue is a priority queue of actions. |
| type actionQueue []*action |
| |
| // Implement heap.Interface |
| func (q *actionQueue) Len() int { return len(*q) } |
| func (q *actionQueue) Swap(i, j int) { (*q)[i], (*q)[j] = (*q)[j], (*q)[i] } |
| func (q *actionQueue) Less(i, j int) bool { return (*q)[i].priority < (*q)[j].priority } |
| func (q *actionQueue) Push(x interface{}) { *q = append(*q, x.(*action)) } |
| func (q *actionQueue) Pop() interface{} { |
| n := len(*q) - 1 |
| x := (*q)[n] |
| *q = (*q)[:n] |
| return x |
| } |
| |
| func (q *actionQueue) push(a *action) { |
| heap.Push(q, a) |
| } |
| |
| func (q *actionQueue) pop() *action { |
| return heap.Pop(q).(*action) |
| } |