summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/windows.yml9
-rw-r--r--CMakeLists.txt1
-rwxr-xr-xconfigure.py2
-rw-r--r--src/build.cc264
-rw-r--r--src/build.h115
-rw-r--r--src/build_test.cc47
-rw-r--r--src/disk_interface.cc28
-rw-r--r--src/disk_interface_test.cc6
-rw-r--r--src/ninja.cc43
-rw-r--r--src/status.cc266
-rw-r--r--src/status.h117
-rw-r--r--src/status_test.cc35
-rw-r--r--src/util.cc29
-rw-r--r--src/util.h8
14 files changed, 573 insertions, 397 deletions
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 04fc2f6..e4fe7bd 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -19,10 +19,15 @@ jobs:
- name: Build ninja
shell: bash
run: |
- cmake -DCMAKE_BUILD_TYPE=Release -B build
+ cmake -Bbuild
+ cmake --build build --parallel --config Debug
cmake --build build --parallel --config Release
- - name: Test ninja
+ - name: Test ninja (Debug)
+ run: .\ninja_test.exe
+ working-directory: build/Debug
+
+ - name: Test ninja (Release)
run: .\ninja_test.exe
working-directory: build/Release
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a79994..0fa1bd2 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 2007d82..fb5890a 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
@@ -36,7 +31,9 @@
#include "deps_log.h"
#include "disk_interface.h"
#include "graph.h"
+#include "metrics.h"
#include "state.h"
+#include "status.h"
#include "subprocess.h"
#include "util.h"
@@ -78,233 +75,6 @@ bool DryRunCommandRunner::WaitForCommand(Result* result) {
} // namespace
-BuildStatus::BuildStatus(const BuildConfig& config)
- : config_(config), start_time_millis_(GetTimeMillis()), started_edges_(0),
- finished_edges_(0), total_edges_(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) {
- assert(running_edges_.find(edge) == running_edges_.end());
- int start_time = (int)(GetTimeMillis() - start_time_millis_);
- running_edges_.insert(make_pair(edge, start_time));
- ++started_edges_;
-
- if (edge->use_console() || printer_.is_smart_terminal())
- PrintStatus(edge, kEdgeStarted);
-
- if (edge->use_console())
- printer_.SetConsoleLocked(true);
-}
-
-void BuildStatus::BuildEdgeFinished(Edge* edge,
- bool success,
- const string& output,
- int* start_time,
- int* end_time) {
- int64_t now = GetTimeMillis();
-
- ++finished_edges_;
-
- RunningEdgeMap::iterator i = running_edges_.find(edge);
- *start_time = i->second;
- *end_time = (int)(now - start_time_millis_);
- running_edges_.erase(i);
-
- if (edge->use_console())
- printer_.SetConsoleLocked(false);
-
- if (config_.verbosity == BuildConfig::QUIET)
- return;
-
- if (!edge->use_console())
- PrintStatus(edge, kEdgeFinished);
-
- // 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() {
- overall_rate_.Restart();
- current_rate_.Restart();
-}
-
-void BuildStatus::BuildFinished() {
- printer_.SetConsoleLocked(false);
- printer_.PrintOnNewLine("");
-}
-
-string BuildStatus::FormatProgressStatus(
- const char* progress_status_format, EdgeStatus status) const {
- string out;
- char buf[32];
- int percent;
- 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': {
- int running_edges = started_edges_ - finished_edges_;
- // count the edge that just finished as a running edge
- if (status == kEdgeFinished)
- running_edges++;
- 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':
- overall_rate_.UpdateRate(finished_edges_);
- SnprintfRate(overall_rate_.rate(), buf, "%.1f");
- out += buf;
- break;
-
- // Current rate, average over the last '-j' jobs.
- case 'c':
- current_rate_.UpdateRate(finished_edges_);
- SnprintfRate(current_rate_.rate(), buf, "%.1f");
- out += buf;
- break;
-
- // Percentage
- case 'p':
- percent = (100 * finished_edges_) / total_edges_;
- snprintf(buf, sizeof(buf), "%3i%%", percent);
- out += buf;
- break;
-
- case 'e': {
- double elapsed = overall_rate_.Elapsed();
- snprintf(buf, sizeof(buf), "%.3f", elapsed);
- 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, EdgeStatus status) {
- 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_, status) + to_print;
-
- printer_.Print(to_print,
- force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
-}
-
Plan::Plan(Builder* builder)
: builder_(builder)
, command_edges_(0)
@@ -729,12 +499,12 @@ bool RealCommandRunner::WaitForCommand(Result* result) {
Builder::Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface)
- : state_(state), config_(config),
- plan_(this), disk_interface_(disk_interface),
+ 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() {
@@ -761,7 +531,7 @@ void Builder::Cleanup() {
string err;
TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), &err);
if (new_mtime == -1) // Log and ignore Stat() errors.
- Error("%s", err.c_str());
+ status_->Error("%s", err.c_str());
if (!depfile.empty() || (*o)->mtime() != new_mtime)
disk_interface_->RemoveFile((*o)->path());
}
@@ -904,7 +674,10 @@ bool Builder::StartEdge(Edge* edge, string* err) {
if (edge->is_phony())
return true;
- status_->BuildEdgeStarted(edge);
+ int64_t start_time_millis = GetTimeMillis() - start_time_millis_;
+ running_edges_.insert(make_pair(edge, start_time_millis));
+
+ status_->BuildEdgeStarted(edge, start_time_millis);
// Create directories necessary for outputs.
// XXX: this will block; do we care?
@@ -957,9 +730,14 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
}
}
- int start_time, end_time;
- status_->BuildEdgeFinished(edge, result->success(), result->output,
- &start_time, &end_time);
+ int64_t start_time_millis, end_time_millis;
+ RunningEdgeMap::iterator it = running_edges_.find(edge);
+ start_time_millis = it->second;
+ end_time_millis = GetTimeMillis() - start_time_millis_;
+ running_edges_.erase(it);
+
+ status_->BuildEdgeFinished(edge, end_time_millis, result->success(),
+ result->output);
// The rest of this function only applies to successful commands.
if (!result->success()) {
@@ -1028,8 +806,8 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
disk_interface_->RemoveFile(rspfile);
if (scan_.build_log()) {
- if (!scan_.build_log()->RecordCommand(edge, start_time, end_time,
- output_mtime)) {
+ if (!scan_.build_log()->RecordCommand(edge, start_time_millis,
+ end_time_millis, output_mtime)) {
*err = string("Error writing to build log: ") + strerror(errno);
return false;
}
diff --git a/src/build.h b/src/build.h
index 0a68478..06823c2 100644
--- a/src/build.h
+++ b/src/build.h
@@ -25,17 +25,15 @@
#include "depfile_parser.h"
#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
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.
@@ -179,7 +177,8 @@ struct BuildConfig {
struct Builder {
Builder(State* state, const BuildConfig& config,
BuildLog* build_log, DepsLog* deps_log,
- DiskInterface* disk_interface);
+ DiskInterface* disk_interface, Status* status,
+ int64_t start_time_millis);
~Builder();
/// Clean up after interrupted commands by deleting output files.
@@ -220,118 +219,26 @@ 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,
const std::string& deps_prefix,
std::vector<Node*>* deps_nodes, std::string* err);
- DiskInterface* disk_interface_;
- DependencyScan scan_;
-
- // Unimplemented copy ctor and operator= ensure we don't copy the auto_ptr.
- Builder(const Builder &other); // DO NOT IMPLEMENT
- 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);
- void BuildEdgeFinished(Edge* edge, bool success, const std::string& output,
- int* start_time, int* end_time);
- void BuildLoadDyndeps();
- void BuildStarted();
- void BuildFinished();
-
- enum EdgeStatus {
- kEdgeStarted,
- kEdgeFinished,
- };
-
- /// 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,
- EdgeStatus status) const;
-
- private:
- void PrintStatus(const Edge* edge, EdgeStatus status);
-
- const BuildConfig& config_;
-
- /// Time the build started.
- int64_t start_time_millis_;
-
- int started_edges_, finished_edges_, total_edges_;
-
/// Map of running edge to time the edge started running.
typedef std::map<const Edge*, int> RunningEdgeMap;
RunningEdgeMap running_edges_;
- /// 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 RateInfo {
- RateInfo() : rate_(-1) {}
-
- void Restart() { stopwatch_.Restart(); }
- double Elapsed() const { return stopwatch_.Elapsed(); }
- double rate() { return rate_; }
-
- void UpdateRate(int edges) {
- if (edges && stopwatch_.Elapsed())
- rate_ = edges / stopwatch_.Elapsed();
- }
-
- private:
- double rate_;
- Stopwatch stopwatch_;
- };
+ /// Time the build started.
+ int64_t start_time_millis_;
- struct SlidingRateInfo {
- SlidingRateInfo(int n) : rate_(-1), N(n), last_update_(-1) {}
-
- void Restart() { stopwatch_.Restart(); }
- double rate() { return rate_; }
-
- void UpdateRate(int update_hint) {
- if (update_hint == last_update_)
- return;
- last_update_ = update_hint;
-
- if (times_.size() == N)
- times_.pop();
- times_.push(stopwatch_.Elapsed());
- if (times_.back() != times_.front())
- rate_ = times_.size() / (times_.back() - times_.front());
- }
-
- private:
- double rate_;
- Stopwatch stopwatch_;
- const size_t N;
- std::queue<double> times_;
- int last_update_;
- };
+ DiskInterface* disk_interface_;
+ DependencyScan scan_;
- mutable RateInfo overall_rate_;
- mutable SlidingRateInfo current_rate_;
+ // Unimplemented copy ctor and operator= ensure we don't copy the auto_ptr.
+ Builder(const Builder &other); // DO NOT IMPLEMENT
+ void operator=(const Builder &other); // DO NOT IMPLEMENT
};
#endif // NINJA_BUILD_H_
diff --git a/src/build_test.cc b/src/build_test.cc
index 0baabc4..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_),
- 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_), 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_);
+ Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_, &status_, 0);
EXPECT_TRUE(builder.AddTarget(target, &err));
command_runner_.commands_ran_.clear();
@@ -1400,8 +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]",
- BuildStatus::kEdgeStarted));
+ EXPECT_EQ(3u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(3u, builder_.plan_.command_edge_count());
command_runner_.commands_ran_.clear();
state_.Reset();
@@ -1843,14 +1842,12 @@ TEST_F(BuildTest, StatusFormatElapsed) {
status_.BuildStarted();
// Before any task is done, the elapsed time must be zero.
EXPECT_EQ("[%/e0.000]",
- status_.FormatProgressStatus("[%%/e%e]",
- BuildStatus::kEdgeStarted));
+ status_.FormatProgressStatus("[%%/e%e]", 0));
}
TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
EXPECT_EQ("[%/s0/t0/r0/u0/f0]",
- status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]",
- BuildStatus::kEdgeStarted));
+ status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]", 0));
}
TEST_F(BuildTest, FailedDepsParse) {
@@ -2120,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_);
+ 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);
@@ -2150,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_);
+ 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));
@@ -2191,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_);
+ 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);
@@ -2220,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_);
+ 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));
@@ -2256,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_);
+ Builder builder(&state, config_, NULL, NULL, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
command_runner_.commands_ran_.clear();
@@ -2314,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_);
+ 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);
@@ -2340,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_);
+ 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));
@@ -2373,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_);
+ 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);
@@ -2394,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_);
+ Builder builder(&state, config_, NULL, &deps_log, &fs_, &status_, 0);
builder.command_runner_.reset(&command_runner_);
Edge* edge = state.edges_.back();
@@ -2435,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_);
+ 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);
@@ -2458,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_);
+ 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/disk_interface.cc b/src/disk_interface.cc
index 49af001..a9497cb 100644
--- a/src/disk_interface.cc
+++ b/src/disk_interface.cc
@@ -265,6 +265,30 @@ FileReader::Status RealDiskInterface::ReadFile(const string& path,
}
int RealDiskInterface::RemoveFile(const string& path) {
+#ifdef _WIN32
+ DWORD attributes = GetFileAttributes(path.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
+ DWORD win_err = GetLastError();
+ if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
+ return 1;
+ }
+ } else if (attributes & FILE_ATTRIBUTE_READONLY) {
+ // On non-Windows systems, remove() will happily delete read-only files.
+ // On Windows Ninja should behave the same:
+ // https://github.com/ninja-build/ninja/issues/1886
+ // Skip error checking. If this fails, accept whatever happens below.
+ SetFileAttributes(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
+ }
+ if (!DeleteFile(path.c_str())) {
+ DWORD win_err = GetLastError();
+ if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
+ return 1;
+ }
+ // Report as remove(), not DeleteFile(), for cross-platform consistency.
+ Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
+ return -1;
+ }
+#else
if (remove(path.c_str()) < 0) {
switch (errno) {
case ENOENT:
@@ -273,9 +297,9 @@ int RealDiskInterface::RemoveFile(const string& path) {
Error("remove(%s): %s", path.c_str(), strerror(errno));
return -1;
}
- } else {
- return 0;
}
+#endif
+ return 0;
}
void RealDiskInterface::AllowStatCache(bool allow) {
diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc
index 066c770..b424243 100644
--- a/src/disk_interface_test.cc
+++ b/src/disk_interface_test.cc
@@ -211,6 +211,12 @@ TEST_F(DiskInterfaceTest, RemoveFile) {
EXPECT_EQ(0, disk_.RemoveFile(kFileName));
EXPECT_EQ(1, disk_.RemoveFile(kFileName));
EXPECT_EQ(1, disk_.RemoveFile("does not exist"));
+#ifdef _WIN32
+ ASSERT_TRUE(Touch(kFileName));
+ EXPECT_EQ(0, system((std::string("attrib +R ") + kFileName).c_str()));
+ EXPECT_EQ(0, disk_.RemoveFile(kFileName));
+ EXPECT_EQ(1, disk_.RemoveFile(kFileName));
+#endif
}
struct StatTest : public StateTestWithBuiltinRules,
diff --git a/src/ninja.cc b/src/ninja.cc
index eb97320..5053fcd 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"
@@ -82,7 +83,8 @@ struct Options {
/// to poke into these, so store them as fields on an object.
struct NinjaMain : public BuildLogUser {
NinjaMain(const char* ninja_command, const BuildConfig& config) :
- ninja_command_(ninja_command), config_(config) {}
+ ninja_command_(ninja_command), config_(config),
+ start_time_millis_(GetTimeMillis()) {}
/// Command line used to run Ninja.
const char* ninja_command_;
@@ -144,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();
@@ -172,6 +174,8 @@ struct NinjaMain : public BuildLogUser {
Error("%s", err.c_str()); // Log and ignore Stat() errors.
return mtime == 0;
}
+
+ int64_t start_time_millis_;
};
/// Subtools, accessible via "-t foo".
@@ -240,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))
@@ -249,7 +254,8 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) {
if (!node)
return false;
- Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
+ status, start_time_millis_);
if (!builder.AddTarget(node, err))
return false;
@@ -1189,21 +1195,22 @@ 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)) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
return 1;
}
disk_interface_.AllowStatCache(g_experimental_statcache);
- Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_);
+ Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_,
+ status, start_time_millis_);
for (size_t i = 0; i < targets.size(); ++i) {
if (!builder.AddTarget(targets[i], &err)) {
if (!err.empty()) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
return 1;
} else {
// Added a target that is already up-to-date; not really
@@ -1216,12 +1223,12 @@ int NinjaMain::RunBuild(int argc, char** argv) {
disk_interface_.AllowStatCache(false);
if (builder.AlreadyUpToDate()) {
- printf("ninja: no work to do.\n");
+ status->Info("no work to do.");
return 0;
}
if (!builder.Build(&err)) {
- printf("ninja: build stopped: %s.\n", err.c_str());
+ status->Info("build stopped: %s.", err.c_str());
if (err.find("interrupted by user") != string::npos) {
return 2;
}
@@ -1359,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
@@ -1366,7 +1375,7 @@ NORETURN void real_main(int argc, char** argv) {
// Don't print this if a tool is being used, so that tool output
// can be piped into a file without this string showing up.
if (!options.tool)
- printf("ninja: Entering directory `%s'\n", options.working_dir);
+ status->Info("Entering directory `%s'", options.working_dir);
if (chdir(options.working_dir) < 0) {
Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno));
}
@@ -1394,7 +1403,7 @@ NORETURN void real_main(int argc, char** argv) {
ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts);
string err;
if (!parser.Load(options.input_file, &err)) {
- Error("%s", err.c_str());
+ status->Error("%s", err.c_str());
exit(1);
}
@@ -1411,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)
@@ -1419,17 +1428,17 @@ NORETURN void real_main(int argc, char** argv) {
// Start the build over with the new manifest.
continue;
} else if (!err.empty()) {
- Error("rebuilding '%s': %s", options.input_file, err.c_str());
+ status->Error("rebuilding '%s': %s", options.input_file, err.c_str());
exit(1);
}
- int result = ninja.RunBuild(argc, argv);
+ int result = ninja.RunBuild(argc, argv, status);
if (g_metrics)
ninja.DumpMetrics();
exit(result);
}
- Error("manifest '%s' still dirty after %d tries\n",
+ status->Error("manifest '%s' still dirty after %d tries",
options.input_file, kCycleLimit);
exit(1);
}
diff --git a/src/status.cc b/src/status.cc
new file mode 100644
index 0000000..171cbeb
--- /dev/null
+++ b/src/status.cc
@@ -0,0 +1,266 @@
+// 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 <stdarg.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);
+}
+
+void StatusPrinter::Warning(const char* msg, ...) {
+ va_list ap;
+ va_start(ap, msg);
+ ::Warning(msg, ap);
+ va_end(ap);
+}
+
+void StatusPrinter::Error(const char* msg, ...) {
+ va_list ap;
+ va_start(ap, msg);
+ ::Error(msg, ap);
+ va_end(ap);
+}
+
+void StatusPrinter::Info(const char* msg, ...) {
+ va_list ap;
+ va_start(ap, msg);
+ ::Info(msg, ap);
+ va_end(ap);
+}
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 0000000..e211ba3
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,117 @@
+// 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 void Info(const char* msg, ...) = 0;
+ virtual void Warning(const char* msg, ...) = 0;
+ virtual void Error(const char* msg, ...) = 0;
+
+ virtual ~Status() { }
+};
+
+/// Implementation of the Status 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 void Info(const char* msg, ...);
+ virtual void Warning(const char* msg, ...);
+ virtual void Error(const char* msg, ...);
+
+ 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));
+}
diff --git a/src/util.cc b/src/util.cc
index 1e0d147..b40a636 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -78,24 +78,45 @@ void Fatal(const char* msg, ...) {
#endif
}
+void Warning(const char* msg, va_list ap) {
+ fprintf(stderr, "ninja: warning: ");
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+}
+
void Warning(const char* msg, ...) {
va_list ap;
- fprintf(stderr, "ninja: warning: ");
va_start(ap, msg);
- vfprintf(stderr, msg, ap);
+ Warning(msg, ap);
va_end(ap);
+}
+
+void Error(const char* msg, va_list ap) {
+ fprintf(stderr, "ninja: error: ");
+ vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
}
void Error(const char* msg, ...) {
va_list ap;
- fprintf(stderr, "ninja: error: ");
va_start(ap, msg);
- vfprintf(stderr, msg, ap);
+ Error(msg, ap);
va_end(ap);
+}
+
+void Info(const char* msg, va_list ap) {
+ fprintf(stderr, "ninja: ");
+ vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
}
+void Info(const char* msg, ...) {
+ va_list ap;
+ va_start(ap, msg);
+ Info(msg, ap);
+ va_end(ap);
+}
+
bool CanonicalizePath(string* path, uint64_t* slash_bits, string* err) {
METRIC_RECORD("canonicalize str");
size_t len = path->size();
diff --git a/src/util.h b/src/util.h
index 4e6ebb8..15414e1 100644
--- a/src/util.h
+++ b/src/util.h
@@ -21,6 +21,8 @@
#include <stdint.h>
#endif
+#include <stdarg.h>
+
#include <string>
#include <vector>
@@ -49,9 +51,15 @@ NORETURN void Fatal(const char* msg, ...);
/// Log a warning message.
void Warning(const char* msg, ...);
+void Warning(const char* msg, va_list ap);
/// Log an error message.
void Error(const char* msg, ...);
+void Error(const char* msg, va_list ap);
+
+/// Log an informational message.
+void Info(const char* msg, ...);
+void Info(const char* msg, va_list ap);
/// Canonicalize a path like "foo/../bar.h" into just "bar.h".
/// |slash_bits| has bits set starting from lowest for a backslash that was