summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconfigure.py5
-rw-r--r--src/build.cc111
-rw-r--r--src/build.h7
-rw-r--r--src/line_printer.cc107
-rw-r--r--src/line_printer.h48
-rw-r--r--src/ninja_test.cc76
6 files changed, 248 insertions, 106 deletions
diff --git a/configure.py b/configure.py
index eaf67c7..dd26906 100755
--- a/configure.py
+++ b/configure.py
@@ -271,6 +271,7 @@ for name in ['build',
'graph',
'graphviz',
'lexer',
+ 'line_printer',
'manifest_parser',
'metrics',
'state',
@@ -326,9 +327,6 @@ if options.with_gtest:
objs += n.build(built('gtest-all' + objext), 'cxx',
os.path.join(path, 'src', 'gtest-all.cc'),
variables=[('cflags', gtest_cflags)])
- objs += n.build(built('gtest_main' + objext), 'cxx',
- os.path.join(path, 'src', 'gtest_main.cc'),
- variables=[('cflags', gtest_cflags)])
test_cflags.append('-I%s' % os.path.join(path, 'include'))
else:
@@ -349,6 +347,7 @@ for name in ['build_log_test',
'graph_test',
'lexer_test',
'manifest_parser_test',
+ 'ninja_test',
'state_test',
'subprocess_test',
'test',
diff --git a/src/build.cc b/src/build.cc
index 7de4b16..ab3d781 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -20,14 +20,6 @@
#include <stdlib.h>
#include <functional>
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-#endif
-
#if defined(__SVR4) && defined(__sun)
#include <sys/termios.h>
#endif
@@ -82,25 +74,12 @@ BuildStatus::BuildStatus(const BuildConfig& config)
: config_(config),
start_time_millis_(GetTimeMillis()),
started_edges_(0), finished_edges_(0), total_edges_(0),
- have_blank_line_(true), progress_status_format_(NULL),
+ progress_status_format_(NULL),
overall_rate_(), current_rate_(config.parallelism) {
-#ifndef _WIN32
- const char* term = getenv("TERM");
- smart_terminal_ = 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
- // buffering. However, for Win32, the behavior is the same as _IOFBF
- // - Full Buffering."
- setvbuf(stdout, NULL, _IONBF, 0);
- console_ = GetStdHandle(STD_OUTPUT_HANDLE);
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
-#endif
// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
- smart_terminal_ = false;
+ printer_.set_smart_terminal(false);
progress_status_format_ = getenv("NINJA_STATUS");
if (!progress_status_format_)
@@ -135,17 +114,14 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
if (config_.verbosity == BuildConfig::QUIET)
return;
- if (smart_terminal_)
+ if (printer_.is_smart_terminal())
PrintStatus(edge);
- if (!success || !output.empty()) {
- if (smart_terminal_)
- printf("\n");
-
- // Print the command that is spewing before printing its output.
- if (!success)
- printf("FAILED: %s\n", edge->EvaluateCommand().c_str());
+ // Print the command that is spewing before printing its output.
+ if (!success)
+ printer_.PrintOnNewLine("FAILED: " + 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.
@@ -159,21 +135,16 @@ void BuildStatus::BuildEdgeFinished(Edge* edge,
// thousands of parallel compile commands.)
// TODO: There should be a flag to disable escape code stripping.
string final_output;
- if (!smart_terminal_)
+ if (!printer_.is_smart_terminal())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;
-
- if (!final_output.empty())
- printf("%s", final_output.c_str());
-
- have_blank_line_ = true;
+ printer_.PrintOnNewLine(final_output);
}
}
void BuildStatus::BuildFinished() {
- if (smart_terminal_ && !have_blank_line_)
- printf("\n");
+ printer_.PrintOnNewLine("");
}
string BuildStatus::FormatProgressStatus(
@@ -269,72 +240,14 @@ void BuildStatus::PrintStatus(Edge* edge) {
if (to_print.empty() || force_full_command)
to_print = edge->GetBinding("command");
-#ifdef _WIN32
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- GetConsoleScreenBufferInfo(console_, &csbi);
-#endif
-
- if (smart_terminal_) {
-#ifndef _WIN32
- printf("\r"); // Print over previous line, if any.
-#else
- csbi.dwCursorPosition.X = 0;
- SetConsoleCursorPosition(console_, csbi.dwCursorPosition);
-#endif
- }
-
if (finished_edges_ == 0) {
overall_rate_.Restart();
current_rate_.Restart();
}
to_print = FormatProgressStatus(progress_status_format_) + to_print;
- if (smart_terminal_ && !force_full_command) {
-#ifndef _WIN32
- // Limit output to width of the terminal if provided so we don't cause
- // line-wrapping.
- winsize size;
- if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) {
- to_print = ElideMiddle(to_print, size.ws_col);
- }
-#else
- // Don't use the full width or console will move to next line.
- size_t width = static_cast<size_t>(csbi.dwSize.X) - 1;
- to_print = ElideMiddle(to_print, width);
-#endif
- }
-
- if (smart_terminal_ && !force_full_command) {
-#ifndef _WIN32
- printf("%s", to_print.c_str());
- printf("\x1B[K"); // Clear to end of line.
- fflush(stdout);
- have_blank_line_ = false;
-#else
- // We don't want to have the cursor spamming back and forth, so
- // use WriteConsoleOutput instead which updates the contents of
- // the buffer, but doesn't move the cursor position.
- GetConsoleScreenBufferInfo(console_, &csbi);
- COORD buf_size = { csbi.dwSize.X, 1 };
- COORD zero_zero = { 0, 0 };
- SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y,
- (SHORT)(csbi.dwCursorPosition.X + csbi.dwSize.X - 1),
- csbi.dwCursorPosition.Y };
- CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X];
- memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X);
- for (int i = 0; i < csbi.dwSize.X; ++i) {
- char_data[i].Char.AsciiChar = ' ';
- char_data[i].Attributes = csbi.wAttributes;
- }
- for (size_t i = 0; i < to_print.size(); ++i)
- char_data[i].Char.AsciiChar = to_print[i];
- WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target);
- delete[] char_data;
- have_blank_line_ = false;
-#endif
- } else {
- printf("%s\n", to_print.c_str());
- }
+ printer_.Print(to_print,
+ force_full_command ? LinePrinter::FULL : LinePrinter::SHORT);
}
Plan::Plan() : command_edges_(0), wanted_edges_(0) {}
diff --git a/src/build.h b/src/build.h
index d5f01cd..9a16990 100644
--- a/src/build.h
+++ b/src/build.h
@@ -25,6 +25,7 @@
#include "graph.h" // XXX needed for DependencyScan; should rearrange.
#include "exit_status.h"
+#include "line_printer.h"
#include "metrics.h"
#include "util.h" // int64_t
@@ -213,14 +214,12 @@ struct BuildStatus {
int started_edges_, finished_edges_, total_edges_;
- bool have_blank_line_;
-
/// Map of running edge to time the edge started running.
typedef map<Edge*, int> RunningEdgeMap;
RunningEdgeMap running_edges_;
- /// Whether we can do fancy terminal control codes.
- bool smart_terminal_;
+ /// Prints progress output.
+ LinePrinter printer_;
/// The custom progress status format to use.
const char* progress_status_format_;
diff --git a/src/line_printer.cc b/src/line_printer.cc
new file mode 100644
index 0000000..d30dd2c
--- /dev/null
+++ b/src/line_printer.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "line_printer.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+
+LinePrinter::LinePrinter() : have_blank_line_(true) {
+#ifndef _WIN32
+ const char* term = getenv("TERM");
+ smart_terminal_ = 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
+ // buffering. However, for Win32, the behavior is the same as _IOFBF
+ // - Full Buffering."
+ setvbuf(stdout, NULL, _IONBF, 0);
+ console_ = GetStdHandle(STD_OUTPUT_HANDLE);
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
+#endif
+}
+
+void LinePrinter::Print(std::string to_print, LineType type) {
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ GetConsoleScreenBufferInfo(console_, &csbi);
+#endif
+
+ if (smart_terminal_) {
+#ifndef _WIN32
+ printf("\r"); // Print over previous line, if any.
+#else
+ csbi.dwCursorPosition.X = 0;
+ SetConsoleCursorPosition(console_, csbi.dwCursorPosition);
+#endif
+ }
+
+ if (smart_terminal_ && type == SHORT) {
+#ifdef _WIN32
+ // Don't use the full width or console will move to next line.
+ size_t width = static_cast<size_t>(csbi.dwSize.X) - 1;
+ to_print = ElideMiddle(to_print, width);
+ // We don't want to have the cursor spamming back and forth, so
+ // use WriteConsoleOutput instead which updates the contents of
+ // the buffer, but doesn't move the cursor position.
+ GetConsoleScreenBufferInfo(console_, &csbi);
+ COORD buf_size = { csbi.dwSize.X, 1 };
+ COORD zero_zero = { 0, 0 };
+ SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y,
+ (SHORT)(csbi.dwCursorPosition.X + csbi.dwSize.X - 1),
+ csbi.dwCursorPosition.Y };
+ CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X];
+ memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X);
+ for (int i = 0; i < csbi.dwSize.X; ++i) {
+ char_data[i].Char.AsciiChar = ' ';
+ char_data[i].Attributes = csbi.wAttributes;
+ }
+ for (size_t i = 0; i < to_print.size(); ++i)
+ char_data[i].Char.AsciiChar = to_print[i];
+ WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target);
+ delete[] char_data;
+#else
+ // Limit output to width of the terminal if provided so we don't cause
+ // line-wrapping.
+ winsize size;
+ if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) {
+ to_print = ElideMiddle(to_print, size.ws_col);
+ }
+ printf("%s", to_print.c_str());
+ printf("\x1B[K"); // Clear to end of line.
+ fflush(stdout);
+#endif
+
+ have_blank_line_ = false;
+ } else {
+ printf("%s\n", to_print.c_str());
+ }
+}
+
+void LinePrinter::PrintOnNewLine(const string& to_print) {
+ if (!have_blank_line_)
+ printf("\n");
+ printf("%s", to_print.c_str());
+ have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n';
+}
diff --git a/src/line_printer.h b/src/line_printer.h
new file mode 100644
index 0000000..78510ea
--- /dev/null
+++ b/src/line_printer.h
@@ -0,0 +1,48 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NINJA_LINE_PRINTER_H_
+#define NINJA_LINE_PRINTER_H_
+
+#include <string>
+
+/// Prints lines of text, possibly overprinting previously printed lines
+/// if the terminal supports it.
+class LinePrinter {
+ public:
+ LinePrinter();
+
+ bool is_smart_terminal() const { return smart_terminal_; }
+ void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
+
+ enum LineType {
+ FULL,
+ SHORT
+ };
+ /// Overprints the current line. If type is SHORT, elides to_print to fit on
+ /// one line.
+ void Print(std::string to_print, LineType type);
+
+ /// Prints a string on a new line, not overprinting previous output.
+ void PrintOnNewLine(const std::string& to_print);
+
+ private:
+ /// Whether we can do fancy terminal control codes.
+ bool smart_terminal_;
+
+ /// Whether the caret is at the beginning of a blank line.
+ bool have_blank_line_;
+};
+
+#endif // NINJA_LINE_PRINTER_H_
diff --git a/src/ninja_test.cc b/src/ninja_test.cc
new file mode 100644
index 0000000..3376050
--- /dev/null
+++ b/src/ninja_test.cc
@@ -0,0 +1,76 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "gtest/gtest.h"
+#include "line_printer.h"
+
+std::string StringPrintf(const char* format, ...) {
+ const int N = 1024;
+ char buf[N];
+
+ va_list ap;
+ va_start(ap, format);
+ vsnprintf(buf, N, format, ap);
+ va_end(ap);
+
+ return buf;
+}
+
+/// A test result printer that's less wordy than gtest's default.
+class LaconicPrinter : public testing::EmptyTestEventListener {
+ public:
+ LaconicPrinter() : tests_started_(0), test_count_(0) {}
+ virtual void OnTestProgramStart(const testing::UnitTest& unit_test) {
+ test_count_ = unit_test.test_to_run_count();
+ }
+
+ virtual void OnTestStart(const testing::TestInfo& test_info) {
+ ++tests_started_;
+ printer_.Print(StringPrintf("[%d/%d] %s.%s", tests_started_, test_count_,
+ test_info.test_case_name(), test_info.name()),
+ LinePrinter::SHORT);
+ }
+
+ virtual void OnTestPartResult(
+ const testing::TestPartResult& test_part_result) {
+ if (!test_part_result.failed())
+ return;
+ printer_.PrintOnNewLine(StringPrintf(
+ "*** Failure in %s:%d\n%s\n", test_part_result.file_name(),
+ test_part_result.line_number(), test_part_result.summary()));
+ }
+
+ virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) {
+ printer_.PrintOnNewLine(unit_test.Passed() ? "passed\n" : "failed\n");
+ }
+
+ private:
+ LinePrinter printer_;
+ int tests_started_;
+ int test_count_;
+};
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ testing::TestEventListeners& listeners =
+ testing::UnitTest::GetInstance()->listeners();
+ delete listeners.Release(listeners.default_result_printer());
+ listeners.Append(new LaconicPrinter);
+
+ return RUN_ALL_TESTS();
+}