| // 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" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strconv" | |
| "strings" | |
| "sync" | |
| ) | |
| // pathf is fmt.Sprintf for generating paths | |
| // (on windows it turns / into \ after the printf). | |
| func pathf(format string, args ...any) string { | |
| return filepath.Clean(fmt.Sprintf(format, args...)) | |
| } | |
| const ( | |
| CheckExit = 1 << iota | |
| ShowOutput | |
| ) | |
| var outputLock sync.Mutex | |
| // run is like runEnv with no additional environment. | |
| func run(dir string, mode int, cmd ...string) string { | |
| return runEnv(dir, mode, nil, cmd...) | |
| } | |
| // runEnv runs the command line cmd in dir with additional environment env. | |
| // If mode has ShowOutput set, 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 fatalf. | |
| func runEnv(dir string, mode int, env []string, cmd ...string) string { | |
| if vflag > 1 { | |
| errprintf("run: %s\n", strings.Join(cmd, " ")) | |
| } | |
| xcmd := exec.Command(cmd[0], cmd[1:]...) | |
| if env != nil { | |
| xcmd.Env = append(os.Environ(), env...) | |
| } | |
| setDir(xcmd, 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 'go build -v bootstrap/...'. | |
| if mode&ShowOutput != 0 { | |
| 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() | |
| fatalf("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 */ | |
| // 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() | |
| } | |
| // readfile returns the content of the named file. | |
| func readfile(file string) string { | |
| data, err := os.ReadFile(file) | |
| if err != nil { | |
| fatalf("%v", err) | |
| } | |
| return string(data) | |
| } | |
| const ( | |
| writeExec = 1 << iota | |
| writeSkipSame | |
| ) | |
| // writefile writes text 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(text, file string, flag int) { | |
| new := []byte(text) | |
| if flag&writeSkipSame != 0 { | |
| old, err := os.ReadFile(file) | |
| if err == nil && bytes.Equal(old, new) { | |
| return | |
| } | |
| } | |
| mode := os.FileMode(0666) | |
| if flag&writeExec != 0 { | |
| mode = 0777 | |
| } | |
| xremove(file) // in case of symlink tricks by misc/reboot test | |
| err := os.WriteFile(file, new, mode) | |
| if err != nil { | |
| fatalf("%v", err) | |
| } | |
| } | |
| // xmkdir creates the directory p. | |
| func xmkdir(p string) { | |
| err := os.Mkdir(p, 0777) | |
| if err != nil { | |
| fatalf("%v", err) | |
| } | |
| } | |
| // xmkdirall creates the directory p and its parents, as needed. | |
| func xmkdirall(p string) { | |
| err := os.MkdirAll(p, 0777) | |
| if err != nil { | |
| fatalf("%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 { | |
| fatalf("%v", err) | |
| } | |
| defer f.Close() | |
| names, err := f.Readdirnames(-1) | |
| if err != nil { | |
| fatalf("reading %s: %v", dir, err) | |
| } | |
| return names | |
| } | |
| // xworkdir creates a new temporary directory to hold object files | |
| // and returns the name of that directory. | |
| func xworkdir() string { | |
| name, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-tool-dist-") | |
| if err != nil { | |
| fatalf("%v", err) | |
| } | |
| return name | |
| } | |
| // fatalf prints an error message to standard error and exits. | |
| func fatalf(format string, args ...any) { | |
| fmt.Fprintf(os.Stderr, "go tool dist: %s\n", fmt.Sprintf(format, args...)) | |
| 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 ...any) { | |
| fmt.Printf(format, args...) | |
| } | |
| // errprintf prints a message to standard output. | |
| func errprintf(format string, args ...any) { | |
| fmt.Fprintf(os.Stderr, format, args...) | |
| } | |
| func xgetgoarm() string { | |
| // If we're building on an actual arm system, and not building | |
| // a cross-compiling toolchain, try to exec ourselves | |
| // to detect whether VFP is supported and set the default GOARM. | |
| // We've always assumed Android is ARMv7. | |
| if gohostarch == "arm" && goarch == "arm" && goos == gohostos && goos != "android" { | |
| // 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" | |
| } | |
| // Otherwise, in the absence of local information, assume GOARM=7. | |
| // | |
| // We used to assume GOARM=5 in certain contexts but not others, | |
| // which produced inconsistent results. For example if you cross-compiled | |
| // for linux/arm from a windows/amd64 machine, you got GOARM=7 binaries, | |
| // but if you cross-compiled for linux/arm from a linux/amd64 machine, | |
| // you got GOARM=5 binaries. Now the default is independent of the | |
| // host operating system, for better reproducibility of builds. | |
| return "7" | |
| } | |
| // elfIsLittleEndian detects if the ELF file is little endian. | |
| func elfIsLittleEndian(fn string) bool { | |
| // read the ELF file header to determine the endianness without using the | |
| // debug/elf package. | |
| file, err := os.Open(fn) | |
| if err != nil { | |
| fatalf("failed to open file to determine endianness: %v", err) | |
| } | |
| defer file.Close() | |
| var hdr [16]byte | |
| if _, err := io.ReadFull(file, hdr[:]); err != nil { | |
| fatalf("failed to read ELF header to determine endianness: %v", err) | |
| } | |
| // hdr[5] is EI_DATA byte, 1 is ELFDATA2LSB and 2 is ELFDATA2MSB | |
| switch hdr[5] { | |
| default: | |
| fatalf("unknown ELF endianness of %s: EI_DATA = %d", fn, hdr[5]) | |
| case 1: | |
| return true | |
| case 2: | |
| return false | |
| } | |
| panic("unreachable") | |
| } | |
| // count is a flag.Value that is like a flag.Bool and a flag.Int. | |
| // If used as -name, it increments the count, but -name=x sets the count. | |
| // Used for verbose flag -v. | |
| type count int | |
| func (c *count) String() string { | |
| return fmt.Sprint(int(*c)) | |
| } | |
| func (c *count) Set(s string) error { | |
| switch s { | |
| case "true": | |
| *c++ | |
| case "false": | |
| *c = 0 | |
| default: | |
| n, err := strconv.Atoi(s) | |
| if err != nil { | |
| return fmt.Errorf("invalid count %q", s) | |
| } | |
| *c = count(n) | |
| } | |
| return nil | |
| } | |
| func (c *count) IsBoolFlag() bool { | |
| return true | |
| } | |
| func xflagparse(maxargs int) { | |
| flag.Var((*count)(&vflag), "v", "verbosity") | |
| flag.Parse() | |
| if maxargs >= 0 && flag.NArg() > maxargs { | |
| flag.Usage() | |
| } | |
| } |