summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvan Martin <martine@danga.com>2012-04-26 03:23:26 (GMT)
committerEvan Martin <martine@danga.com>2012-04-26 03:23:26 (GMT)
commit496f7729009c46691a3210fcb67c418aa102da96 (patch)
tree7cbbb34ada96929eb2af1af0be266375445a9000
parent3fda08c34d4c91a4d16855ce40a2d0ab13a4d768 (diff)
parenteeb572aa84564768311b70e14c5b5354a31bfd29 (diff)
downloadNinja-496f7729009c46691a3210fcb67c418aa102da96.zip
Ninja-496f7729009c46691a3210fcb67c418aa102da96.tar.gz
Ninja-496f7729009c46691a3210fcb67c418aa102da96.tar.bz2
Merge branch 'custom_ninja_status' of git://github.com/polrop/ninja
-rw-r--r--doc/manual.asciidoc17
-rw-r--r--src/build.cc141
-rw-r--r--src/build.h58
-rw-r--r--src/build_test.cc51
4 files changed, 216 insertions, 51 deletions
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index a330095..0201820 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -375,6 +375,23 @@ depfiles are not removed. Files created but not referenced in the
graph are not removed. This tool takes in account the +-v+ and the
+-n+ options (note that +-n+ implies +-v+).
+Environment variables
+---------------------
+
+Ninja supports various environment variables to control its behavior.
+
+`NINJA_STATUS`:: The progress status printed before the rule being run.
+Several placeholders are available:
+* `%s`: The number of started edges.
+* `%t`: The total number of edges that must be run to complete the build.
+* `%r`: The number of currently running edges.
+* `%u`: The number of remaining edges to start.
+* `%f`: The number of finished edges.
+* `%%`: A plain `%` character.
+* The default progress status is `"[%s/%t] "` (note the trailing space
+to separate from the build rule). Another example of possible progress status
+could be `"[%u/%r/%f] "`.
+
Ninja file reference
--------------------
diff --git a/src/build.cc b/src/build.cc
index ac49d27..bacae6c 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -16,6 +16,7 @@
#include <assert.h>
#include <stdio.h>
+#include <sstream>
#ifdef _WIN32
#include <windows.h>
@@ -33,47 +34,12 @@
#include "subprocess.h"
#include "util.h"
-/// Tracks the status of a build: completion fraction, printing updates.
-struct BuildStatus {
- BuildStatus(const BuildConfig& config);
- void PlanHasTotalEdges(int total);
- void BuildEdgeStarted(Edge* edge);
- void BuildEdgeFinished(Edge* edge, bool success, const string& output,
- int* start_time, int* end_time);
- void BuildFinished();
-
- private:
- void PrintStatus(Edge* edge);
-
- const BuildConfig& config_;
-
- /// Time the build started.
- int64_t start_time_millis_;
- /// Time we last printed an update.
- int64_t last_update_millis_;
-
- int started_edges_, finished_edges_, total_edges_;
-
- bool have_blank_line_;
-
- /// Map of running edge to time the edge started running.
- typedef map<Edge*, int> RunningEdgeMap;
- RunningEdgeMap running_edges_;
-
- /// Whether we can do fancy terminal control codes.
- bool smart_terminal_;
-
-#ifdef _WIN32
- HANDLE console_;
-#endif
-};
-
BuildStatus::BuildStatus(const BuildConfig& config)
: config_(config),
start_time_millis_(GetTimeMillis()),
last_update_millis_(start_time_millis_),
started_edges_(0), finished_edges_(0), total_edges_(0),
- have_blank_line_(true) {
+ have_blank_line_(true), progress_status_format_(NULL) {
#ifndef _WIN32
const char* term = getenv("TERM");
smart_terminal_ = isatty(1) && term && string(term) != "dumb";
@@ -91,6 +57,10 @@ BuildStatus::BuildStatus(const BuildConfig& config)
// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
smart_terminal_ = false;
+
+ progress_status_format_ = getenv("NINJA_STATUS");
+ if (progress_status_format_ == NULL)
+ progress_status_format_ = "[%s/%t] ";
}
void BuildStatus::PlanHasTotalEdges(int total) {
@@ -171,6 +141,91 @@ void BuildStatus::BuildFinished() {
printf("\n");
}
+int BuildStatus::FormatProgressStatus(const char* progress_status_format,
+ char* buffer,
+ const int buffer_size,
+ string* err) const {
+ int i = 0;
+ for (const char* s = progress_status_format;
+ *s != '\0' && i < buffer_size;
+ ++s) {
+ if (*s == '%') {
+ ++s;
+ switch(*s) {
+ case '%':
+ buffer[i] = '%';
+ ++i;
+ break;
+
+ // Started edges.
+ case 's':
+ i += snprintf(&buffer[i], buffer_size - i, "%d", started_edges_);
+ break;
+
+ // Total edges.
+ case 't':
+ i += snprintf(&buffer[i], buffer_size - i, "%d", total_edges_);
+ break;
+
+ // Running edges.
+ case 'r':
+ i += snprintf(&buffer[i], buffer_size - i, "%d",
+ started_edges_ - finished_edges_);
+ break;
+
+ // Unstarted edges.
+ case 'u':
+ i += snprintf(&buffer[i], buffer_size - i, "%d",
+ total_edges_ - started_edges_);
+ break;
+
+ // Finished edges.
+ case 'f':
+ i += snprintf(&buffer[i], buffer_size - i, "%d", finished_edges_);
+ break;
+
+ default:
+ if (err != NULL) {
+ ostringstream oss;
+ oss << "unknown placeholders '%" << *s << "' in NINJA_STATUS";
+ *err = oss.str();
+ }
+ return -1;
+ }
+ } else {
+ buffer[i] = *s;
+ ++i;
+ }
+ }
+ if (i >= buffer_size) {
+ if (err != NULL) {
+ ostringstream oss;
+ oss << "custom NINJA_STATUS exceed buffer size " << buffer_size;
+ *err = oss.str();
+ }
+ return -1;
+ } else {
+ buffer[i] = '\0';
+ if (err != NULL)
+ *err = "";
+ return i;
+ }
+}
+
+int BuildStatus::PrintProgressStatus() const {
+ const int kBUFF_SIZE = 1024;
+ char buff[kBUFF_SIZE] = { '\0' };
+ string err;
+ int progress_chars = FormatProgressStatus(progress_status_format_,
+ buff,
+ kBUFF_SIZE,
+ &err);
+ if (progress_chars < 0)
+ return printf("!! %s !! ", err.c_str());
+ else
+ return printf("%s", buff);
+}
+
void BuildStatus::PrintStatus(Edge* edge) {
if (config_.verbosity == BuildConfig::QUIET)
return;
@@ -195,7 +250,7 @@ void BuildStatus::PrintStatus(Edge* edge) {
#endif
}
- int progress_chars = printf("[%d/%d] ", started_edges_, total_edges_);
+ int progress_chars = PrintProgressStatus();
if (smart_terminal_ && !force_full_command) {
#ifndef _WIN32
@@ -387,7 +442,7 @@ void Plan::CleanNode(BuildLog* build_log, Node* node) {
if ((*ni)->mtime() > most_recent_input)
most_recent_input = (*ni)->mtime();
string command = (*ei)->EvaluateCommand(true);
-
+
// Now, recompute the dirty state of each output.
bool all_outputs_clean = true;
for (vector<Node*>::iterator ni = (*ei)->outputs_.begin();
@@ -461,7 +516,7 @@ bool RealCommandRunner::StartCommand(Edge* edge) {
if (!subproc)
return false;
subproc_to_edge_.insert(make_pair(subproc, edge));
-
+
return true;
}
@@ -660,7 +715,7 @@ bool Builder::Build(string* err) {
*err = "subcommand failed";
} else if (failures_allowed < config_.failures_allowed)
*err = "cannot make progress due to previous errors";
- else
+ else
*err = "stuck [this is a bug]";
return false;
@@ -683,11 +738,11 @@ bool Builder::StartEdge(Edge* edge, string* err) {
if (!disk_interface_->MakeDirs((*i)->path()))
return false;
}
-
+
// Create response file, if needed
// XXX: this may also block; do we care?
if (edge->HasRspFile()) {
- if (!disk_interface_->WriteFile(edge->GetRspFile(), edge->GetRspFileContent()))
+ if (!disk_interface_->WriteFile(edge->GetRspFile(), edge->GetRspFileContent()))
return false;
}
@@ -748,7 +803,7 @@ void Builder::FinishEdge(Edge* edge, bool success, const string& output) {
}
// delete the response file on success (if exists)
- if (edge->HasRspFile())
+ if (edge->HasRspFile())
disk_interface_->RemoveFile(edge->GetRspFile());
plan_.EdgeFinished(edge);
diff --git a/src/build.h b/src/build.h
index 179fca6..8078910 100644
--- a/src/build.h
+++ b/src/build.h
@@ -149,4 +149,62 @@ private:
void operator=(const Builder &other); // DO NOT IMPLEMENT
};
+/// Tracks the status of a build: completion fraction, printing updates.
+struct BuildStatus {
+ BuildStatus(const BuildConfig& config);
+ void PlanHasTotalEdges(int total);
+ void BuildEdgeStarted(Edge* edge);
+ void BuildEdgeFinished(Edge* edge, bool success, const string& output,
+ int* start_time, int* end_time);
+ 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 buffer The buffer where is stored the formatted progress status.
+ /// @param buffer_size The size of the given @a buffer.
+ /// @param err The error message if -1 is returned.
+ /// @return The number of characters inserted in @a buffer and -1 on error.
+ int FormatProgressStatus(const char* progress_status_format,
+ char* buffer,
+ const int buffer_size,
+ string* err) const;
+
+ private:
+ void PrintStatus(Edge* edge);
+
+ /// Print the progress status.
+ ///
+ /// Get the status from the NINJA_STATUS environment variable if defined.
+ ///
+ /// @return The number of printed characters.
+ int PrintProgressStatus() const;
+
+ const BuildConfig& config_;
+
+ /// Time the build started.
+ int64_t start_time_millis_;
+ /// Time we last printed an update.
+ int64_t last_update_millis_;
+
+ int started_edges_, finished_edges_, total_edges_;
+
+ bool have_blank_line_;
+
+ /// Map of running edge to time the edge started running.
+ typedef map<Edge*, int> RunningEdgeMap;
+ RunningEdgeMap running_edges_;
+
+ /// Whether we can do fancy terminal control codes.
+ bool smart_terminal_;
+
+ /// The custom progress status format to use.
+ const char* progress_status_format_;
+
+#ifdef _WIN32
+ HANDLE console_;
+#endif
+};
+
#endif // NINJA_BUILD_H_
diff --git a/src/build_test.cc b/src/build_test.cc
index c015bc9..f3f5472 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -179,7 +179,7 @@ TEST_F(PlanTest, DependencyCycle) {
struct BuildTest : public StateTestWithBuiltinRules,
public CommandRunner {
BuildTest() : config_(MakeConfig()), builder_(&state_, config_), now_(1),
- last_command_(NULL) {
+ last_command_(NULL), status_(config_) {
builder_.disk_interface_ = &fs_;
builder_.command_runner_.reset(this);
AssertParse(&state_,
@@ -202,8 +202,8 @@ struct BuildTest : public StateTestWithBuiltinRules,
virtual bool CanRunMore();
virtual bool StartCommand(Edge* edge);
virtual Edge* WaitForCommand(ExitStatus* status, string* output);
- virtual vector<Edge*> GetActiveEdges();
- virtual void Abort();
+ virtual vector<Edge*> GetActiveEdges();
+ virtual void Abort();
BuildConfig MakeConfig() {
BuildConfig config;
@@ -219,6 +219,7 @@ struct BuildTest : public StateTestWithBuiltinRules,
vector<string> commands_ran_;
Edge* last_command_;
+ BuildStatus status_;
};
void BuildTest::Dirty(const string& path) {
@@ -853,7 +854,7 @@ TEST_F(BuildTest, RspFileSuccess)
fs_.Create("out1", now_, "");
fs_.Create("out2", now_, "");
fs_.Create("out3", now_, "");
-
+
now_++;
fs_.Create("in", now_, "");
@@ -869,11 +870,11 @@ TEST_F(BuildTest, RspFileSuccess)
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ(2u, commands_ran_.size()); // cat + cat_rsp
-
+
// The RSP file was created
ASSERT_EQ(files_created + 1, fs_.files_created_.size());
ASSERT_EQ(1u, fs_.files_created_.count("out2.rsp"));
-
+
// The RSP file was removed
ASSERT_EQ(files_removed + 1, fs_.files_removed_.size());
ASSERT_EQ(1u, fs_.files_removed_.count("out2.rsp"));
@@ -917,7 +918,7 @@ TEST_F(BuildTest, RspFileFailure) {
ASSERT_EQ("Another very long command", fs_.files_["out.rsp"].contents);
}
-// Test that contens of the RSP file behaves like a regular part of
+// Test that contens of the RSP file behaves like a regular part of
// command line, i.e. triggers a rebuild if changed
TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
@@ -948,7 +949,7 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
EXPECT_EQ("", err);
ASSERT_TRUE(builder_.AlreadyUpToDate());
- // 3. Alter the entry in the logfile
+ // 3. Alter the entry in the logfile
// (to simulate a change in the command line between 2 builds)
BuildLog::LogEntry * log_entry = build_log_.LookupByOutput("out");
ASSERT_TRUE(NULL != log_entry);
@@ -1022,3 +1023,37 @@ TEST_F(BuildTest, PhonyWithNoInputs) {
EXPECT_EQ("", err);
ASSERT_EQ(1u, commands_ran_.size());
}
+
+TEST_F(BuildTest, StatusFormatBufferTooSmall) {
+ const int kBUFF_SIZE = 5;
+ char buff[kBUFF_SIZE] = { '\0' };
+ string err;
+
+ EXPECT_EQ(-1, status_.FormatProgressStatus("0123456789",
+ buff, kBUFF_SIZE,
+ &err));
+ EXPECT_EQ("custom NINJA_STATUS exceed buffer size 5", err);
+}
+
+TEST_F(BuildTest, StatusFormatWrongPlaceholder) {
+ const int kBUFF_SIZE = 1024;
+ char buff[kBUFF_SIZE] = { '\0' };
+ string err;
+
+ EXPECT_EQ(-1, status_.FormatProgressStatus("[%r/%X]",
+ buff, kBUFF_SIZE,
+ &err));
+ EXPECT_EQ("unknown placeholders '%X' in NINJA_STATUS", err);
+}
+
+TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
+ const int kBUFF_SIZE = 1024;
+ char buff[kBUFF_SIZE] = { '\0' };
+ string err;
+
+ EXPECT_EQ(18, status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]",
+ buff, kBUFF_SIZE,
+ &err));
+ EXPECT_EQ("", err);
+ EXPECT_STREQ("[%/s0/t0/r0/u0/f0]", buff);
+}