...

Source file src/cmd/dist/util.go

Documentation: cmd/dist

     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  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    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  const (
    54  	CheckExit = 1 << iota
    55  	ShowOutput
    56  	Background
    57  )
    58  
    59  var outputLock sync.Mutex
    60  
    61  // run is like runEnv with no additional environment.
    62  func run(dir string, mode int, cmd ...string) string {
    63  	return runEnv(dir, mode, nil, cmd...)
    64  }
    65  
    66  // runEnv runs the command line cmd in dir with additional environment env.
    67  // If mode has ShowOutput set and Background unset, run passes cmd's output to
    68  // stdout/stderr directly. Otherwise, run returns cmd's output as a string.
    69  // If mode has CheckExit set and the command fails, run calls fatalf.
    70  // If mode has Background set, this command is being run as a
    71  // Background job. Only bgrun should use the Background mode,
    72  // not other callers.
    73  func runEnv(dir string, mode int, env []string, cmd ...string) string {
    74  	if vflag > 1 {
    75  		errprintf("run: %s\n", strings.Join(cmd, " "))
    76  	}
    77  
    78  	xcmd := exec.Command(cmd[0], cmd[1:]...)
    79  	if env != nil {
    80  		xcmd.Env = append(os.Environ(), env...)
    81  	}
    82  	setDir(xcmd, dir)
    83  	var data []byte
    84  	var err error
    85  
    86  	// If we want to show command output and this is not
    87  	// a background command, assume it's the only thing
    88  	// running, so we can just let it write directly stdout/stderr
    89  	// as it runs without fear of mixing the output with some
    90  	// other command's output. Not buffering lets the output
    91  	// appear as it is printed instead of once the command exits.
    92  	// This is most important for the invocation of 'go build -v bootstrap/...'.
    93  	if mode&(Background|ShowOutput) == ShowOutput {
    94  		xcmd.Stdout = os.Stdout
    95  		xcmd.Stderr = os.Stderr
    96  		err = xcmd.Run()
    97  	} else {
    98  		data, err = xcmd.CombinedOutput()
    99  	}
   100  	if err != nil && mode&CheckExit != 0 {
   101  		outputLock.Lock()
   102  		if len(data) > 0 {
   103  			xprintf("%s\n", data)
   104  		}
   105  		outputLock.Unlock()
   106  		if mode&Background != 0 {
   107  			// Prevent fatalf from waiting on our own goroutine's
   108  			// bghelper to exit:
   109  			bghelpers.Done()
   110  		}
   111  		fatalf("FAILED: %v: %v", strings.Join(cmd, " "), err)
   112  	}
   113  	if mode&ShowOutput != 0 {
   114  		outputLock.Lock()
   115  		os.Stdout.Write(data)
   116  		outputLock.Unlock()
   117  	}
   118  	if vflag > 2 {
   119  		errprintf("run: %s DONE\n", strings.Join(cmd, " "))
   120  	}
   121  	return string(data)
   122  }
   123  
   124  var maxbg = 4 /* maximum number of jobs to run at once */
   125  
   126  var (
   127  	bgwork = make(chan func(), 1e5)
   128  
   129  	bghelpers sync.WaitGroup
   130  
   131  	dieOnce sync.Once // guards close of dying
   132  	dying   = make(chan struct{})
   133  )
   134  
   135  func bginit() {
   136  	bghelpers.Add(maxbg)
   137  	for i := 0; i < maxbg; i++ {
   138  		go bghelper()
   139  	}
   140  }
   141  
   142  func bghelper() {
   143  	defer bghelpers.Done()
   144  	for {
   145  		select {
   146  		case <-dying:
   147  			return
   148  		case w := <-bgwork:
   149  			// Dying takes precedence over doing more work.
   150  			select {
   151  			case <-dying:
   152  				return
   153  			default:
   154  				w()
   155  			}
   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  // bgrun adds 1 to wg immediately, and calls Done when the work completes.
   163  func bgrun(wg *sync.WaitGroup, dir string, cmd ...string) {
   164  	wg.Add(1)
   165  	bgwork <- func() {
   166  		defer wg.Done()
   167  		run(dir, CheckExit|ShowOutput|Background, cmd...)
   168  	}
   169  }
   170  
   171  // bgwait waits for pending bgruns to finish.
   172  // bgwait must be called from only a single goroutine at a time.
   173  func bgwait(wg *sync.WaitGroup) {
   174  	done := make(chan struct{})
   175  	go func() {
   176  		wg.Wait()
   177  		close(done)
   178  	}()
   179  	select {
   180  	case <-done:
   181  	case <-dying:
   182  		// Don't return to the caller, to avoid reporting additional errors
   183  		// to the user.
   184  		select {}
   185  	}
   186  }
   187  
   188  // xgetwd returns the current directory.
   189  func xgetwd() string {
   190  	wd, err := os.Getwd()
   191  	if err != nil {
   192  		fatalf("%s", err)
   193  	}
   194  	return wd
   195  }
   196  
   197  // xrealwd returns the 'real' name for the given path.
   198  // real is defined as what xgetwd returns in that directory.
   199  func xrealwd(path string) string {
   200  	old := xgetwd()
   201  	if err := os.Chdir(path); err != nil {
   202  		fatalf("chdir %s: %v", path, err)
   203  	}
   204  	real := xgetwd()
   205  	if err := os.Chdir(old); err != nil {
   206  		fatalf("chdir %s: %v", old, err)
   207  	}
   208  	return real
   209  }
   210  
   211  // isdir reports whether p names an existing directory.
   212  func isdir(p string) bool {
   213  	fi, err := os.Stat(p)
   214  	return err == nil && fi.IsDir()
   215  }
   216  
   217  // isfile reports whether p names an existing file.
   218  func isfile(p string) bool {
   219  	fi, err := os.Stat(p)
   220  	return err == nil && fi.Mode().IsRegular()
   221  }
   222  
   223  // mtime returns the modification time of the file p.
   224  func mtime(p string) time.Time {
   225  	fi, err := os.Stat(p)
   226  	if err != nil {
   227  		return time.Time{}
   228  	}
   229  	return fi.ModTime()
   230  }
   231  
   232  // readfile returns the content of the named file.
   233  func readfile(file string) string {
   234  	data, err := os.ReadFile(file)
   235  	if err != nil {
   236  		fatalf("%v", err)
   237  	}
   238  	return string(data)
   239  }
   240  
   241  const (
   242  	writeExec = 1 << iota
   243  	writeSkipSame
   244  )
   245  
   246  // writefile writes text to the named file, creating it if needed.
   247  // if exec is non-zero, marks the file as executable.
   248  // If the file already exists and has the expected content,
   249  // it is not rewritten, to avoid changing the time stamp.
   250  func writefile(text, file string, flag int) {
   251  	new := []byte(text)
   252  	if flag&writeSkipSame != 0 {
   253  		old, err := os.ReadFile(file)
   254  		if err == nil && bytes.Equal(old, new) {
   255  			return
   256  		}
   257  	}
   258  	mode := os.FileMode(0666)
   259  	if flag&writeExec != 0 {
   260  		mode = 0777
   261  	}
   262  	xremove(file) // in case of symlink tricks by misc/reboot test
   263  	err := os.WriteFile(file, new, mode)
   264  	if err != nil {
   265  		fatalf("%v", err)
   266  	}
   267  }
   268  
   269  // xmkdir creates the directory p.
   270  func xmkdir(p string) {
   271  	err := os.Mkdir(p, 0777)
   272  	if err != nil {
   273  		fatalf("%v", err)
   274  	}
   275  }
   276  
   277  // xmkdirall creates the directory p and its parents, as needed.
   278  func xmkdirall(p string) {
   279  	err := os.MkdirAll(p, 0777)
   280  	if err != nil {
   281  		fatalf("%v", err)
   282  	}
   283  }
   284  
   285  // xremove removes the file p.
   286  func xremove(p string) {
   287  	if vflag > 2 {
   288  		errprintf("rm %s\n", p)
   289  	}
   290  	os.Remove(p)
   291  }
   292  
   293  // xremoveall removes the file or directory tree rooted at p.
   294  func xremoveall(p string) {
   295  	if vflag > 2 {
   296  		errprintf("rm -r %s\n", p)
   297  	}
   298  	os.RemoveAll(p)
   299  }
   300  
   301  // xreaddir replaces dst with a list of the names of the files and subdirectories in dir.
   302  // The names are relative to dir; they are not full paths.
   303  func xreaddir(dir string) []string {
   304  	f, err := os.Open(dir)
   305  	if err != nil {
   306  		fatalf("%v", err)
   307  	}
   308  	defer f.Close()
   309  	names, err := f.Readdirnames(-1)
   310  	if err != nil {
   311  		fatalf("reading %s: %v", dir, err)
   312  	}
   313  	return names
   314  }
   315  
   316  // xworkdir creates a new temporary directory to hold object files
   317  // and returns the name of that directory.
   318  func xworkdir() string {
   319  	name, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-tool-dist-")
   320  	if err != nil {
   321  		fatalf("%v", err)
   322  	}
   323  	return name
   324  }
   325  
   326  // fatalf prints an error message to standard error and exits.
   327  func fatalf(format string, args ...interface{}) {
   328  	fmt.Fprintf(os.Stderr, "go tool dist: %s\n", fmt.Sprintf(format, args...))
   329  
   330  	dieOnce.Do(func() { close(dying) })
   331  
   332  	// Wait for background goroutines to finish,
   333  	// so that exit handler that removes the work directory
   334  	// is not fighting with active writes or open files.
   335  	bghelpers.Wait()
   336  
   337  	xexit(2)
   338  }
   339  
   340  var atexits []func()
   341  
   342  // xexit exits the process with return code n.
   343  func xexit(n int) {
   344  	for i := len(atexits) - 1; i >= 0; i-- {
   345  		atexits[i]()
   346  	}
   347  	os.Exit(n)
   348  }
   349  
   350  // xatexit schedules the exit-handler f to be run when the program exits.
   351  func xatexit(f func()) {
   352  	atexits = append(atexits, f)
   353  }
   354  
   355  // xprintf prints a message to standard output.
   356  func xprintf(format string, args ...interface{}) {
   357  	fmt.Printf(format, args...)
   358  }
   359  
   360  // errprintf prints a message to standard output.
   361  func errprintf(format string, args ...interface{}) {
   362  	fmt.Fprintf(os.Stderr, format, args...)
   363  }
   364  
   365  // xsamefile reports whether f1 and f2 are the same file (or dir).
   366  func xsamefile(f1, f2 string) bool {
   367  	fi1, err1 := os.Stat(f1)
   368  	fi2, err2 := os.Stat(f2)
   369  	if err1 != nil || err2 != nil {
   370  		return f1 == f2
   371  	}
   372  	return os.SameFile(fi1, fi2)
   373  }
   374  
   375  func xgetgoarm() string {
   376  	// If we're building on an actual arm system, and not building
   377  	// a cross-compiling toolchain, try to exec ourselves
   378  	// to detect whether VFP is supported and set the default GOARM.
   379  	// Windows requires ARMv7, so we can skip the check.
   380  	// We've always assumed Android is ARMv7 too.
   381  	if gohostarch == "arm" && goarch == "arm" && goos == gohostos && goos != "windows" && goos != "android" {
   382  		// Try to exec ourselves in a mode to detect VFP support.
   383  		// Seeing how far it gets determines which instructions failed.
   384  		// The test is OS-agnostic.
   385  		out := run("", 0, os.Args[0], "-check-goarm")
   386  		v1ok := strings.Contains(out, "VFPv1 OK.")
   387  		v3ok := strings.Contains(out, "VFPv3 OK.")
   388  		if v1ok && v3ok {
   389  			return "7"
   390  		}
   391  		if v1ok {
   392  			return "6"
   393  		}
   394  		return "5"
   395  	}
   396  
   397  	// Otherwise, in the absence of local information, assume GOARM=7.
   398  	//
   399  	// We used to assume GOARM=5 in certain contexts but not others,
   400  	// which produced inconsistent results. For example if you cross-compiled
   401  	// for linux/arm from a windows/amd64 machine, you got GOARM=7 binaries,
   402  	// but if you cross-compiled for linux/arm from a linux/amd64 machine,
   403  	// you got GOARM=5 binaries. Now the default is independent of the
   404  	// host operating system, for better reproducibility of builds.
   405  	return "7"
   406  }
   407  
   408  func min(a, b int) int {
   409  	if a < b {
   410  		return a
   411  	}
   412  	return b
   413  }
   414  
   415  // elfIsLittleEndian detects if the ELF file is little endian.
   416  func elfIsLittleEndian(fn string) bool {
   417  	// read the ELF file header to determine the endianness without using the
   418  	// debug/elf package.
   419  	file, err := os.Open(fn)
   420  	if err != nil {
   421  		fatalf("failed to open file to determine endianness: %v", err)
   422  	}
   423  	defer file.Close()
   424  	var hdr [16]byte
   425  	if _, err := io.ReadFull(file, hdr[:]); err != nil {
   426  		fatalf("failed to read ELF header to determine endianness: %v", err)
   427  	}
   428  	// hdr[5] is EI_DATA byte, 1 is ELFDATA2LSB and 2 is ELFDATA2MSB
   429  	switch hdr[5] {
   430  	default:
   431  		fatalf("unknown ELF endianness of %s: EI_DATA = %d", fn, hdr[5])
   432  	case 1:
   433  		return true
   434  	case 2:
   435  		return false
   436  	}
   437  	panic("unreachable")
   438  }
   439  
   440  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   441  // If used as -name, it increments the count, but -name=x sets the count.
   442  // Used for verbose flag -v.
   443  type count int
   444  
   445  func (c *count) String() string {
   446  	return fmt.Sprint(int(*c))
   447  }
   448  
   449  func (c *count) Set(s string) error {
   450  	switch s {
   451  	case "true":
   452  		*c++
   453  	case "false":
   454  		*c = 0
   455  	default:
   456  		n, err := strconv.Atoi(s)
   457  		if err != nil {
   458  			return fmt.Errorf("invalid count %q", s)
   459  		}
   460  		*c = count(n)
   461  	}
   462  	return nil
   463  }
   464  
   465  func (c *count) IsBoolFlag() bool {
   466  	return true
   467  }
   468  
   469  func xflagparse(maxargs int) {
   470  	flag.Var((*count)(&vflag), "v", "verbosity")
   471  	flag.Parse()
   472  	if maxargs >= 0 && flag.NArg() > maxargs {
   473  		flag.Usage()
   474  	}
   475  }
   476  

View as plain text