github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/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 dist 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 }