From 589f5b2497929a50a1c74786478cc6fea7a2e1c6 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 14 Nov 2016 15:59:41 -0800 Subject: Turn BuildStatus into an interface Make BuildStatus an abstract interface, and move the current implementation to StatusPrinter, to make way for a serialized Status output. --- CMakeLists.txt | 1 + configure.py | 2 + src/build.cc | 231 +------------------------------------------------- src/build.h | 76 +---------------- src/build_test.cc | 40 ++++----- src/ninja.cc | 20 +++-- src/status.cc | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/status.h | 107 +++++++++++++++++++++++ src/status_test.cc | 35 ++++++++ 9 files changed, 429 insertions(+), 327 deletions(-) create mode 100644 src/status.cc create mode 100644 src/status.h create mode 100644 src/status_test.cc 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 #include -#ifdef _WIN32 -#include -#include -#endif - #if defined(__SVR4) && defined(__sun) #include #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::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 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 - 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 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 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 + +#ifdef _WIN32 +#include +#include +#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::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 +#include + +#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 + 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 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)); +} -- cgit v0.12