| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package build |
| |
| import ( |
| "bytes" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| "syscall" |
| "unsafe" |
| ) |
| |
| func absPath(ctx Context, p string) string { |
| ret, err := filepath.Abs(p) |
| if err != nil { |
| ctx.Fatalf("Failed to get absolute path: %v", err) |
| } |
| return ret |
| } |
| |
| // indexList finds the index of a string in a []string |
| func indexList(s string, list []string) int { |
| for i, l := range list { |
| if l == s { |
| return i |
| } |
| } |
| |
| return -1 |
| } |
| |
| // inList determines whether a string is in a []string |
| func inList(s string, list []string) bool { |
| return indexList(s, list) != -1 |
| } |
| |
| // ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger. |
| func ensureDirectoriesExist(ctx Context, dirs ...string) { |
| for _, dir := range dirs { |
| err := os.MkdirAll(dir, 0777) |
| if err != nil { |
| ctx.Fatalf("Error creating %s: %q\n", dir, err) |
| } |
| } |
| } |
| |
| // ensureEmptyDirectoriesExist ensures that the given directories exist and are empty |
| func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) { |
| // remove all the directories |
| for _, dir := range dirs { |
| seenErr := map[string]bool{} |
| for { |
| err := os.RemoveAll(dir) |
| if err == nil { |
| break |
| } |
| |
| if pathErr, ok := err.(*os.PathError); !ok || |
| dir == pathErr.Path || seenErr[pathErr.Path] { |
| |
| ctx.Fatalf("Error removing %s: %q\n", dir, err) |
| } else { |
| seenErr[pathErr.Path] = true |
| err = os.Chmod(filepath.Dir(pathErr.Path), 0700) |
| if err != nil { |
| ctx.Fatal(err) |
| } |
| } |
| } |
| } |
| // recreate all the directories |
| ensureDirectoriesExist(ctx, dirs...) |
| } |
| |
| // ensureEmptyFileExists ensures that the containing directory exists, and the |
| // specified file exists. If it doesn't exist, it will write an empty file. |
| func ensureEmptyFileExists(ctx Context, file string) { |
| ensureDirectoriesExist(ctx, filepath.Dir(file)) |
| if _, err := os.Stat(file); os.IsNotExist(err) { |
| f, err := os.Create(file) |
| if err != nil { |
| ctx.Fatalf("Error creating %s: %q\n", file, err) |
| } |
| f.Close() |
| } else if err != nil { |
| ctx.Fatalf("Error checking %s: %q\n", file, err) |
| } |
| } |
| |
| // singleUnquote is similar to strconv.Unquote, but can handle multi-character strings inside single quotes. |
| func singleUnquote(str string) (string, bool) { |
| if len(str) < 2 || str[0] != '\'' || str[len(str)-1] != '\'' { |
| return "", false |
| } |
| return str[1 : len(str)-1], true |
| } |
| |
| // decodeKeyValue decodes a key=value string |
| func decodeKeyValue(str string) (string, string, bool) { |
| idx := strings.IndexRune(str, '=') |
| if idx == -1 { |
| return "", "", false |
| } |
| return str[:idx], str[idx+1:], true |
| } |
| |
| func isTerminal(w io.Writer) bool { |
| if f, ok := w.(*os.File); ok { |
| var termios syscall.Termios |
| _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), |
| 0, 0, 0) |
| return err == 0 |
| } |
| return false |
| } |
| |
| func termWidth(w io.Writer) (int, bool) { |
| if f, ok := w.(*os.File); ok { |
| var winsize struct { |
| ws_row, ws_column uint16 |
| ws_xpixel, ws_ypixel uint16 |
| } |
| _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), |
| 0, 0, 0) |
| return int(winsize.ws_column), err == 0 |
| } |
| return 0, false |
| } |
| |
| // stripAnsiEscapes strips ANSI control codes from a byte array in place. |
| func stripAnsiEscapes(input []byte) []byte { |
| // read represents the remaining part of input that needs to be processed. |
| read := input |
| // write represents where we should be writing in input. |
| // It will share the same backing store as input so that we make our modifications |
| // in place. |
| write := input |
| |
| // advance will copy count bytes from read to write and advance those slices |
| advance := func(write, read []byte, count int) ([]byte, []byte) { |
| copy(write, read[:count]) |
| return write[count:], read[count:] |
| } |
| |
| for { |
| // Find the next escape sequence |
| i := bytes.IndexByte(read, 0x1b) |
| // If it isn't found, or if there isn't room for <ESC>[, finish |
| if i == -1 || i+1 >= len(read) { |
| copy(write, read) |
| break |
| } |
| |
| // Not a CSI code, continue searching |
| if read[i+1] != '[' { |
| write, read = advance(write, read, i+1) |
| continue |
| } |
| |
| // Found a CSI code, advance up to the <ESC> |
| write, read = advance(write, read, i) |
| |
| // Find the end of the CSI code |
| i = bytes.IndexFunc(read, func(r rune) bool { |
| return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') |
| }) |
| if i == -1 { |
| // We didn't find the end of the code, just remove the rest |
| i = len(read) - 1 |
| } |
| |
| // Strip off the end marker too |
| i = i + 1 |
| |
| // Skip the reader forward and reduce final length by that amount |
| read = read[i:] |
| input = input[:len(input)-i] |
| } |
| |
| return input |
| } |