summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rwxr-xr-xconfigure.py2
-rw-r--r--src/build.cc231
-rw-r--r--src/build.h76
-rw-r--r--src/build_test.cc40
-rw-r--r--src/ninja.cc20
-rw-r--r--src/status.cc244
-rw-r--r--src/status.h107
-rw-r--r--src/status_test.cc35
9 files changed, 429 insertions, 327 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39348c9..4d99a0c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,6 +95,7 @@ add_library(libninja OBJECT
src/metrics.cc
src/parser.cc
src/state.cc
+ src/status.cc
src/string_piece_util.cc
src/util.cc
src/version.cc
diff --git a/configure.py b/configure.py
index cded265..1a3aca5 100755
--- a/configure.py
+++ b/configure.py
@@ -513,6 +513,7 @@ for name in ['build',
'metrics',
'parser',
'state',
+ 'status',
'string_piece_util',
'util',
'version']:
@@ -579,6 +580,7 @@ for name in ['build_log_test',
'manifest_parser_test',
'ninja_test',
'state_test',
+ 'status_test',
'string_piece_util_test',
'subprocess_test',
'test',
diff --git a/src/build.cc b/src/build.cc
index 3b1a3f4..3d1e76b 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -20,11 +20,6 @@
#include <stdlib.h>
#include <functional>
-#ifdef _WIN32
-#include <fcntl.h>
-#include <io.h>
-#endif
-
#if defined(__SVR4) && defined(__sun)
#include <sys/termios.h>
#endif
@@ -38,6 +33,7 @@
#include "graph.h"
#include "metrics.h"
#include "state.h"
+#include "status.h"
#include "subprocess.h"
#include "util.h"
@@ -79,225 +75,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(const 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
- // explanation 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(const 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)
@@ -722,12 +499,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() {
diff --git a/src/build.h b/src/build.h
index 265723a..06823c2 100644
--- a/src/build.h
+++ b/src/build.h
@@ -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.
@@ -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.
@@ -219,7 +219,7 @@ struct Builder {
#else
std::unique_ptr<CommandRunner> command_runner_; // auto_ptr was removed in C++17.
#endif
- BuildStatus* status_;
+ Status* status_;
private:
bool ExtractDeps(CommandRunner::Result* result, const std::string& deps_type,
@@ -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(const Edge* edge, int64_t start_time_millis);
- void BuildEdgeFinished(Edge* edge, int64_t end_time_millis, bool success,
- const std::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.
- std::string FormatProgressStatus(const char* progress_status_format,
- int64_t time) const;
-
- private:
- void PrintStatus(const 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;
- std::queue<double> times_;
- int last_update_;
- };
-
- mutable SlidingRateInfo current_rate_;
-};
-
#endif // NINJA_BUILD_H_
diff --git a/src/build_test.cc b/src/build_test.cc
index 1baec65..f58a7de 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -19,6 +19,7 @@
#include "build_log.h"
#include "deps_log.h"
#include "graph.h"
+#include "status.h"
#include "test.h"
using namespace std;
@@ -485,14 +486,13 @@ struct FakeCommandRunner : public CommandRunner {
};
struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
- BuildTest() : config_(MakeConfig()), command_runner_(&fs_),
- builder_(&state_, config_, NULL, NULL, &fs_, 0),
- status_(config_) {
+ BuildTest() : config_(MakeConfig()), command_runner_(&fs_), status_(config_),
+ builder_(&state_, config_, NULL, NULL, &fs_, &status_, 0) {
}
explicit BuildTest(DepsLog* log)
- : config_(MakeConfig()), command_runner_(&fs_),
- builder_(&state_, config_, NULL, log, &fs_, 0), status_(config_) {}
+ : config_(MakeConfig()), command_runner_(&fs_), status_(config_),
+ builder_(&state_, config_, NULL, log, &fs_, &status_, 0) {}
virtual void SetUp() {
StateTestWithBuiltinRules::SetUp();
@@ -532,9 +532,8 @@ struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser {
BuildConfig config_;
FakeCommandRunner command_runner_;
VirtualFileSystem fs_;
+ StatusPrinter status_;
Builder builder_;
-
- BuildStatus status_;
};
void BuildTest::RebuildTarget(const string& target, const char* manifest,
@@ -563,7 +562,7 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest,
pdeps_log = &deps_log;
}
- Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_, 0);
+ Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_, &status_, 0);
EXPECT_TRUE(builder.AddTarget(target, &err));
command_runner_.commands_ran_.clear();
@@ -1400,7 +1399,8 @@ TEST_F(BuildWithLogTest, RestatTest) {
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- EXPECT_EQ("[3/3]", builder_.status_->FormatProgressStatus("[%s/%t]", 0));
+ EXPECT_EQ(3u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(3u, builder_.plan_.command_edge_count());
command_runner_.commands_ran_.clear();
state_.Reset();
@@ -2117,7 +2117,7 @@ TEST_F(BuildWithDepsLogTest, Straightforward) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
EXPECT_TRUE(builder.AddTarget("out", &err));
ASSERT_EQ("", err);
@@ -2147,7 +2147,7 @@ TEST_F(BuildWithDepsLogTest, Straightforward) {
ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
EXPECT_TRUE(builder.AddTarget("out", &err));
@@ -2188,7 +2188,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
EXPECT_TRUE(builder.AddTarget("out", &err));
ASSERT_EQ("", err);
@@ -2217,7 +2217,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
EXPECT_TRUE(builder.AddTarget("out", &err));
@@ -2253,7 +2253,7 @@ TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) {
// The deps log is NULL in dry runs.
config_.dry_run = true;
- Builder builder(&state, config_, NULL, NULL, &fs_, 0);
+ Builder builder(&state, config_, NULL, NULL, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
@@ -2311,7 +2311,7 @@ TEST_F(BuildWithDepsLogTest, RestatDepfileDependencyDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
EXPECT_TRUE(builder.AddTarget("out", &err));
ASSERT_EQ("", err);
@@ -2337,7 +2337,7 @@ TEST_F(BuildWithDepsLogTest, RestatDepfileDependencyDepsLog) {
ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
EXPECT_TRUE(builder.AddTarget("out", &err));
@@ -2370,7 +2370,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
EXPECT_TRUE(builder.AddTarget("fo o.o", &err));
ASSERT_EQ("", err);
@@ -2391,7 +2391,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
Edge* edge = state.edges_.back();
@@ -2432,7 +2432,7 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err));
ASSERT_EQ("", err);
@@ -2455,7 +2455,7 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) {
ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
ASSERT_EQ("", err);
- Builder builder(&state, config_, NULL, &deps_log, &fs_, 0);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
Edge* edge = state.edges_.back();
diff --git a/src/ninja.cc b/src/ninja.cc
index 57690be..d92e95b 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -43,6 +43,7 @@
#include "manifest_parser.h"
#include "metrics.h"
#include "state.h"
+#include "status.h"
#include "util.h"
#include "version.h"
@@ -145,11 +146,11 @@ struct NinjaMain : public BuildLogUser {
/// Rebuild the manifest, if necessary.
/// Fills in \a err on error.
/// @return true if the manifest was rebuilt.
- bool RebuildManifest(const char* input_file, string* err);
+ bool RebuildManifest(const char* input_file, string* err, Status* status);
/// Build the targets listed on the command line.
/// @return an exit code.
- int RunBuild(int argc, char** argv);
+ int RunBuild(int argc, char** argv, Status* status);
/// Dump the output requested by '-d stats'.
void DumpMetrics();
@@ -243,7 +244,8 @@ int GuessParallelism() {
/// Rebuild the build manifest, if necessary.
/// Returns true if the manifest was rebuilt.
-bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
+bool NinjaMain::RebuildManifest(const char* input_file, string* err,
+ Status *status) {
string path = input_file;
uint64_t slash_bits; // Unused because this path is only used for lookup.
if (!CanonicalizePath(&path, &slash_bits, err))
@@ -253,7 +255,7 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
return false;
Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
- start_time_millis_);
+ status, start_time_millis_);
if (!builder.AddTarget(node, err))
return false;
@@ -1193,7 +1195,7 @@ bool NinjaMain::EnsureBuildDirExists() {
return true;
}
-int NinjaMain::RunBuild(int argc, char** argv) {
+int NinjaMain::RunBuild(int argc, char** argv, Status* status) {
string err;
vector<Node*> targets;
if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) {
@@ -1204,7 +1206,7 @@ int NinjaMain::RunBuild(int argc, char** argv) {
disk_interface_.AllowStatCache(g_experimental_statcache);
Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
- start_time_millis_);
+ status, start_time_millis_);
for (size_t i = 0; i < targets.size(); ++i) {
if (!builder.AddTarget(targets[i], &err)) {
if (!err.empty()) {
@@ -1364,6 +1366,8 @@ NORETURN void real_main(int argc, char** argv) {
if (exit_code >= 0)
exit(exit_code);
+ Status* status = new StatusPrinter(config);
+
if (options.working_dir) {
// The formatting of this string, complete with funny quotes, is
// so Emacs can properly identify that the cwd has changed for
@@ -1416,7 +1420,7 @@ NORETURN void real_main(int argc, char** argv) {
exit((ninja.*options.tool->func)(&options, argc, argv));
// Attempt to rebuild the manifest before building anything else
- if (ninja.RebuildManifest(options.input_file, &err)) {
+ if (ninja.RebuildManifest(options.input_file, &err, status)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return immediately.
if (config.dry_run)
@@ -1428,7 +1432,7 @@ NORETURN void real_main(int argc, char** argv) {
exit(1);
}
- int result = ninja.RunBuild(argc, argv);
+ int result = ninja.RunBuild(argc, argv, status);
if (g_metrics)
ninja.DumpMetrics();
exit(result);
diff --git a/src/status.cc b/src/status.cc
new file mode 100644
index 0000000..a308f62
--- /dev/null
+++ b/src/status.cc
@@ -0,0 +1,244 @@
+// Copyright 2016 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 "status.h"
+
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#include "debug_flags.h"
+
+using namespace std;
+
+StatusPrinter::StatusPrinter(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 StatusPrinter::PlanHasTotalEdges(int total) {
+ total_edges_ = total;
+}
+
+void StatusPrinter::BuildEdgeStarted(const Edge* edge,
+ int64_t start_time_millis) {
+ ++started_edges_;
+ ++running_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 StatusPrinter::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);
+
+ --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 StatusPrinter::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
+ // explanation 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 StatusPrinter::BuildStarted() {
+ started_edges_ = 0;
+ finished_edges_ = 0;
+ running_edges_ = 0;
+}
+
+void StatusPrinter::BuildFinished() {
+ printer_.SetConsoleLocked(false);
+ printer_.PrintOnNewLine("");
+}
+
+string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
+ int64_t time_millis) 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 StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
+ 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_millis)
+ + to_print;
+
+ printer_.Print(to_print,
+ force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
+}
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 0000000..8344b8e
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,107 @@
+// Copyright 2016 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_STATUS_H_
+#define NINJA_STATUS_H_
+
+#include <map>
+#include <string>
+
+#include "build.h"
+#include "line_printer.h"
+
+/// Abstract interface to object that tracks the status of a build:
+/// completion fraction, printing updates.
+struct Status {
+ virtual void PlanHasTotalEdges(int total) = 0;
+ virtual void BuildEdgeStarted(const Edge* edge, int64_t start_time_millis) = 0;
+ virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
+ bool success, const std::string& output) = 0;
+ virtual void BuildLoadDyndeps() = 0;
+ virtual void BuildStarted() = 0;
+ virtual void BuildFinished() = 0;
+ virtual ~Status() { }
+};
+
+/// Implementation of the BuildStatus interface that prints the status as
+/// human-readable strings to stdout
+struct StatusPrinter : Status {
+ explicit StatusPrinter(const BuildConfig& config);
+ virtual void PlanHasTotalEdges(int total);
+ virtual void BuildEdgeStarted(const Edge* edge, int64_t start_time_millis);
+ virtual void BuildEdgeFinished(Edge* edge, int64_t end_time_millis,
+ bool success, const std::string& output);
+ virtual void BuildLoadDyndeps();
+ virtual void BuildStarted();
+ virtual void BuildFinished();
+ virtual ~StatusPrinter() { }
+
+ /// 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.
+ std::string FormatProgressStatus(const char* progress_status_format,
+ int64_t time_millis) const;
+
+ private:
+ void PrintStatus(const Edge* edge, int64_t time_millis);
+
+ 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;
+ std::queue<double> times_;
+ int last_update_;
+ };
+
+ mutable SlidingRateInfo current_rate_;
+};
+
+#endif // NINJA_STATUS_H_
diff --git a/src/status_test.cc b/src/status_test.cc
new file mode 100644
index 0000000..6e42490
--- /dev/null
+++ b/src/status_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2011 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 "status.h"
+
+#include "test.h"
+
+TEST(StatusTest, StatusFormatElapsed) {
+ BuildConfig config;
+ StatusPrinter status(config);
+
+ status.BuildStarted();
+ // Before any task is done, the elapsed time must be zero.
+ EXPECT_EQ("[%/e0.000]",
+ status.FormatProgressStatus("[%%/e%e]", 0));
+}
+
+TEST(StatusTest, StatusFormatReplacePlaceholder) {
+ BuildConfig config;
+ StatusPrinter status(config);
+
+ EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
+ status.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", 0));
+}