diff options
author | Evan Martin <martine@danga.com> | 2012-04-26 03:23:26 (GMT) |
---|---|---|
committer | Evan Martin <martine@danga.com> | 2012-04-26 03:23:26 (GMT) |
commit | 496f7729009c46691a3210fcb67c418aa102da96 (patch) | |
tree | 7cbbb34ada96929eb2af1af0be266375445a9000 | |
parent | 3fda08c34d4c91a4d16855ce40a2d0ab13a4d768 (diff) | |
parent | eeb572aa84564768311b70e14c5b5354a31bfd29 (diff) | |
download | Ninja-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.asciidoc | 17 | ||||
-rw-r--r-- | src/build.cc | 141 | ||||
-rw-r--r-- | src/build.h | 58 | ||||
-rw-r--r-- | src/build_test.cc | 51 |
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); +} |