github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/cmd/nin/status.go (about) 1 // Copyright 2016 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 main 16 17 import ( 18 "fmt" 19 "os" 20 "strconv" 21 22 "github.com/maruel/nin" 23 ) 24 25 // Implementation of the Status interface that prints the status as 26 // human-readable strings to stdout 27 type statusPrinter struct { 28 config *nin.BuildConfig 29 30 startedEdges, finishedEdges, totalEdges, runningEdges int 31 timeMillis int32 32 33 // Prints progress output. 34 printer linePrinter 35 36 // The custom progress status format to use. 37 progressStatusFormat string 38 currentRate slidingRateInfo 39 } 40 41 type slidingRateInfo struct { 42 rate float64 43 N int 44 times []float64 45 lastUpdate int 46 } 47 48 func (s *slidingRateInfo) updateRate(updateHint int, timeMillis int32) { 49 if updateHint == s.lastUpdate { 50 return 51 } 52 s.lastUpdate = updateHint 53 54 if len(s.times) == s.N { 55 s.times = s.times[:len(s.times)-1] 56 } 57 s.times = append(s.times, float64(timeMillis)) 58 back := s.times[0] 59 front := s.times[len(s.times)-1] 60 if back != front { 61 s.rate = float64(len(s.times)) / ((back - front) / 1e3) 62 } 63 } 64 65 func newStatusPrinter(config *nin.BuildConfig) *statusPrinter { 66 s := &statusPrinter{ 67 config: config, 68 printer: newLinePrinter(), 69 currentRate: slidingRateInfo{ 70 rate: -1, 71 N: config.Parallelism, 72 lastUpdate: -1, 73 }, 74 } 75 // Don't do anything fancy in verbose mode. 76 if s.config.Verbosity != nin.Normal { 77 s.printer.setSmartTerminal(false) 78 } 79 80 s.progressStatusFormat = os.Getenv("NINJA_STATUS") 81 if s.progressStatusFormat == "" { 82 s.progressStatusFormat = "[%f/%t] " 83 } 84 return s 85 } 86 87 func (s *statusPrinter) PlanHasTotalEdges(total int) { 88 s.totalEdges = total 89 } 90 91 func (s *statusPrinter) BuildEdgeStarted(edge *nin.Edge, startTimeMillis int32) { 92 s.startedEdges++ 93 s.runningEdges++ 94 s.timeMillis = startTimeMillis 95 if edge.Pool == nin.ConsolePool || s.printer.isSmartTerminal() { 96 s.PrintStatus(edge, startTimeMillis) 97 } 98 99 if edge.Pool == nin.ConsolePool { 100 s.printer.SetConsoleLocked(true) 101 } 102 } 103 104 func (s *statusPrinter) BuildEdgeFinished(edge *nin.Edge, endTimeMillis int32, success bool, output string) { 105 s.timeMillis = endTimeMillis 106 s.finishedEdges++ 107 108 if edge.Pool == nin.ConsolePool { 109 s.printer.SetConsoleLocked(false) 110 } 111 112 if s.config.Verbosity == nin.Quiet { 113 return 114 } 115 116 if edge.Pool != nin.ConsolePool { 117 s.PrintStatus(edge, endTimeMillis) 118 } 119 120 s.runningEdges-- 121 122 // Print the command that is spewing before printing its output. 123 if !success { 124 outputs := "" 125 for _, o := range edge.Outputs { 126 outputs += o.Path + " " 127 } 128 if s.printer.supportsColor { 129 s.printer.PrintOnNewLine("\x1B[31mFAILED: \x1B[0m" + outputs + "\n") 130 } else { 131 s.printer.PrintOnNewLine("FAILED: " + outputs + "\n") 132 } 133 s.printer.PrintOnNewLine(edge.EvaluateCommand(false) + "\n") 134 } 135 136 if len(output) != 0 { 137 // ninja sets stdout and stderr of subprocesses to a pipe, to be able to 138 // check if the output is empty. Some compilers, e.g. clang, check 139 // isatty(stderr) to decide if they should print colored output. 140 // To make it possible to use colored output with ninja, subprocesses should 141 // be run with a flag that forces them to always print color escape codes. 142 // To make sure these escape codes don't show up in a file if ninja's output 143 // is piped to a file, ninja strips ansi escape codes again if it's not 144 // writing to a |smartTerminal|. 145 // (Launching subprocesses in pseudo ttys doesn't work because there are 146 // only a few hundred available on some systems, and ninja can launch 147 // thousands of parallel compile commands.) 148 finalOutput := "" 149 if !s.printer.supportsColor { 150 finalOutput = stripAnsiEscapeCodes(output) 151 } else { 152 finalOutput = output 153 } 154 155 // TODO(maruel): Use an existing Go package. 156 // Fix extra CR being added on Windows, writing out CR CR LF (#773) 157 //Setmode(Fileno(stdout), _O_BINARY) // Begin Windows extra CR fix 158 159 s.printer.PrintOnNewLine(finalOutput) 160 161 //Setmode(Fileno(stdout), _O_TEXT) // End Windows extra CR fix 162 163 } 164 } 165 166 func (s *statusPrinter) BuildLoadDyndeps() { 167 // The DependencyScan calls Explain() to print lines explaining why 168 // it considers a portion of the graph to be out of date. Normally 169 // this is done before the build starts, but our caller is about to 170 // load a dyndep file during the build. Doing so may generate more 171 // explanation lines (via fprintf directly to stderr), but in an 172 // interactive console the cursor is currently at the end of a status 173 // line. Start a new line so that the first explanation does not 174 // append to the status line. After the explanations are done a 175 // new build status line will appear. 176 if nin.Debug.Explaining { 177 s.printer.PrintOnNewLine("") 178 } 179 } 180 181 func (s *statusPrinter) BuildStarted() { 182 s.startedEdges = 0 183 s.finishedEdges = 0 184 s.runningEdges = 0 185 } 186 187 func (s *statusPrinter) BuildFinished() { 188 s.printer.SetConsoleLocked(false) 189 s.printer.PrintOnNewLine("") 190 } 191 192 // Format the progress status string by replacing the placeholders. 193 // See the user manual for more information about the available 194 // placeholders. 195 // @param progressStatusFormat The format of the progress status. 196 // @param status The status of the edge. 197 func (s *statusPrinter) formatProgressStatus(progressStatusFormat string, timeMillis int32) string { 198 out := "" 199 // TODO(maruel): Benchmark to optimize memory usage and performance 200 // especially when GC is disabled. 201 for i := 0; i < len(progressStatusFormat); i++ { 202 c := progressStatusFormat[i] 203 if c == '%' { 204 i++ 205 c = progressStatusFormat[i] 206 switch c { 207 case '%': 208 out += "%" 209 210 // Started edges. 211 case 's': 212 out += strconv.Itoa(s.startedEdges) 213 214 // Total edges. 215 case 't': 216 out += strconv.Itoa(s.totalEdges) 217 218 // Running edges. 219 case 'r': 220 out += strconv.Itoa(s.runningEdges) 221 222 // Unstarted edges. 223 case 'u': 224 out += strconv.Itoa(s.totalEdges - s.startedEdges) 225 226 // Finished edges. 227 case 'f': 228 out += strconv.Itoa(s.finishedEdges) 229 230 // Overall finished edges per second. 231 case 'o': 232 rate := float64(s.finishedEdges) / float64(s.timeMillis) * 1000. 233 if rate == -1 { 234 out += "?" 235 } else { 236 out += fmt.Sprintf("%.1f", rate) 237 } 238 239 // Current rate, average over the last '-j' jobs. 240 case 'c': 241 s.currentRate.updateRate(s.finishedEdges, s.timeMillis) 242 rate := s.currentRate.rate 243 if rate == -1 { 244 out += "?" 245 } else { 246 out += fmt.Sprintf("%.1f", rate) 247 } 248 249 // Percentage 250 case 'p': 251 percent := (100 * s.finishedEdges) / s.totalEdges 252 out += fmt.Sprintf("%3d%%", percent) 253 254 case 'e': 255 out += fmt.Sprintf("%.3f", float64(s.timeMillis)*0.001) 256 257 default: 258 fatalf("unknown placeholder '%%%c' in $NINJA_STATUS", c) 259 return "" 260 } 261 } else { 262 out += string(c) 263 } 264 } 265 return out 266 } 267 268 func (s *statusPrinter) PrintStatus(edge *nin.Edge, timeMillis int32) { 269 if s.config.Verbosity == nin.Quiet || s.config.Verbosity == nin.NoStatusUpdate { 270 return 271 } 272 273 forceFullCommand := s.config.Verbosity == nin.Verbose 274 275 toPrint := edge.GetBinding("description") 276 if toPrint == "" || forceFullCommand { 277 toPrint = edge.GetBinding("command") 278 } 279 280 toPrint = s.formatProgressStatus(s.progressStatusFormat, timeMillis) + toPrint 281 s.printer.Print(toPrint, !forceFullCommand) 282 } 283 284 func (s *statusPrinter) Warning(msg string, i ...interface{}) { 285 warningf(msg, i...) 286 } 287 288 func (s *statusPrinter) Error(msg string, i ...interface{}) { 289 errorf(msg, i...) 290 } 291 292 func (s *statusPrinter) Info(msg string, i ...interface{}) { 293 infof(msg, i...) 294 }