// Copyright 2012 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" "debug/elf" "encoding/binary" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "sort" "strconv" "strings" "sync" "time" ) // pathf is fmt.Sprintf for generating paths // (on windows it turns / into \ after the printf). func pathf(format string, args ...interface{}) string { return filepath.Clean(fmt.Sprintf(format, args...)) } // filter returns a slice containing the elements x from list for which f(x) == true. func filter(list []string, f func(string) bool) []string { var out []string for _, x := range list { if f(x) { out = append(out, x) } } return out } // uniq returns a sorted slice containing the unique elements of list. func uniq(list []string) []string { out := make([]string, len(list)) copy(out, list) sort.Strings(out) keep := out[:0] for _, x := range out { if len(keep) == 0 || keep[len(keep)-1] != x { keep = append(keep, x) } } return keep } // splitlines returns a slice with the result of splitting // the input p after each \n. func splitlines(p string) []string { return strings.SplitAfter(p, "\n") } // splitfields replaces the vector v with the result of splitting // the input p into non-empty fields containing no spaces. func splitfields(p string) []string { return strings.Fields(p) } const ( CheckExit = 1 << iota ShowOutput Background ) var outputLock sync.Mutex // run runs the command line cmd in dir. // If mode has ShowOutput set and Background unset, run passes cmd's output to // stdout/stderr directly. Otherwise, run returns cmd's output as a string. // If mode has CheckExit set and the command fails, run calls fatal. // If mode has Background set, this command is being run as a // Background job. Only bgrun should use the Background mode, // not other callers. func run(dir string, mode int, cmd ...string) string { if vflag > 1 { errprintf("run: %s\n", strings.Join(cmd, " ")) } xcmd := exec.Command(cmd[0], cmd[1:]...) xcmd.Dir = dir var data []byte var err error // If we want to show command output and this is not // a background command, assume it's the only thing // running, so we can just let it write directly stdout/stderr // as it runs without fear of mixing the output with some // other command's output. Not buffering lets the output // appear as it is printed instead of once the command exits. // This is most important for the invocation of 'go1.4 build -v bootstrap/...'. if mode&(Background|ShowOutput) == ShowOutput { xcmd.Stdout = os.Stdout xcmd.Stderr = os.Stderr err = xcmd.Run() } else { data, err = xcmd.CombinedOutput() } if err != nil && mode&CheckExit != 0 { outputLock.Lock() if len(data) > 0 { xprintf("%s\n", data) } outputLock.Unlock() if mode&Background != 0 { // Prevent fatal from waiting on our own goroutine's // bghelper to exit: bghelpers.Done() } fatal("FAILED: %v: %v", strings.Join(cmd, " "), err) } if mode&ShowOutput != 0 { outputLock.Lock() os.Stdout.Write(data) outputLock.Unlock() } if vflag > 2 { errprintf("run: %s DONE\n", strings.Join(cmd, " ")) } return string(data) } var maxbg = 4 /* maximum number of jobs to run at once */ var ( bgwork = make(chan func(), 1e5) bgdone = make(chan struct{}, 1e5) bghelpers sync.WaitGroup dieOnce sync.Once // guards close of dying dying = make(chan struct{}) ) func bginit() { bghelpers.Add(maxbg) for i := 0; i < maxbg; i++ { go bghelper() } } func bghelper() { defer bghelpers.Done() for { select { case <-dying: return case w := <-bgwork: // Dying takes precedence over doing more work. select { case <-dying: return default: w() } } } } // bgrun is like run but runs the command in the background. // CheckExit|ShowOutput mode is implied (since output cannot be returned). // bgrun adds 1 to wg immediately, and calls Done when the work completes. func bgrun(wg *sync.WaitGroup, dir string, cmd ...string) { wg.Add(1) bgwork <- func() { defer wg.Done() run(dir, CheckExit|ShowOutput|Background, cmd...) } } // bgwait waits for pending bgruns to finish. // bgwait must be called from only a single goroutine at a time. func bgwait(wg *sync.WaitGroup) { done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: case <-dying: } } // xgetwd returns the current directory. func xgetwd() string { wd, err := os.Getwd() if err != nil { fatal("%s", err) } return wd } // xrealwd returns the 'real' name for the given path. // real is defined as what xgetwd returns in that directory. func xrealwd(path string) string { old := xgetwd() if err := os.Chdir(path); err != nil { fatal("chdir %s: %v", path, err) } real := xgetwd() if err := os.Chdir(old); err != nil { fatal("chdir %s: %v", old, err) } return real } // isdir reports whether p names an existing directory. func isdir(p string) bool { fi, err := os.Stat(p) return err == nil && fi.IsDir() } // isfile reports whether p names an existing file. func isfile(p string) bool { fi, err := os.Stat(p) return err == nil && fi.Mode().IsRegular() } // mtime returns the modification time of the file p. func mtime(p string) time.Time { fi, err := os.Stat(p) if err != nil { return time.Time{} } return fi.ModTime() } // isabs reports whether p is an absolute path. func isabs(p string) bool { return filepath.IsAbs(p) } // readfile returns the content of the named file. func readfile(file string) string { data, err := ioutil.ReadFile(file) if err != nil { fatal("%v", err) } return string(data) } const ( writeExec = 1 << iota writeSkipSame ) // writefile writes b to the named file, creating it if needed. // if exec is non-zero, marks the file as executable. // If the file already exists and has the expected content, // it is not rewritten, to avoid changing the time stamp. func writefile(b, file string, flag int) { new := []byte(b) if flag&writeSkipSame != 0 { old, err := ioutil.ReadFile(file) if err == nil && bytes.Equal(old, new) { return } } mode := os.FileMode(0666) if flag&writeExec != 0 { mode = 0777 } err := ioutil.WriteFile(file, new, mode) if err != nil { fatal("%v", err) } } // xmkdir creates the directory p. func xmkdir(p string) { err := os.Mkdir(p, 0777) if err != nil { fatal("%v", err) } } // xmkdirall creates the directory p and its parents, as needed. func xmkdirall(p string) { err := os.MkdirAll(p, 0777) if err != nil { fatal("%v", err) } } // xremove removes the file p. func xremove(p string) { if vflag > 2 { errprintf("rm %s\n", p) } os.Remove(p) } // xremoveall removes the file or directory tree rooted at p. func xremoveall(p string) { if vflag > 2 { errprintf("rm -r %s\n", p) } os.RemoveAll(p) } // xreaddir replaces dst with a list of the names of the files and subdirectories in dir. // The names are relative to dir; they are not full paths. func xreaddir(dir string) []string { f, err := os.Open(dir) if err != nil { fatal("%v", err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { fatal("reading %s: %v", dir, err) } return names } // xreaddir replaces dst with a list of the names of the files in dir. // The names are relative to dir; they are not full paths. func xreaddirfiles(dir string) []string { f, err := os.Open(dir) if err != nil { fatal("%v", err) } defer f.Close() infos, err := f.Readdir(-1) if err != nil { fatal("reading %s: %v", dir, err) } var names []string for _, fi := range infos { if !fi.IsDir() { names = append(names, fi.Name()) } } return names } // xworkdir creates a new temporary directory to hold object files // and returns the name of that directory. func xworkdir() string { name, err := ioutil.TempDir("", "go-tool-dist-") if err != nil { fatal("%v", err) } return name } // fatal prints an error message to standard error and exits. func fatal(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, "go tool dist: %s\n", fmt.Sprintf(format, args...)) dieOnce.Do(func() { close(dying) }) // Wait for background goroutines to finish, // so that exit handler that removes the work directory // is not fighting with active writes or open files. bghelpers.Wait() xexit(2) } var atexits []func() // xexit exits the process with return code n. func xexit(n int) { for i := len(atexits) - 1; i >= 0; i-- { atexits[i]() } os.Exit(n) } // xatexit schedules the exit-handler f to be run when the program exits. func xatexit(f func()) { atexits = append(atexits, f) } // xprintf prints a message to standard output. func xprintf(format string, args ...interface{}) { fmt.Printf(format, args...) } // errprintf prints a message to standard output. func errprintf(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format, args...) } // main takes care of OS-specific startup and dispatches to xmain. func main() { os.Setenv("TERM", "dumb") // disable escape codes in clang errors slash = string(filepath.Separator) gohostos = runtime.GOOS switch gohostos { case "darwin": // Even on 64-bit platform, darwin uname -m prints i386. // We don't support any of the OS X versions that run on 32-bit-only hardware anymore. gohostarch = "amd64" case "freebsd": // Since FreeBSD 10 gcc is no longer part of the base system. defaultclang = true case "solaris": // Even on 64-bit platform, solaris uname -m prints i86pc. out := run("", CheckExit, "isainfo", "-n") if strings.Contains(out, "amd64") { gohostarch = "amd64" } if strings.Contains(out, "i386") { gohostarch = "386" } case "plan9": gohostarch = os.Getenv("objtype") if gohostarch == "" { fatal("$objtype is unset") } case "windows": exe = ".exe" } sysinit() if gohostarch == "" { // Default Unix system. out := run("", CheckExit, "uname", "-m") switch { case strings.Contains(out, "x86_64"), strings.Contains(out, "amd64"): gohostarch = "amd64" case strings.Contains(out, "86"): gohostarch = "386" case strings.Contains(out, "arm"): gohostarch = "arm" case strings.Contains(out, "aarch64"): gohostarch = "arm64" case strings.Contains(out, "ppc64le"): gohostarch = "ppc64le" case strings.Contains(out, "ppc64"): gohostarch = "ppc64" case strings.Contains(out, "mips64"): file, err := elf.Open(os.Args[0]) if err != nil { fatal("failed to open %s to determine endianness: %v", os.Args[0], err) } if file.FileHeader.ByteOrder == binary.BigEndian { gohostarch = "mips64" } else { gohostarch = "mips64le" } case gohostos == "darwin": if strings.Contains(run("", CheckExit, "uname", "-v"), "RELEASE_ARM_") { gohostarch = "arm" } default: fatal("unknown architecture: %s", out) } } if gohostarch == "arm" || gohostarch == "mips64" || gohostarch == "mips64le" { maxbg = min(maxbg, runtime.NumCPU()) } bginit() // The OS X 10.6 linker does not support external linking mode. // See golang.org/issue/5130. // // OS X 10.6 does not work with clang either, but OS X 10.9 requires it. // It seems to work with OS X 10.8, so we default to clang for 10.8 and later. // See golang.org/issue/5822. // // Roughly, OS X 10.N shows up as uname release (N+4), // so OS X 10.6 is uname version 10 and OS X 10.8 is uname version 12. if gohostos == "darwin" { rel := run("", CheckExit, "uname", "-r") if i := strings.Index(rel, "."); i >= 0 { rel = rel[:i] } osx, _ := strconv.Atoi(rel) if osx <= 6+4 { goextlinkenabled = "0" } if osx >= 8+4 { defaultclang = true } } if len(os.Args) > 1 && os.Args[1] == "-check-goarm" { useVFPv1() // might fail with SIGILL println("VFPv1 OK.") useVFPv3() // might fail with SIGILL println("VFPv3 OK.") os.Exit(0) } xinit() xmain() xexit(0) } // xsamefile reports whether f1 and f2 are the same file (or dir) func xsamefile(f1, f2 string) bool { fi1, err1 := os.Stat(f1) fi2, err2 := os.Stat(f2) if err1 != nil || err2 != nil { return f1 == f2 } return os.SameFile(fi1, fi2) } func xgetgoarm() string { if goos == "nacl" { // NaCl guarantees VFPv3 and is always cross-compiled. return "7" } if goos == "darwin" { // Assume all darwin/arm devices are have VFPv3. This // port is also mostly cross-compiled, so it makes little // sense to auto-detect the setting. return "7" } if gohostarch != "arm" || goos != gohostos { // Conservative default for cross-compilation. return "5" } if goos == "freebsd" || goos == "openbsd" { // FreeBSD has broken VFP support. // OpenBSD currently only supports softfloat. return "5" } // Try to exec ourselves in a mode to detect VFP support. // Seeing how far it gets determines which instructions failed. // The test is OS-agnostic. out := run("", 0, os.Args[0], "-check-goarm") v1ok := strings.Contains(out, "VFPv1 OK.") v3ok := strings.Contains(out, "VFPv3 OK.") if v1ok && v3ok { return "7" } if v1ok { return "6" } return "5" } func min(a, b int) int { if a < b { return a } return b }