From a2c4b6780dcf105821e4f2ec1f0b591adbeb6dca Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Wed, 18 Jan 2012 16:09:16 -0800 Subject: Strip ansi escape sequences from subcommand output when not writing to a smart terminal. --- src/build.cc | 22 ++++++++++++++++++++-- src/util.cc | 28 ++++++++++++++++++++++++++++ src/util.h | 3 +++ src/util_test.cc | 17 +++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/build.cc b/src/build.cc index 94c9d77..8d0abce 100644 --- a/src/build.cc +++ b/src/build.cc @@ -133,8 +133,26 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (!success) printf("FAILED: %s\n", edge->EvaluateCommand().c_str()); - if (!output.empty()) - printf("%s", output.c_str()); + // 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.) + // TODO: There should be a flag to disable escape code stripping. + string final_output; + if (!smart_terminal_) + final_output = StripAnsiEscapeCodes(output); + else + final_output = output; + + if (!final_output.empty()) + printf("%s", final_output.c_str()); } } diff --git a/src/util.cc b/src/util.cc index dbf7566..5f5d8dc 100644 --- a/src/util.cc +++ b/src/util.cc @@ -255,3 +255,31 @@ string GetLastErrorString() { return msg; } #endif + +static bool islatinalpha(int c) { + // isalpha() is locale-dependent. + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +string StripAnsiEscapeCodes(const string& in) { + string stripped; + stripped.reserve(in.size()); + + for (size_t i = 0; i < in.size(); ++i) { + if (in[i] != '\33') { + // Not an escape code. + stripped.push_back(in[i]); + continue; + } + + // Only strip CSIs for now. + if (i + 1 >= in.size()) break; + if (in[i + 1] != '[') continue; // Not a CSI. + i += 2; + + // Skip everything up to and including the next [a-zA-Z]. + while (i < in.size() && !islatinalpha(in[i])) + ++i; + } + return stripped; +} diff --git a/src/util.h b/src/util.h index 0ae2743..711d476 100644 --- a/src/util.h +++ b/src/util.h @@ -65,6 +65,9 @@ const char* SpellcheckStringV(const string& text, const vector& wor /// Like SpellcheckStringV, but takes a NULL-terminated list. const char* SpellcheckString(const string& text, ...); +/// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm). +string StripAnsiEscapeCodes(const string& in); + #ifdef _WIN32 #define snprintf _snprintf #define fileno _fileno diff --git a/src/util_test.cc b/src/util_test.cc index 8c3d023..c44ab77 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -73,3 +73,20 @@ TEST(CanonicalizePath, AbsolutePath) { EXPECT_TRUE(CanonicalizePath(&path, &err)); EXPECT_EQ("/usr/include/stdio.h", path); } + +TEST(StripAnsiEscapeCodes, EscapeAtEnd) { + string stripped = StripAnsiEscapeCodes("foo\33"); + EXPECT_EQ("foo", stripped); + + stripped = StripAnsiEscapeCodes("foo\33["); + EXPECT_EQ("foo", stripped); +} + +TEST(StripAnsiEscapeCodes, StripColors) { + // An actual clang warning. + string input = "\33[1maffixmgr.cxx:286:15: \33[0m\33[0;1;35mwarning: " + "\33[0m\33[1musing the result... [-Wparentheses]\33[0m"; + string stripped = StripAnsiEscapeCodes(input); + EXPECT_EQ("affixmgr.cxx:286:15: warning: using the result... [-Wparentheses]", + stripped); +} -- cgit v0.12