codekingpro/portable-devtools
114k
1// Copyright 2012 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5package main6 7import (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 paths23// (on windows it turns / into \ after the printf).24func pathf(format string, args ...any) 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.29func filter(list []string, f func(string) bool) []string {30 var out []string31 for _, x := range list {32 if f(x) {33 out = append(out, x)34 }35 }36 return out37}38 39// uniq returns a sorted slice containing the unique elements of list.40func 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 keep51}52 53const (54 CheckExit = 1 << iota55 ShowOutput56 Background57)58 59var outputLock sync.Mutex60 61// run is like runEnv with no additional environment.62func 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 to68// 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 a71// Background job. Only bgrun should use the Background mode,72// not other callers.73func 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 []byte84 var err error85 86 // If we want to show command output and this is not87 // a background command, assume it's the only thing88 // running, so we can just let it write directly stdout/stderr89 // as it runs without fear of mixing the output with some90 // other command's output. Not buffering lets the output91 // 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.Stdout95 xcmd.Stderr = os.Stderr96 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's108 // 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 124var maxbg = 4 /* maximum number of jobs to run at once */125 126var (127 bgwork = make(chan func(), 1e5)128 129 bghelpers sync.WaitGroup130 131 dieOnce sync.Once // guards close of dying132 dying = make(chan struct{})133)134 135func bginit() {136 bghelpers.Add(maxbg)137 for i := 0; i < maxbg; i++ {138 go bghelper()139 }140}141 142func bghelper() {143 defer bghelpers.Done()144 for {145 select {146 case <-dying:147 return148 case w := <-bgwork:149 // Dying takes precedence over doing more work.150 select {151 case <-dying:152 return153 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.163func 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.173func 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 errors183 // to the user.184 select {}185 }186}187 188// xgetwd returns the current directory.189func xgetwd() string {190 wd, err := os.Getwd()191 if err != nil {192 fatalf("%s", err)193 }194 return wd195}196 197// xrealwd returns the 'real' name for the given path.198// real is defined as what xgetwd returns in that directory.199func 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 real209}210 211// isdir reports whether p names an existing directory.212func 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.218func 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.224func 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.233func 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 241const (242 writeExec = 1 << iota243 writeSkipSame244)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.250func 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 return256 }257 }258 mode := os.FileMode(0666)259 if flag&writeExec != 0 {260 mode = 0777261 }262 xremove(file) // in case of symlink tricks by misc/reboot test263 err := os.WriteFile(file, new, mode)264 if err != nil {265 fatalf("%v", err)266 }267}268 269// xmkdir creates the directory p.270func 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.278func 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.286func 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.294func 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.303func 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 names314}315 316// xworkdir creates a new temporary directory to hold object files317// and returns the name of that directory.318func xworkdir() string {319 name, err := os.MkdirTemp(os.Getenv("GOTMPDIR"), "go-tool-dist-")320 if err != nil {321 fatalf("%v", err)322 }323 return name324}325 326// fatalf prints an error message to standard error and exits.327func fatalf(format string, args ...any) {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 directory334 // is not fighting with active writes or open files.335 bghelpers.Wait()336 337 xexit(2)338}339 340var atexits []func()341 342// xexit exits the process with return code n.343func 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.351func xatexit(f func()) {352 atexits = append(atexits, f)353}354 355// xprintf prints a message to standard output.356func xprintf(format string, args ...any) {357 fmt.Printf(format, args...)358}359 360// errprintf prints a message to standard output.361func errprintf(format string, args ...any) {362 fmt.Fprintf(os.Stderr, format, args...)363}364 365func xgetgoarm() string {366 // If we're building on an actual arm system, and not building367 // a cross-compiling toolchain, try to exec ourselves368 // to detect whether VFP is supported and set the default GOARM.369 // We've always assumed Android is ARMv7.370 if gohostarch == "arm" && goarch == "arm" && goos == gohostos && goos != "android" {371 // Try to exec ourselves in a mode to detect VFP support.372 // Seeing how far it gets determines which instructions failed.373 // The test is OS-agnostic.374 out := run("", 0, os.Args[0], "-check-goarm")375 v1ok := strings.Contains(out, "VFPv1 OK.")376 v3ok := strings.Contains(out, "VFPv3 OK.")377 if v1ok && v3ok {378 return "7"379 }380 if v1ok {381 return "6"382 }383 return "5"384 }385 386 // Otherwise, in the absence of local information, assume GOARM=7.387 //388 // We used to assume GOARM=5 in certain contexts but not others,389 // which produced inconsistent results. For example if you cross-compiled390 // for linux/arm from a windows/amd64 machine, you got GOARM=7 binaries,391 // but if you cross-compiled for linux/arm from a linux/amd64 machine,392 // you got GOARM=5 binaries. Now the default is independent of the393 // host operating system, for better reproducibility of builds.394 return "7"395}396 397// elfIsLittleEndian detects if the ELF file is little endian.398func elfIsLittleEndian(fn string) bool {399 // read the ELF file header to determine the endianness without using the400 // debug/elf package.401 file, err := os.Open(fn)402 if err != nil {403 fatalf("failed to open file to determine endianness: %v", err)404 }405 defer file.Close()406 var hdr [16]byte407 if _, err := io.ReadFull(file, hdr[:]); err != nil {408 fatalf("failed to read ELF header to determine endianness: %v", err)409 }410 // hdr[5] is EI_DATA byte, 1 is ELFDATA2LSB and 2 is ELFDATA2MSB411 switch hdr[5] {412 default:413 fatalf("unknown ELF endianness of %s: EI_DATA = %d", fn, hdr[5])414 case 1:415 return true416 case 2:417 return false418 }419 panic("unreachable")420}421 422// count is a flag.Value that is like a flag.Bool and a flag.Int.423// If used as -name, it increments the count, but -name=x sets the count.424// Used for verbose flag -v.425type count int426 427func (c *count) String() string {428 return fmt.Sprint(int(*c))429}430 431func (c *count) Set(s string) error {432 switch s {433 case "true":434 *c++435 case "false":436 *c = 0437 default:438 n, err := strconv.Atoi(s)439 if err != nil {440 return fmt.Errorf("invalid count %q", s)441 }442 *c = count(n)443 }444 return nil445}446 447func (c *count) IsBoolFlag() bool {448 return true449}450 451func xflagparse(maxargs int) {452 flag.Var((*count)(&vflag), "v", "verbosity")453 flag.Parse()454 if maxargs >= 0 && flag.NArg() > maxargs {455 flag.Usage()456 }457}458 