github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/cmd/dist/util.go (about)

     1  // Copyright 2012 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"sync/atomic"
    19  	"time"
    20  )
    21  
    22  // pathf is fmt.Sprintf for generating paths
    23  // (on windows it turns / into \ after the printf).
    24  func pathf(format string, args ...interface{}) string {
    25  	return filepath.Clean(fmt.Sprintf(format, args...))
    26  }
    27  
    28  // filter returns a slice containing the elements x from list for which f(x) == true.
    29  func filter(list []string, f func(string) bool) []string {
    30  	var out []string
    31  	for _, x := range list {
    32  		if f(x) {
    33  			out = append(out, x)
    34  		}
    35  	}
    36  	return out
    37  }
    38  
    39  // uniq returns a sorted slice containing the unique elements of list.
    40  func uniq(list []string) []string {
    41  	out := make([]string, len(list))
    42  	copy(out, list)
    43  	sort.Strings(out)
    44  	keep := out[:0]
    45  	for _, x := range out {
    46  		if len(keep) == 0 || keep[len(keep)-1] != x {
    47  			keep = append(keep, x)
    48  		}
    49  	}
    50  	return keep
    51  }
    52  
    53  // splitlines returns a slice with the result of splitting
    54  // the input p after each \n.
    55  func splitlines(p string) []string {
    56  	return strings.SplitAfter(p, "\n")
    57  }
    58  
    59  // splitfields replaces the vector v with the result of splitting
    60  // the input p into non-empty fields containing no spaces.
    61  func splitfields(p string) []string {
    62  	return strings.Fields(p)
    63  }
    64  
    65  const (
    66  	CheckExit = 1 << iota
    67  	ShowOutput
    68  	Background
    69  )
    70  
    71  var outputLock sync.Mutex
    72  
    73  // run runs the command line cmd in dir.
    74  // If mode has ShowOutput set, run collects cmd's output and returns it as a string;
    75  // otherwise, run prints cmd's output to standard output after the command finishes.
    76  // If mode has CheckExit set and the command fails, run calls fatal.
    77  // If mode has Background set, this command is being run as a
    78  // Background job. Only bgrun should use the Background mode,
    79  // not other callers.
    80  func run(dir string, mode int, cmd ...string) string {
    81  	if vflag > 1 {
    82  		errprintf("run: %s\n", strings.Join(cmd, " "))
    83  	}
    84  
    85  	xcmd := exec.Command(cmd[0], cmd[1:]...)
    86  	xcmd.Dir = dir
    87  	var data []byte
    88  	var err error
    89  
    90  	// If we want to show command output and this is not
    91  	// a background command, assume it's the only thing
    92  	// running, so we can just let it write directly stdout/stderr
    93  	// as it runs without fear of mixing the output with some
    94  	// other command's output. Not buffering lets the output
    95  	// appear as it is printed instead of once the command exits.
    96  	// This is most important for the invocation of 'go1.4 build -v bootstrap/...'.
    97  	if mode&(Background|ShowOutput) == ShowOutput {
    98  		xcmd.Stdout = os.Stdout
    99  		xcmd.Stderr = os.Stderr
   100  		err = xcmd.Run()
   101  	} else {
   102  		data, err = xcmd.CombinedOutput()
   103  	}
   104  	if err != nil && mode&CheckExit != 0 {
   105  		outputLock.Lock()
   106  		if len(data) > 0 {
   107  			xprintf("%s\n", data)
   108  		}
   109  		outputLock.Unlock()
   110  		if mode&Background != 0 {
   111  			bgdied.Done()
   112  		}
   113  		fatal("FAILED: %v: %v", strings.Join(cmd, " "), err)
   114  	}
   115  	if mode&ShowOutput != 0 {
   116  		outputLock.Lock()
   117  		os.Stdout.Write(data)
   118  		outputLock.Unlock()
   119  	}
   120  	if vflag > 2 {
   121  		errprintf("run: %s DONE\n", strings.Join(cmd, " "))
   122  	}
   123  	return string(data)
   124  }
   125  
   126  var maxbg = 4 /* maximum number of jobs to run at once */
   127  
   128  var (
   129  	bgwork = make(chan func(), 1e5)
   130  	bgdone = make(chan struct{}, 1e5)
   131  
   132  	bgdied sync.WaitGroup
   133  	nwork  int32
   134  	ndone  int32
   135  
   136  	dying  = make(chan bool)
   137  	nfatal int32
   138  )
   139  
   140  func bginit() {
   141  	bgdied.Add(maxbg)
   142  	for i := 0; i < maxbg; i++ {
   143  		go bghelper()
   144  	}
   145  }
   146  
   147  func bghelper() {
   148  	for {
   149  		w := <-bgwork
   150  		w()
   151  
   152  		// Stop if we're dying.
   153  		if atomic.LoadInt32(&nfatal) > 0 {
   154  			bgdied.Done()
   155  			return
   156  		}
   157  	}
   158  }
   159  
   160  // bgrun is like run but runs the command in the background.
   161  // CheckExit|ShowOutput mode is implied (since output cannot be returned).
   162  func bgrun(dir string, cmd ...string) {
   163  	bgwork <- func() {
   164  		run(dir, CheckExit|ShowOutput|Background, cmd...)
   165  	}
   166  }
   167  
   168  // bgwait waits for pending bgruns to finish.
   169  // bgwait must be called from only a single goroutine at a time.
   170  func bgwait() {
   171  	var wg sync.WaitGroup
   172  	wg.Add(maxbg)
   173  	done := make(chan bool)
   174  	for i := 0; i < maxbg; i++ {
   175  		bgwork <- func() {
   176  			wg.Done()
   177  
   178  			// Hold up bg goroutine until either the wait finishes
   179  			// or the program starts dying due to a call to fatal.
   180  			select {
   181  			case <-dying:
   182  			case <-done:
   183  			}
   184  		}
   185  	}
   186  	wg.Wait()
   187  	close(done)
   188  }
   189  
   190  // xgetwd returns the current directory.
   191  func xgetwd() string {
   192  	wd, err := os.Getwd()
   193  	if err != nil {
   194  		fatal("%s", err)
   195  	}
   196  	return wd
   197  }
   198  
   199  // xrealwd returns the 'real' name for the given path.
   200  // real is defined as what xgetwd returns in that directory.
   201  func xrealwd(path string) string {
   202  	old := xgetwd()
   203  	if err := os.Chdir(path); err != nil {
   204  		fatal("chdir %s: %v", path, err)
   205  	}
   206  	real := xgetwd()
   207  	if err := os.Chdir(old); err != nil {
   208  		fatal("chdir %s: %v", old, err)
   209  	}
   210  	return real
   211  }
   212  
   213  // isdir reports whether p names an existing directory.
   214  func isdir(p string) bool {
   215  	fi, err := os.Stat(p)
   216  	return err == nil && fi.IsDir()
   217  }
   218  
   219  // isfile reports whether p names an existing file.
   220  func isfile(p string) bool {
   221  	fi, err := os.Stat(p)
   222  	return err == nil && fi.Mode().IsRegular()
   223  }
   224  
   225  // mtime returns the modification time of the file p.
   226  func mtime(p string) time.Time {
   227  	fi, err := os.Stat(p)
   228  	if err != nil {
   229  		return time.Time{}
   230  	}
   231  	return fi.ModTime()
   232  }
   233  
   234  // isabs reports whether p is an absolute path.
   235  func isabs(p string) bool {
   236  	return filepath.IsAbs(p)
   237  }
   238  
   239  // readfile returns the content of the named file.
   240  func readfile(file string) string {
   241  	data, err := ioutil.ReadFile(file)
   242  	if err != nil {
   243  		fatal("%v", err)
   244  	}
   245  	return string(data)
   246  }
   247  
   248  // writefile writes b to the named file, creating it if needed.  if
   249  // exec is non-zero, marks the file as executable.
   250  func writefile(b, file string, exec int) {
   251  	mode := os.FileMode(0666)
   252  	if exec != 0 {
   253  		mode = 0777
   254  	}
   255  	err := ioutil.WriteFile(file, []byte(b), mode)
   256  	if err != nil {
   257  		fatal("%v", err)
   258  	}
   259  }
   260  
   261  // xmkdir creates the directory p.
   262  func xmkdir(p string) {
   263  	err := os.Mkdir(p, 0777)
   264  	if err != nil {
   265  		fatal("%v", err)
   266  	}
   267  }
   268  
   269  // xmkdirall creates the directory p and its parents, as needed.
   270  func xmkdirall(p string) {
   271  	err := os.MkdirAll(p, 0777)
   272  	if err != nil {
   273  		fatal("%v", err)
   274  	}
   275  }
   276  
   277  // xremove removes the file p.
   278  func xremove(p string) {
   279  	if vflag > 2 {
   280  		errprintf("rm %s\n", p)
   281  	}
   282  	os.Remove(p)
   283  }
   284  
   285  // xremoveall removes the file or directory tree rooted at p.
   286  func xremoveall(p string) {
   287  	if vflag > 2 {
   288  		errprintf("rm -r %s\n", p)
   289  	}
   290  	os.RemoveAll(p)
   291  }
   292  
   293  // xreaddir replaces dst with a list of the names of the files and subdirectories in dir.
   294  // The names are relative to dir; they are not full paths.
   295  func xreaddir(dir string) []string {
   296  	f, err := os.Open(dir)
   297  	if err != nil {
   298  		fatal("%v", err)
   299  	}
   300  	defer f.Close()
   301  	names, err := f.Readdirnames(-1)
   302  	if err != nil {
   303  		fatal("reading %s: %v", dir, err)
   304  	}
   305  	return names
   306  }
   307  
   308  // xreaddir replaces dst with a list of the names of the files in dir.
   309  // The names are relative to dir; they are not full paths.
   310  func xreaddirfiles(dir string) []string {
   311  	f, err := os.Open(dir)
   312  	if err != nil {
   313  		fatal("%v", err)
   314  	}
   315  	defer f.Close()
   316  	infos, err := f.Readdir(-1)
   317  	if err != nil {
   318  		fatal("reading %s: %v", dir, err)
   319  	}
   320  	var names []string
   321  	for _, fi := range infos {
   322  		if !fi.IsDir() {
   323  			names = append(names, fi.Name())
   324  		}
   325  	}
   326  	return names
   327  }
   328  
   329  // xworkdir creates a new temporary directory to hold object files
   330  // and returns the name of that directory.
   331  func xworkdir() string {
   332  	name, err := ioutil.TempDir("", "go-tool-dist-")
   333  	if err != nil {
   334  		fatal("%v", err)
   335  	}
   336  	return name
   337  }
   338  
   339  // fatal prints an error message to standard error and exits.
   340  func fatal(format string, args ...interface{}) {
   341  	fmt.Fprintf(os.Stderr, "go tool dist: %s\n", fmt.Sprintf(format, args...))
   342  
   343  	// Wait for background goroutines to finish,
   344  	// so that exit handler that removes the work directory
   345  	// is not fighting with active writes or open files.
   346  	if atomic.AddInt32(&nfatal, 1) == 1 {
   347  		close(dying)
   348  	}
   349  	for i := 0; i < maxbg; i++ {
   350  		bgwork <- func() {} // wake up workers so they notice nfatal > 0
   351  	}
   352  	bgdied.Wait()
   353  
   354  	xexit(2)
   355  }
   356  
   357  var atexits []func()
   358  
   359  // xexit exits the process with return code n.
   360  func xexit(n int) {
   361  	for i := len(atexits) - 1; i >= 0; i-- {
   362  		atexits[i]()
   363  	}
   364  	os.Exit(n)
   365  }
   366  
   367  // xatexit schedules the exit-handler f to be run when the program exits.
   368  func xatexit(f func()) {
   369  	atexits = append(atexits, f)
   370  }
   371  
   372  // xprintf prints a message to standard output.
   373  func xprintf(format string, args ...interface{}) {
   374  	fmt.Printf(format, args...)
   375  }
   376  
   377  // errprintf prints a message to standard output.
   378  func errprintf(format string, args ...interface{}) {
   379  	fmt.Fprintf(os.Stderr, format, args...)
   380  }
   381  
   382  // main takes care of OS-specific startup and dispatches to xmain.
   383  func main() {
   384  	os.Setenv("TERM", "dumb") // disable escape codes in clang errors
   385  
   386  	slash = string(filepath.Separator)
   387  
   388  	gohostos = runtime.GOOS
   389  	switch gohostos {
   390  	case "darwin":
   391  		// Even on 64-bit platform, darwin uname -m prints i386.
   392  		if strings.Contains(run("", CheckExit, "sysctl", "machdep.cpu.extfeatures"), "EM64T") {
   393  			gohostarch = "amd64"
   394  		}
   395  	case "solaris":
   396  		// Even on 64-bit platform, solaris uname -m prints i86pc.
   397  		out := run("", CheckExit, "isainfo", "-n")
   398  		if strings.Contains(out, "amd64") {
   399  			gohostarch = "amd64"
   400  		}
   401  		if strings.Contains(out, "i386") {
   402  			gohostarch = "386"
   403  		}
   404  	case "plan9":
   405  		gohostarch = os.Getenv("objtype")
   406  		if gohostarch == "" {
   407  			fatal("$objtype is unset")
   408  		}
   409  	case "windows":
   410  		exe = ".exe"
   411  	}
   412  
   413  	sysinit()
   414  
   415  	if gohostarch == "" {
   416  		// Default Unix system.
   417  		out := run("", CheckExit, "uname", "-m")
   418  		switch {
   419  		case strings.Contains(out, "x86_64"), strings.Contains(out, "amd64"):
   420  			gohostarch = "amd64"
   421  		case strings.Contains(out, "86"):
   422  			gohostarch = "386"
   423  		case strings.Contains(out, "arm"):
   424  			gohostarch = "arm"
   425  		case strings.Contains(out, "aarch64"):
   426  			gohostarch = "arm64"
   427  		case strings.Contains(out, "ppc64le"):
   428  			gohostarch = "ppc64le"
   429  		case strings.Contains(out, "ppc64"):
   430  			gohostarch = "ppc64"
   431  		case gohostos == "darwin":
   432  			if strings.Contains(run("", CheckExit, "uname", "-v"), "RELEASE_ARM_") {
   433  				gohostarch = "arm"
   434  			}
   435  		default:
   436  			fatal("unknown architecture: %s", out)
   437  		}
   438  	}
   439  
   440  	if gohostarch == "arm" {
   441  		maxbg = min(maxbg, runtime.NumCPU())
   442  	}
   443  	bginit()
   444  
   445  	// The OS X 10.6 linker does not support external linking mode.
   446  	// See golang.org/issue/5130.
   447  	//
   448  	// OS X 10.6 does not work with clang either, but OS X 10.9 requires it.
   449  	// It seems to work with OS X 10.8, so we default to clang for 10.8 and later.
   450  	// See golang.org/issue/5822.
   451  	//
   452  	// Roughly, OS X 10.N shows up as uname release (N+4),
   453  	// so OS X 10.6 is uname version 10 and OS X 10.8 is uname version 12.
   454  	if gohostos == "darwin" {
   455  		rel := run("", CheckExit, "uname", "-r")
   456  		if i := strings.Index(rel, "."); i >= 0 {
   457  			rel = rel[:i]
   458  		}
   459  		osx, _ := strconv.Atoi(rel)
   460  		if osx <= 6+4 {
   461  			goextlinkenabled = "0"
   462  		}
   463  		if osx >= 8+4 {
   464  			defaultclang = true
   465  		}
   466  	}
   467  
   468  	xinit()
   469  	xmain()
   470  	xexit(0)
   471  }
   472  
   473  // xsamefile reports whether f1 and f2 are the same file (or dir)
   474  func xsamefile(f1, f2 string) bool {
   475  	fi1, err1 := os.Stat(f1)
   476  	fi2, err2 := os.Stat(f2)
   477  	if err1 != nil || err2 != nil {
   478  		return f1 == f2
   479  	}
   480  	return os.SameFile(fi1, fi2)
   481  }
   482  
   483  func xgetgoarm() string {
   484  	if goos == "nacl" {
   485  		// NaCl guarantees VFPv3 and is always cross-compiled.
   486  		return "7"
   487  	}
   488  	if goos == "darwin" {
   489  		// Assume all darwin/arm devices are have VFPv3. This
   490  		// port is also mostly cross-compiled, so it makes little
   491  		// sense to auto-detect the setting.
   492  		return "7"
   493  	}
   494  	if gohostarch != "arm" || goos != gohostos {
   495  		// Conservative default for cross-compilation.
   496  		return "5"
   497  	}
   498  	if goos == "freebsd" || goos == "openbsd" {
   499  		// FreeBSD has broken VFP support.
   500  		// OpenBSD currently only supports softfloat.
   501  		return "5"
   502  	}
   503  	if goos != "linux" {
   504  		// All other arm platforms that we support
   505  		// require ARMv7.
   506  		return "7"
   507  	}
   508  	cpuinfo := readfile("/proc/cpuinfo")
   509  	goarm := "5"
   510  	for _, line := range splitlines(cpuinfo) {
   511  		line := strings.SplitN(line, ":", 2)
   512  		if len(line) < 2 {
   513  			continue
   514  		}
   515  		if strings.TrimSpace(line[0]) != "Features" {
   516  			continue
   517  		}
   518  		features := splitfields(line[1])
   519  		sort.Strings(features) // so vfpv3 sorts after vfp
   520  
   521  		// Infer GOARM value from the vfp features available
   522  		// on this host. Values of GOARM detected are:
   523  		// 5: no vfp support was found
   524  		// 6: vfp (v1) support was detected, but no higher
   525  		// 7: vfpv3 support was detected.
   526  		// This matches the assertions in runtime.checkarm.
   527  		for _, f := range features {
   528  			switch f {
   529  			case "vfp":
   530  				goarm = "6"
   531  			case "vfpv3":
   532  				goarm = "7"
   533  			}
   534  		}
   535  	}
   536  	return goarm
   537  }
   538  
   539  func min(a, b int) int {
   540  	if a < b {
   541  		return a
   542  	}
   543  	return b
   544  }