Skip to content

Commit

Permalink
Allow output to be modified with -O
Browse files Browse the repository at this point in the history
Split the "smart terminal" configuration into three separate settings:
color, elide, and reprint.  Allow setting these options individually or
in bulk through a new -O option.

To have build output into a dumb terminal still include ANSI color
codes, for example if you wanted to pass continuous build output through
ansi2html, use:
   ninja -O color

To force smart terminal output, use:
   ninja -O smart

To force smart terminal output, but still print full status
descriptions:
   ninja -O smart -O noelide

Addresses some of the issues in ninja-build#746 while allowing for adding more
output options in the future.
  • Loading branch information
colincross committed Sep 23, 2015
1 parent a484344 commit 006546a
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 19 deletions.
6 changes: 3 additions & 3 deletions src/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ BuildStatus::BuildStatus(const BuildConfig& config)
: config_(config),
start_time_millis_(GetTimeMillis()),
started_edges_(0), finished_edges_(0), total_edges_(0),
printer_(config.printer),
progress_status_format_(NULL),
overall_rate_(), current_rate_(config.parallelism) {

Expand Down Expand Up @@ -121,7 +122,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
if (config_.verbosity == BuildConfig::QUIET)
return;

if (!edge->use_console() && printer_.is_smart_terminal())
if (!edge->use_console() && printer_.reprint())
PrintStatus(edge);

// Print the command that is spewing before printing its output.
Expand All @@ -140,9 +141,8 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
// (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.)
// TODO: There should be a flag to disable escape code stripping.
string final_output;
if (!printer_.is_smart_terminal())
if (!printer_.color())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;
Expand Down
6 changes: 4 additions & 2 deletions src/build.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ struct CommandRunner {

/// Options (e.g. verbosity, parallelism) passed to a build.
struct BuildConfig {
BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1),
BuildConfig() : verbosity(NORMAL), printer(LinePrinter()),
dry_run(false), parallelism(1),
failures_allowed(1), max_load_average(-0.0f) {}

enum Verbosity {
Expand All @@ -128,6 +129,7 @@ struct BuildConfig {
VERBOSE
};
Verbosity verbosity;
mutable LinePrinter printer;
bool dry_run;
int parallelism;
int failures_allowed;
Expand Down Expand Up @@ -219,7 +221,7 @@ struct BuildStatus {
RunningEdgeMap running_edges_;

/// Prints progress output.
LinePrinter printer_;
LinePrinter& printer_;

/// The custom progress status format to use.
const char* progress_status_format_;
Expand Down
19 changes: 11 additions & 8 deletions src/line_printer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
#ifndef _WIN32
const char* term = getenv("TERM");
smart_terminal_ = isatty(1) && term && string(term) != "dumb";
color_ = reprint_ = elide_ = isatty(1) && term && string(term) != "dumb";
#else
// Disable output buffer. It'd be nice to use line buffering but
// MSDN says: "For some systems, [_IOLBF] provides line
Expand All @@ -39,7 +39,7 @@ LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
setvbuf(stdout, NULL, _IONBF, 0);
console_ = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
color_ = reprint_ = elide = GetConsoleScreenBufferInfo(console_, &csbi);
#endif
}

Expand All @@ -50,13 +50,13 @@ void LinePrinter::Print(string to_print, LineType type) {
return;
}

if (smart_terminal_) {
if (reprint_) {
printf("\r"); // Print over previous line, if any.
// On Windows, calling a C library function writing to stdout also handles
// pausing the executable when the "Pause" key or Ctrl-S is pressed.
}

if (smart_terminal_ && type == ELIDE) {
if (elide_ && type == ELIDE) {
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(console_, &csbi);
Expand Down Expand Up @@ -86,11 +86,14 @@ void LinePrinter::Print(string to_print, LineType type) {
to_print = ElideMiddle(to_print, size.ws_col);
}
printf("%s", to_print.c_str());
printf("\x1B[K"); // Clear to end of line.
fflush(stdout);
if (reprint_) {
printf("\x1B[K"); // Clear to end of line.
fflush(stdout);
have_blank_line_ = false;
} else {
printf("\n");
}
#endif

have_blank_line_ = false;
} else {
printf("%s\n", to_print.c_str());
}
Expand Down
22 changes: 18 additions & 4 deletions src/line_printer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ using namespace std;
struct LinePrinter {
LinePrinter();

bool is_smart_terminal() const { return smart_terminal_; }
void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
void set_smart_terminal(bool smart) { color_ = elide_ = reprint_ = smart; }

bool color() const { return color_; }
void set_color(bool color) { color_ = color; }

bool elide() const { return elide_; }
void set_elide(bool elide) { elide_ = elide; }

bool reprint() const { return reprint_; }
void set_reprint(bool reprint) { reprint_ = reprint; }

enum LineType {
FULL,
Expand All @@ -43,8 +51,14 @@ struct LinePrinter {
void SetConsoleLocked(bool locked);

private:
/// Whether we can do fancy terminal control codes.
bool smart_terminal_;
/// Whether we can do ANSI color codes.
bool color_;

/// Whether we can do \r to print over the same line.
bool reprint_;

/// Whether we get the size of the terminal to stay within.
bool elide_;

/// Whether the caret is at the beginning of a blank line.
bool have_blank_line_;
Expand Down
52 changes: 50 additions & 2 deletions src/ninja.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ void Usage(const BuildConfig& config) {
" -d MODE enable debugging (use -d list to list modes)\n"
" -t TOOL run a subtool (use -t list to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
" -w FLAG adjust warnings (use -w list to list warnings)\n",
" -w FLAG adjust warnings (use -w list to list warnings)\n"
" -O flag adjust output (use -O list to list output flags)\n",
kNinjaVersion, config.parallelism);
}

Expand Down Expand Up @@ -805,6 +806,49 @@ bool DebugEnable(const string& name) {
}
}

bool OutputConfigure(const string& name, LinePrinter& printer) {
if (name == "list") {
printf("output flags:\n"
" smart force all flags on\n"
" dumb force all flags off\n"
"prefix the following flags with 'no' to disable them\n"
"default value is on for smart terminals, off for dumb terminals\n"
" color pass through ANSI color codes from command output\n"
" elide shorten command status lines to avoid line wrapping\n"
" reprint print command statuses on the same line\n"
"multiple flags can be modified with -O FOO -O BAR\n");
return false;
} else if (name == "smart") {
printer.set_smart_terminal(true);
} else if (name == "dumb") {
printer.set_smart_terminal(false);
} else if (name == "color") {
printer.set_color(true);
} else if (name == "nocolor") {
printer.set_color(false);
} else if (name == "elide") {
printer.set_elide(true);
} else if (name == "noelide") {
printer.set_elide(false);
} else if (name == "reprint") {
printer.set_reprint(true);
} else if (name == "noreprint") {
printer.set_reprint(false);
} else {
const char* suggestion =
SpellcheckString(name.c_str(), "smart", "dumb", "color", "nocolor",
"elide", "noelide", "reprint", "noreprint", NULL);
if (suggestion) {
Error("unknown output flag '%s', did you mean '%s'?",
name.c_str(), suggestion);
} else {
Error("unknown output flag '%s'", name.c_str());
}
return false;
}
return true;
}

/// Set a warning flag. Returns false if Ninja should exit instead of
/// continuing.
bool WarningEnable(const string& name, Options* options) {
Expand Down Expand Up @@ -1001,7 +1045,7 @@ int ReadFlags(int* argc, char*** argv,

int opt;
while (!options->tool &&
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions,
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:O:h", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
Expand Down Expand Up @@ -1057,6 +1101,10 @@ int ReadFlags(int* argc, char*** argv,
case 'C':
options->working_dir = optarg;
break;
case 'O':
if (!OutputConfigure(optarg, config->printer))
return 1;
break;
case OPT_VERSION:
printf("%s\n", kNinjaVersion);
return 0;
Expand Down

0 comments on commit 006546a

Please sign in to comment.