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  }