Dan Willemsen | 1e70446 | 2016-08-21 15:17:17 -0700 | [diff] [blame] | 1 | // Copyright 2017 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package build |
| 16 | |
| 17 | import ( |
Dan Willemsen | 29f8827 | 2017-02-18 18:12:41 -0800 | [diff] [blame] | 18 | "bytes" |
| 19 | "io" |
Dan Willemsen | 1e70446 | 2016-08-21 15:17:17 -0700 | [diff] [blame] | 20 | "os" |
| 21 | "path/filepath" |
| 22 | "strings" |
Dan Willemsen | 29f8827 | 2017-02-18 18:12:41 -0800 | [diff] [blame] | 23 | "syscall" |
| 24 | "unsafe" |
Dan Willemsen | 1e70446 | 2016-08-21 15:17:17 -0700 | [diff] [blame] | 25 | ) |
| 26 | |
Dan Willemsen | d9e8f0a | 2017-10-30 13:42:06 -0700 | [diff] [blame^] | 27 | func absPath(ctx Context, p string) string { |
| 28 | ret, err := filepath.Abs(p) |
| 29 | if err != nil { |
| 30 | ctx.Fatalf("Failed to get absolute path: %v", err) |
| 31 | } |
| 32 | return ret |
| 33 | } |
| 34 | |
Dan Willemsen | 1e70446 | 2016-08-21 15:17:17 -0700 | [diff] [blame] | 35 | // indexList finds the index of a string in a []string |
| 36 | func indexList(s string, list []string) int { |
| 37 | for i, l := range list { |
| 38 | if l == s { |
| 39 | return i |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | return -1 |
| 44 | } |
| 45 | |
| 46 | // inList determines whether a string is in a []string |
| 47 | func inList(s string, list []string) bool { |
| 48 | return indexList(s, list) != -1 |
| 49 | } |
| 50 | |
| 51 | // ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger. |
| 52 | func ensureDirectoriesExist(ctx Context, dirs ...string) { |
| 53 | for _, dir := range dirs { |
| 54 | err := os.MkdirAll(dir, 0777) |
| 55 | if err != nil { |
| 56 | ctx.Fatalf("Error creating %s: %q\n", dir, err) |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
Jeff Gaston | efc1b41 | 2017-03-29 17:29:06 -0700 | [diff] [blame] | 61 | // ensureEmptyDirectoriesExist ensures that the given directories exist and are empty |
| 62 | func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) { |
| 63 | // remove all the directories |
| 64 | for _, dir := range dirs { |
| 65 | err := os.RemoveAll(dir) |
| 66 | if err != nil { |
| 67 | ctx.Fatalf("Error removing %s: %q\n", dir, err) |
| 68 | } |
| 69 | } |
| 70 | // recreate all the directories |
| 71 | ensureDirectoriesExist(ctx, dirs...) |
| 72 | } |
| 73 | |
Dan Willemsen | 1e70446 | 2016-08-21 15:17:17 -0700 | [diff] [blame] | 74 | // ensureEmptyFileExists ensures that the containing directory exists, and the |
| 75 | // specified file exists. If it doesn't exist, it will write an empty file. |
| 76 | func ensureEmptyFileExists(ctx Context, file string) { |
| 77 | ensureDirectoriesExist(ctx, filepath.Dir(file)) |
| 78 | if _, err := os.Stat(file); os.IsNotExist(err) { |
| 79 | f, err := os.Create(file) |
| 80 | if err != nil { |
| 81 | ctx.Fatalf("Error creating %s: %q\n", file, err) |
| 82 | } |
| 83 | f.Close() |
| 84 | } else if err != nil { |
| 85 | ctx.Fatalf("Error checking %s: %q\n", file, err) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | // singleUnquote is similar to strconv.Unquote, but can handle multi-character strings inside single quotes. |
| 90 | func singleUnquote(str string) (string, bool) { |
| 91 | if len(str) < 2 || str[0] != '\'' || str[len(str)-1] != '\'' { |
| 92 | return "", false |
| 93 | } |
| 94 | return str[1 : len(str)-1], true |
| 95 | } |
| 96 | |
| 97 | // decodeKeyValue decodes a key=value string |
| 98 | func decodeKeyValue(str string) (string, string, bool) { |
| 99 | idx := strings.IndexRune(str, '=') |
| 100 | if idx == -1 { |
| 101 | return "", "", false |
| 102 | } |
| 103 | return str[:idx], str[idx+1:], true |
| 104 | } |
Dan Willemsen | 29f8827 | 2017-02-18 18:12:41 -0800 | [diff] [blame] | 105 | |
| 106 | func isTerminal(w io.Writer) bool { |
| 107 | if f, ok := w.(*os.File); ok { |
| 108 | var termios syscall.Termios |
| 109 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| 110 | ioctlGetTermios, uintptr(unsafe.Pointer(&termios)), |
| 111 | 0, 0, 0) |
| 112 | return err == 0 |
| 113 | } |
| 114 | return false |
| 115 | } |
| 116 | |
| 117 | func termWidth(w io.Writer) (int, bool) { |
| 118 | if f, ok := w.(*os.File); ok { |
| 119 | var winsize struct { |
| 120 | ws_row, ws_column uint16 |
| 121 | ws_xpixel, ws_ypixel uint16 |
| 122 | } |
| 123 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, f.Fd(), |
| 124 | syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&winsize)), |
| 125 | 0, 0, 0) |
| 126 | return int(winsize.ws_column), err == 0 |
| 127 | } |
| 128 | return 0, false |
| 129 | } |
| 130 | |
| 131 | // stripAnsiEscapes strips ANSI control codes from a byte array in place. |
| 132 | func stripAnsiEscapes(input []byte) []byte { |
| 133 | // read represents the remaining part of input that needs to be processed. |
| 134 | read := input |
| 135 | // write represents where we should be writing in input. |
| 136 | // It will share the same backing store as input so that we make our modifications |
| 137 | // in place. |
| 138 | write := input |
| 139 | |
| 140 | // advance will copy count bytes from read to write and advance those slices |
| 141 | advance := func(write, read []byte, count int) ([]byte, []byte) { |
| 142 | copy(write, read[:count]) |
| 143 | return write[count:], read[count:] |
| 144 | } |
| 145 | |
| 146 | for { |
| 147 | // Find the next escape sequence |
| 148 | i := bytes.IndexByte(read, 0x1b) |
| 149 | // If it isn't found, or if there isn't room for <ESC>[, finish |
| 150 | if i == -1 || i+1 >= len(read) { |
| 151 | copy(write, read) |
| 152 | break |
| 153 | } |
| 154 | |
| 155 | // Not a CSI code, continue searching |
| 156 | if read[i+1] != '[' { |
| 157 | write, read = advance(write, read, i+1) |
| 158 | continue |
| 159 | } |
| 160 | |
| 161 | // Found a CSI code, advance up to the <ESC> |
| 162 | write, read = advance(write, read, i) |
| 163 | |
| 164 | // Find the end of the CSI code |
| 165 | i = bytes.IndexFunc(read, func(r rune) bool { |
| 166 | return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') |
| 167 | }) |
| 168 | if i == -1 { |
| 169 | // We didn't find the end of the code, just remove the rest |
| 170 | i = len(read) - 1 |
| 171 | } |
| 172 | |
| 173 | // Strip off the end marker too |
| 174 | i = i + 1 |
| 175 | |
| 176 | // Skip the reader forward and reduce final length by that amount |
| 177 | read = read[i:] |
| 178 | input = input[:len(input)-i] |
| 179 | } |
| 180 | |
| 181 | return input |
| 182 | } |