summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2012-01-19 00:09:16 (GMT)
committerNico Weber <thakis@chromium.org>2012-01-19 00:09:16 (GMT)
commita2c4b6780dcf105821e4f2ec1f0b591adbeb6dca (patch)
treea62348131d053354b7d8d5fb1c3ca5b3d5527d67
parent63013b3bd5ca79d454a95a6f985d98f16f21da1c (diff)
downloadNinja-a2c4b6780dcf105821e4f2ec1f0b591adbeb6dca.zip
Ninja-a2c4b6780dcf105821e4f2ec1f0b591adbeb6dca.tar.gz
Ninja-a2c4b6780dcf105821e4f2ec1f0b591adbeb6dca.tar.bz2
Strip ansi escape sequences from subcommand output when not writing to a smart terminal.
-rw-r--r--src/build.cc22
-rw-r--r--src/util.cc28
-rw-r--r--src/util.h3
-rw-r--r--src/util_test.cc17
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<const char*>& 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);
+}