github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/src/status.cc (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  #include "status.h"
    16  
    17  #include <stdarg.h>
    18  #include <stdlib.h>
    19  
    20  #ifdef _WIN32
    21  #include <fcntl.h>
    22  #include <io.h>
    23  #endif
    24  
    25  #include "debug_flags.h"
    26  
    27  using namespace std;
    28  
    29  StatusPrinter::StatusPrinter(const BuildConfig& config)
    30      : config_(config),
    31        started_edges_(0), finished_edges_(0), total_edges_(0), running_edges_(0),
    32        time_millis_(0), progress_status_format_(NULL),
    33        current_rate_(config.parallelism) {
    34  
    35    // Don't do anything fancy in verbose mode.
    36    if (config_.verbosity != BuildConfig::NORMAL)
    37      printer_.set_smart_terminal(false);
    38  
    39    progress_status_format_ = getenv("NINJA_STATUS");
    40    if (!progress_status_format_)
    41      progress_status_format_ = "[%f/%t] ";
    42  }
    43  
    44  void StatusPrinter::PlanHasTotalEdges(int total) {
    45    total_edges_ = total;
    46  }
    47  
    48  void StatusPrinter::BuildEdgeStarted(const Edge* edge,
    49                                       int64_t start_time_millis) {
    50    ++started_edges_;
    51    ++running_edges_;
    52    time_millis_ = start_time_millis;
    53  
    54    if (edge->use_console() || printer_.is_smart_terminal())
    55      PrintStatus(edge, start_time_millis);
    56  
    57    if (edge->use_console())
    58      printer_.SetConsoleLocked(true);
    59  }
    60  
    61  void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
    62                                        bool success, const string& output) {
    63    time_millis_ = end_time_millis;
    64    ++finished_edges_;
    65  
    66    if (edge->use_console())
    67      printer_.SetConsoleLocked(false);
    68  
    69    if (config_.verbosity == BuildConfig::QUIET)
    70      return;
    71  
    72    if (!edge->use_console())
    73      PrintStatus(edge, end_time_millis);
    74  
    75    --running_edges_;
    76  
    77    // Print the command that is spewing before printing its output.
    78    if (!success) {
    79      string outputs;
    80      for (vector<Node*>::const_iterator o = edge->outputs_.begin();
    81           o != edge->outputs_.end(); ++o)
    82        outputs += (*o)->path() + " ";
    83  
    84      if (printer_.supports_color()) {
    85          printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
    86      } else {
    87          printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
    88      }
    89      printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
    90    }
    91  
    92    if (!output.empty()) {
    93      // ninja sets stdout and stderr of subprocesses to a pipe, to be able to
    94      // check if the output is empty. Some compilers, e.g. clang, check
    95      // isatty(stderr) to decide if they should print colored output.
    96      // To make it possible to use colored output with ninja, subprocesses should
    97      // be run with a flag that forces them to always print color escape codes.
    98      // To make sure these escape codes don't show up in a file if ninja's output
    99      // is piped to a file, ninja strips ansi escape codes again if it's not
   100      // writing to a |smart_terminal_|.
   101      // (Launching subprocesses in pseudo ttys doesn't work because there are
   102      // only a few hundred available on some systems, and ninja can launch
   103      // thousands of parallel compile commands.)
   104      string final_output;
   105      if (!printer_.supports_color())
   106        final_output = StripAnsiEscapeCodes(output);
   107      else
   108        final_output = output;
   109  
   110  #ifdef _WIN32
   111      // Fix extra CR being added on Windows, writing out CR CR LF (#773)
   112      _setmode(_fileno(stdout), _O_BINARY);  // Begin Windows extra CR fix
   113  #endif
   114  
   115      printer_.PrintOnNewLine(final_output);
   116  
   117  #ifdef _WIN32
   118      _setmode(_fileno(stdout), _O_TEXT);  // End Windows extra CR fix
   119  #endif
   120    }
   121  }
   122  
   123  void StatusPrinter::BuildLoadDyndeps() {
   124    // The DependencyScan calls EXPLAIN() to print lines explaining why
   125    // it considers a portion of the graph to be out of date.  Normally
   126    // this is done before the build starts, but our caller is about to
   127    // load a dyndep file during the build.  Doing so may generate more
   128    // explanation lines (via fprintf directly to stderr), but in an
   129    // interactive console the cursor is currently at the end of a status
   130    // line.  Start a new line so that the first explanation does not
   131    // append to the status line.  After the explanations are done a
   132    // new build status line will appear.
   133    if (g_explaining)
   134      printer_.PrintOnNewLine("");
   135  }
   136  
   137  void StatusPrinter::BuildStarted() {
   138    started_edges_ = 0;
   139    finished_edges_ = 0;
   140    running_edges_ = 0;
   141  }
   142  
   143  void StatusPrinter::BuildFinished() {
   144    printer_.SetConsoleLocked(false);
   145    printer_.PrintOnNewLine("");
   146  }
   147  
   148  string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
   149                                             int64_t time_millis) const {
   150    string out;
   151    char buf[32];
   152    for (const char* s = progress_status_format; *s != '\0'; ++s) {
   153      if (*s == '%') {
   154        ++s;
   155        switch (*s) {
   156        case '%':
   157          out.push_back('%');
   158          break;
   159  
   160          // Started edges.
   161        case 's':
   162          snprintf(buf, sizeof(buf), "%d", started_edges_);
   163          out += buf;
   164          break;
   165  
   166          // Total edges.
   167        case 't':
   168          snprintf(buf, sizeof(buf), "%d", total_edges_);
   169          out += buf;
   170          break;
   171  
   172          // Running edges.
   173        case 'r': {
   174          snprintf(buf, sizeof(buf), "%d", running_edges_);
   175          out += buf;
   176          break;
   177        }
   178  
   179          // Unstarted edges.
   180        case 'u':
   181          snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
   182          out += buf;
   183          break;
   184  
   185          // Finished edges.
   186        case 'f':
   187          snprintf(buf, sizeof(buf), "%d", finished_edges_);
   188          out += buf;
   189          break;
   190  
   191          // Overall finished edges per second.
   192        case 'o':
   193          SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
   194          out += buf;
   195          break;
   196  
   197          // Current rate, average over the last '-j' jobs.
   198        case 'c':
   199          current_rate_.UpdateRate(finished_edges_, time_millis_);
   200          SnprintfRate(current_rate_.rate(), buf, "%.1f");
   201          out += buf;
   202          break;
   203  
   204          // Percentage
   205        case 'p': {
   206          int percent = (100 * finished_edges_) / total_edges_;
   207          snprintf(buf, sizeof(buf), "%3i%%", percent);
   208          out += buf;
   209          break;
   210        }
   211  
   212        case 'e': {
   213          snprintf(buf, sizeof(buf), "%.3f", time_millis_ / 1e3);
   214          out += buf;
   215          break;
   216        }
   217  
   218        default:
   219          Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
   220          return "";
   221        }
   222      } else {
   223        out.push_back(*s);
   224      }
   225    }
   226  
   227    return out;
   228  }
   229  
   230  void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
   231    if (config_.verbosity == BuildConfig::QUIET
   232        || config_.verbosity == BuildConfig::NO_STATUS_UPDATE)
   233      return;
   234  
   235    bool force_full_command = config_.verbosity == BuildConfig::VERBOSE;
   236  
   237    string to_print = edge->GetBinding("description");
   238    if (to_print.empty() || force_full_command)
   239      to_print = edge->GetBinding("command");
   240  
   241    to_print = FormatProgressStatus(progress_status_format_, time_millis)
   242        + to_print;
   243  
   244    printer_.Print(to_print,
   245                   force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
   246  }
   247  
   248  void StatusPrinter::Warning(const char* msg, ...) {
   249    va_list ap;
   250    va_start(ap, msg);
   251    ::Warning(msg, ap);
   252    va_end(ap);
   253  }
   254  
   255  void StatusPrinter::Error(const char* msg, ...) {
   256    va_list ap;
   257    va_start(ap, msg);
   258    ::Error(msg, ap);
   259    va_end(ap);
   260  }
   261  
   262  void StatusPrinter::Info(const char* msg, ...) {
   263    va_list ap;
   264    va_start(ap, msg);
   265    ::Info(msg, ap);
   266    va_end(ap);
   267  }