Skip to content

Commit

Permalink
Turn BuildStatus into an interface
Browse files Browse the repository at this point in the history
Make BuildStatus an abstract interface, and move the current
implementation to StatusPrinter, to make way for a serialized
Status output.
  • Loading branch information
colincross authored and Eli Ribble committed Aug 27, 2019
1 parent d602fab commit 3cc514b
Show file tree
Hide file tree
Showing 8 changed files with 426 additions and 317 deletions.
2 changes: 2 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ def has_re2c():
'metrics',
'parser',
'state',
'status',
'string_piece_util',
'util',
'version']:
Expand Down Expand Up @@ -579,6 +580,7 @@ def has_re2c():
'manifest_parser_test',
'ninja_test',
'state_test',
'status_test',
'string_piece_util_test',
'subprocess_test',
'test',
Expand Down
226 changes: 4 additions & 222 deletions src/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "graph.h"
#include "metrics.h"
#include "state.h"
#include "status.h"
#include "subprocess.h"
#include "util.h"

Expand Down Expand Up @@ -77,225 +78,6 @@ bool DryRunCommandRunner::WaitForCommand(Result* result) {

} // namespace

BuildStatus::BuildStatus(const BuildConfig& config)
: config_(config),
started_edges_(0), finished_edges_(0), total_edges_(0), running_edges_(0),
time_millis_(0), progress_status_format_(NULL),
current_rate_(config.parallelism) {

// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
printer_.set_smart_terminal(false);

progress_status_format_ = getenv("NINJA_STATUS");
if (!progress_status_format_)
progress_status_format_ = "[%f/%t] ";
}

void BuildStatus::PlanHasTotalEdges(int total) {
total_edges_ = total;
}

void BuildStatus::BuildEdgeStarted(Edge* edge, int64_t start_time_millis) {
++started_edges_;
time_millis_ = start_time_millis;

if (edge->use_console() || printer_.is_smart_terminal())
PrintStatus(edge, start_time_millis);

if (edge->use_console())
printer_.SetConsoleLocked(true);
}

void BuildStatus::BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
bool success,
const string& output) {
time_millis_ = end_time_millis;
++finished_edges_;

if (edge->use_console())
printer_.SetConsoleLocked(false);

if (config_.verbosity == BuildConfig::QUIET)
return;

if (!edge->use_console())
PrintStatus(edge, end_time_millis);

// Update running_edges_ after PrintStatus so that the number of running
// edges doesn't oscillate between config.parallelism_ and
// config.parallelism_ - 1.
--running_edges_;

// Print the command that is spewing before printing its output.
if (!success) {
string outputs;
for (vector<Node*>::const_iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o)
outputs += (*o)->path() + " ";

if (printer_.supports_color()) {
printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
} else {
printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
}
printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
}

if (!output.empty()) {
// ninja sets stdout and stderr of subprocesses to a pipe, to be able to
// check if the output is empty. Some compilers, e.g. clang, check
// isatty(stderr) to decide if they should print colored output.
// To make it possible to use colored output with ninja, subprocesses should
// be run with a flag that forces them to always print color escape codes.
// To make sure these escape codes don't show up in a file if ninja's output
// is piped to a file, ninja strips ansi escape codes again if it's not
// writing to a |smart_terminal_|.
// (Launching subprocesses in pseudo ttys doesn't work because there are
// only a few hundred available on some systems, and ninja can launch
// thousands of parallel compile commands.)
string final_output;
if (!printer_.supports_color())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;

#ifdef _WIN32
// Fix extra CR being added on Windows, writing out CR CR LF (#773)
_setmode(_fileno(stdout), _O_BINARY); // Begin Windows extra CR fix
#endif

printer_.PrintOnNewLine(final_output);

#ifdef _WIN32
_setmode(_fileno(stdout), _O_TEXT); // End Windows extra CR fix
#endif
}
}

void BuildStatus::BuildLoadDyndeps() {
// The DependencyScan calls EXPLAIN() to print lines explaining why
// it considers a portion of the graph to be out of date. Normally
// this is done before the build starts, but our caller is about to
// load a dyndep file during the build. Doing so may generate more
// exlanation lines (via fprintf directly to stderr), but in an
// interactive console the cursor is currently at the end of a status
// line. Start a new line so that the first explanation does not
// append to the status line. After the explanations are done a
// new build status line will appear.
if (g_explaining)
printer_.PrintOnNewLine("");
}

void BuildStatus::BuildStarted() {
started_edges_ = 0;
finished_edges_ = 0;
running_edges_ = 0;
}

void BuildStatus::BuildFinished() {
printer_.SetConsoleLocked(false);
printer_.PrintOnNewLine("");
}

string BuildStatus::FormatProgressStatus(
const char* progress_status_format, int64_t time) const {
string out;
char buf[32];
for (const char* s = progress_status_format; *s != '\0'; ++s) {
if (*s == '%') {
++s;
switch (*s) {
case '%':
out.push_back('%');
break;

// Started edges.
case 's':
snprintf(buf, sizeof(buf), "%d", started_edges_);
out += buf;
break;

// Total edges.
case 't':
snprintf(buf, sizeof(buf), "%d", total_edges_);
out += buf;
break;

// Running edges.
case 'r': {
snprintf(buf, sizeof(buf), "%d", running_edges_);
out += buf;
break;
}

// Unstarted edges.
case 'u':
snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
out += buf;
break;

// Finished edges.
case 'f':
snprintf(buf, sizeof(buf), "%d", finished_edges_);
out += buf;
break;

// Overall finished edges per second.
case 'o':
SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
out += buf;
break;

// Current rate, average over the last '-j' jobs.
case 'c':
current_rate_.UpdateRate(finished_edges_, time_millis_);
SnprintfRate(current_rate_.rate(), buf, "%.1f");
out += buf;
break;

// Percentage
case 'p': {
int percent = (100 * finished_edges_) / total_edges_;
snprintf(buf, sizeof(buf), "%3i%%", percent);
out += buf;
break;
}

case 'e': {
snprintf(buf, sizeof(buf), "%.3f", time_millis_ / 1e3);
out += buf;
break;
}

default:
Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
return "";
}
} else {
out.push_back(*s);
}
}

return out;
}

void BuildStatus::PrintStatus(Edge* edge, int64_t time) {
if (config_.verbosity == BuildConfig::QUIET)
return;

bool force_full_command = config_.verbosity == BuildConfig::VERBOSE;

string to_print = edge->GetBinding("description");
if (to_print.empty() || force_full_command)
to_print = edge->GetBinding("command");

to_print = FormatProgressStatus(progress_status_format_, time) + to_print;

printer_.Print(to_print,
force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
}

Plan::Plan(Builder* builder)
: builder_(builder)
, command_edges_(0)
Expand Down Expand Up @@ -720,12 +502,12 @@ bool RealCommandRunner::WaitForCommand(Result* result) {

Builder::Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
DiskInterface* disk_interface, int64_t start_time_millis)
: state_(state), config_(config), plan_(this),
DiskInterface* disk_interface, Status* status,
int64_t start_time_millis)
: state_(state), config_(config), plan_(this), status_(status),
start_time_millis_(start_time_millis), disk_interface_(disk_interface),
scan_(state, build_log, deps_log, disk_interface,
&config_.depfile_parser_options) {
status_ = new BuildStatus(config);
}

Builder::~Builder() {
Expand Down
76 changes: 4 additions & 72 deletions src/build.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@
#include "depfile_parser.h"
#include "graph.h" // XXX needed for DependencyScan; should rearrange.
#include "exit_status.h"
#include "line_printer.h"
#include "util.h" // int64_t

struct BuildLog;
struct BuildStatus;
struct Builder;
struct DiskInterface;
struct Edge;
struct Node;
struct State;
struct Status;

/// Plan stores the state of a build plan: what we intend to build,
/// which steps we're ready to execute.
Expand Down Expand Up @@ -178,7 +177,8 @@ struct BuildConfig {
struct Builder {
Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
DiskInterface* disk_interface, int64_t start_time_millis);
DiskInterface* disk_interface, Status* status,
int64_t start_time_millis);
~Builder();

/// Clean up after interrupted commands by deleting output files.
Expand Down Expand Up @@ -219,7 +219,7 @@ struct Builder {
#else
unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
#endif
BuildStatus* status_;
Status* status_;

private:
bool ExtractDeps(CommandRunner::Result* result, const string& deps_type,
Expand All @@ -241,72 +241,4 @@ struct Builder {
void operator=(const Builder &other); // DO NOT IMPLEMENT
};

/// Tracks the status of a build: completion fraction, printing updates.
struct BuildStatus {
explicit BuildStatus(const BuildConfig& config);
void PlanHasTotalEdges(int total);
void BuildEdgeStarted(Edge* edge, int64_t start_time_millis);
void BuildEdgeFinished(Edge* edge, int64_t end_time_millis, bool success,
const string& output);
void BuildLoadDyndeps();
void BuildStarted();
void BuildFinished();

/// Format the progress status string by replacing the placeholders.
/// See the user manual for more information about the available
/// placeholders.
/// @param progress_status_format The format of the progress status.
/// @param status The status of the edge.
string FormatProgressStatus(const char* progress_status_format,
int64_t time) const;

private:
void PrintStatus(Edge* edge, int64_t time);

const BuildConfig& config_;

int started_edges_, finished_edges_, total_edges_, running_edges_;
int64_t time_millis_;

/// Prints progress output.
LinePrinter printer_;

/// The custom progress status format to use.
const char* progress_status_format_;

template<size_t S>
void SnprintfRate(double rate, char(&buf)[S], const char* format) const {
if (rate == -1)
snprintf(buf, S, "?");
else
snprintf(buf, S, format, rate);
}

struct SlidingRateInfo {
SlidingRateInfo(int n) : rate_(-1), N(n), last_update_(-1) {}

double rate() { return rate_; }

void UpdateRate(int update_hint, int64_t time_millis_) {
if (update_hint == last_update_)
return;
last_update_ = update_hint;

if (times_.size() == N)
times_.pop();
times_.push(time_millis_);
if (times_.back() != times_.front())
rate_ = times_.size() / ((times_.back() - times_.front()) / 1e3);
}

private:
double rate_;
const size_t N;
queue<double> times_;
int last_update_;
};

mutable SlidingRateInfo current_rate_;
};

#endif // NINJA_BUILD_H_
Loading

0 comments on commit 3cc514b

Please sign in to comment.