mirror of
https://github.com/ninja-build/ninja.git
synced 2026-06-24 08:48:42 +00:00
Merge pull request #2772 from jhasse/status-cmd-line
Add --status flag with Ninja-style variable expansion
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@
|
||||
*.pdb
|
||||
*.ilk
|
||||
/build*/
|
||||
/build.ninja
|
||||
/build*.ninja
|
||||
/ninja
|
||||
/ninja.bootstrap
|
||||
/build_log_perftest
|
||||
|
||||
@@ -248,6 +248,30 @@ The default progress status is `"[%f/%t] "` (note the trailing space
|
||||
to separate from the build rule). Another example of possible progress status
|
||||
could be `"[%u/%r/%f] "`.
|
||||
|
||||
The `--status FMT` command-line flag also configures the progress status,
|
||||
but uses Ninja's regular variable-expansion syntax (`$var` or `${var}`)
|
||||
instead of `%`-escapes, with descriptive variable names. When passed,
|
||||
it takes precedence over `NINJA_STATUS`.
|
||||
|
||||
The available variables and their `NINJA_STATUS` equivalents are:
|
||||
|
||||
`$started`:: `%s` -- The number of started edges.
|
||||
`$total`:: `%t` -- The total number of edges.
|
||||
`$progress`:: `%p` -- The percentage of finished edges.
|
||||
`$running`:: `%r` -- The number of currently running edges.
|
||||
`$remaining`:: `%u` -- The number of remaining edges to start.
|
||||
`$finished`:: `%f` -- The number of finished edges.
|
||||
`$rate`:: `%o` -- Overall rate of finished edges per second.
|
||||
`$current_rate`:: `%c` -- Current rate of finished edges per second.
|
||||
`$elapsed_seconds`:: `%e` -- Elapsed time in seconds.
|
||||
`$eta_seconds`:: `%E` -- Remaining time (ETA) in seconds.
|
||||
`$elapsed`:: `%w` -- Elapsed time in [h:]mm:ss format.
|
||||
`$eta`:: `%W` -- Remaining time (ETA) in [h:]mm:ss format.
|
||||
`$predicted_progress`:: `%P` -- Percentage (in `ppp%` format) of time elapsed
|
||||
out of predicted total runtime.
|
||||
|
||||
To produce a literal `$`, use `$$`. The `--status` flag was added in Ninja 1.14.
|
||||
|
||||
If `MAKEFLAGS` is defined in the environment, if may alter how
|
||||
Ninja dispatches parallel build commands. See the GNU Jobserver support
|
||||
section for details.
|
||||
|
||||
@@ -386,6 +386,18 @@ ninja: build stopped: subcommand failed.
|
||||
output = run(Output.BUILD_SIMPLE_ECHO, flags='--quiet')
|
||||
self.assertEqual(output, 'do thing\n')
|
||||
|
||||
def test_status_flag(self) -> None:
|
||||
'Does --status accept a Ninja-style $-format?'
|
||||
output = run(Output.BUILD_SIMPLE_ECHO,
|
||||
flags="--status '<${finished}/${total}> '")
|
||||
self.assertEqual(output, '<1/1> echo a\x1b[K\ndo thing\n')
|
||||
|
||||
def test_status_flag_unknown_variable(self) -> None:
|
||||
'Does --status fail clearly on an unknown variable?'
|
||||
self._test_expected_error(
|
||||
Output.BUILD_SIMPLE_ECHO, "--status '$nope '",
|
||||
"ninja: fatal: unknown variable 'nope' in --status format\n")
|
||||
|
||||
def test_entering_directory_on_stdout(self) -> None:
|
||||
output = run(Output.BUILD_SIMPLE_ECHO, flags='-C$PWD', pipe=True)
|
||||
self.assertEqual(output.splitlines()[0][:25], "ninja: Entering directory")
|
||||
|
||||
@@ -194,6 +194,9 @@ struct BuildConfig {
|
||||
/// The maximum load average we must not exceed. A negative value
|
||||
/// means that we do not have any limit.
|
||||
double max_load_average = -0.0f;
|
||||
/// Progress status format, as set by --status. Overrides $NINJA_STATUS
|
||||
/// when non-null.
|
||||
const char* progress_status_format = nullptr;
|
||||
DepfileParserOptions depfile_parser_options;
|
||||
};
|
||||
|
||||
|
||||
@@ -234,6 +234,8 @@ void Usage(const BuildConfig& config) {
|
||||
" --version print ninja version (\"%s\")\n"
|
||||
" -v, --verbose show all command lines while building\n"
|
||||
" --quiet don't show progress status, just command output\n"
|
||||
" --status FMT progress status format using Ninja-style $vars\n"
|
||||
" (e.g. --status '[$finished/$total] ')\n"
|
||||
"\n"
|
||||
" -C DIR change to DIR before doing anything else\n"
|
||||
" -f FILE specify input build file [default=build.ninja]\n"
|
||||
@@ -1729,12 +1731,13 @@ int ReadFlags(int* argc, char*** argv,
|
||||
Options* options, BuildConfig* config) {
|
||||
DeferGuessParallelism deferGuessParallelism(config);
|
||||
|
||||
enum { OPT_VERSION = 1, OPT_QUIET = 2 };
|
||||
enum { OPT_VERSION = 1, OPT_QUIET = 2, OPT_STATUS = 3 };
|
||||
const option kLongOptions[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, OPT_VERSION },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ "quiet", no_argument, NULL, OPT_QUIET },
|
||||
{ "status", required_argument, NULL, OPT_STATUS },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
@@ -1800,6 +1803,9 @@ int ReadFlags(int* argc, char*** argv,
|
||||
case OPT_QUIET:
|
||||
config->verbosity = BuildConfig::NO_STATUS_UPDATE;
|
||||
break;
|
||||
case OPT_STATUS:
|
||||
config->progress_status_format = optarg;
|
||||
break;
|
||||
case 'w':
|
||||
if (!WarningEnable(optarg, options))
|
||||
return 1;
|
||||
|
||||
@@ -34,9 +34,23 @@
|
||||
#include "build.h"
|
||||
#include "debug_flags.h"
|
||||
#include "exit_status.h"
|
||||
#include "lexer.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
/// Env that resolves variables in a `--status` format string by asking
|
||||
/// the StatusPrinter for their current value.
|
||||
struct StatusFormatEnv : public Env {
|
||||
const StatusPrinter* printer;
|
||||
explicit StatusFormatEnv(const StatusPrinter* p) : printer(p) {}
|
||||
string LookupVariable(const string& var) override {
|
||||
return printer->FormatStatusVariable(var);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status* Status::factory(const BuildConfig& config) {
|
||||
return new StatusPrinter(config);
|
||||
}
|
||||
@@ -49,9 +63,22 @@ StatusPrinter::StatusPrinter(const BuildConfig& config)
|
||||
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] ";
|
||||
if (config.progress_status_format) {
|
||||
// --status uses Ninja-style variable expansion ($var / ${var}).
|
||||
// Append a newline because Lexer::ReadVarValue terminates on \n.
|
||||
string input = string(config.progress_status_format) + "\n";
|
||||
Lexer lexer;
|
||||
lexer.Start("--status", input);
|
||||
status_eval_.reset(new EvalString());
|
||||
string err;
|
||||
if (!lexer.ReadVarValue(status_eval_.get(), &err))
|
||||
Fatal("invalid --status: %s", err.c_str());
|
||||
progress_status_format_ = NULL;
|
||||
} else {
|
||||
progress_status_format_ = getenv("NINJA_STATUS");
|
||||
if (!progress_status_format_)
|
||||
progress_status_format_ = "[%f/%t] ";
|
||||
}
|
||||
}
|
||||
|
||||
void StatusPrinter::EdgeAddedToPlan(const Edge* edge) {
|
||||
@@ -405,6 +432,79 @@ string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
|
||||
return out;
|
||||
}
|
||||
|
||||
string StatusPrinter::FormatStatusVariable(const string& name) const {
|
||||
char buf[32];
|
||||
|
||||
if (name == "started") {
|
||||
snprintf(buf, sizeof(buf), "%d", started_edges_);
|
||||
return buf;
|
||||
}
|
||||
if (name == "total") {
|
||||
snprintf(buf, sizeof(buf), "%d", total_edges_);
|
||||
return buf;
|
||||
}
|
||||
if (name == "running") {
|
||||
snprintf(buf, sizeof(buf), "%d", running_edges_);
|
||||
return buf;
|
||||
}
|
||||
if (name == "remaining") {
|
||||
snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
|
||||
return buf;
|
||||
}
|
||||
if (name == "finished") {
|
||||
snprintf(buf, sizeof(buf), "%d", finished_edges_);
|
||||
return buf;
|
||||
}
|
||||
if (name == "rate") {
|
||||
SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
|
||||
return buf;
|
||||
}
|
||||
if (name == "current_rate") {
|
||||
current_rate_.UpdateRate(finished_edges_, time_millis_);
|
||||
SnprintfRate(current_rate_.rate(), buf, "%.1f");
|
||||
return buf;
|
||||
}
|
||||
if (name == "progress") {
|
||||
int percent = 0;
|
||||
if (finished_edges_ != 0 && total_edges_ != 0)
|
||||
percent = (100 * finished_edges_) / total_edges_;
|
||||
snprintf(buf, sizeof(buf), "%3i%%", percent);
|
||||
return buf;
|
||||
}
|
||||
if (name == "predicted_progress") {
|
||||
snprintf(buf, sizeof(buf), "%3i%%",
|
||||
(int)(100. * time_predicted_percentage_));
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (name == "elapsed" || name == "elapsed_seconds" ||
|
||||
name == "eta" || name == "eta_seconds") {
|
||||
double elapsed_sec = time_millis_ / 1e3;
|
||||
double eta_sec = -1;
|
||||
if (time_predicted_percentage_ != 0.0) {
|
||||
double total_wall_time = time_millis_ / time_predicted_percentage_;
|
||||
eta_sec = (total_wall_time - time_millis_) / 1e3;
|
||||
}
|
||||
const bool print_with_hours =
|
||||
elapsed_sec >= 60 * 60 || eta_sec >= 60 * 60;
|
||||
const bool is_eta = (name == "eta" || name == "eta_seconds");
|
||||
double sec = is_eta ? eta_sec : elapsed_sec;
|
||||
if (sec < 0)
|
||||
return "?";
|
||||
if (name == "elapsed_seconds" || name == "eta_seconds") {
|
||||
snprintf(buf, sizeof(buf), "%.3f", sec);
|
||||
} else if (print_with_hours) {
|
||||
snprintf(buf, sizeof(buf), FORMAT_TIME_HMMSS((int64_t)sec));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), FORMAT_TIME_MMSS((int64_t)sec));
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
Fatal("unknown variable '%s' in --status format", name.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
|
||||
if (explanations_) {
|
||||
explanations_->ExplainEdge(edge);
|
||||
@@ -422,8 +522,13 @@ void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
|
||||
if (to_print.empty() || force_full_command)
|
||||
to_print = edge->GetBinding("command");
|
||||
|
||||
to_print = FormatProgressStatus(progress_status_format_, time_millis)
|
||||
+ to_print;
|
||||
if (status_eval_) {
|
||||
StatusFormatEnv env(this);
|
||||
to_print = status_eval_->Evaluate(&env) + to_print;
|
||||
} else {
|
||||
to_print = FormatProgressStatus(progress_status_format_, time_millis)
|
||||
+ to_print;
|
||||
}
|
||||
|
||||
printer_.Print(to_print,
|
||||
force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
#include "eval_env.h"
|
||||
#include "exit_status.h"
|
||||
#include "explanations.h"
|
||||
#include "line_printer.h"
|
||||
@@ -50,6 +52,11 @@ struct StatusPrinter : Status {
|
||||
std::string FormatProgressStatus(const char* progress_status_format,
|
||||
int64_t time_millis) const;
|
||||
|
||||
/// Look up the value of a named status variable (used by `--status`,
|
||||
/// which evaluates a Ninja-style format string). Calls Fatal on an
|
||||
/// unknown name.
|
||||
std::string FormatStatusVariable(const std::string& name) const;
|
||||
|
||||
/// Set the |explanations_| pointer. Used to implement `-d explain`.
|
||||
void SetExplanations(Explanations* explanations) override {
|
||||
explanations_ = explanations;
|
||||
@@ -92,9 +99,14 @@ struct StatusPrinter : Status {
|
||||
/// An optional Explanations pointer, used to implement `-d explain`.
|
||||
Explanations* explanations_ = nullptr;
|
||||
|
||||
/// The custom progress status format to use.
|
||||
/// The custom progress status format to use (NINJA_STATUS or default).
|
||||
/// Null when `--status` is in effect; in that case `status_eval_` is used.
|
||||
const char* progress_status_format_;
|
||||
|
||||
/// Parsed `--status` format, evaluated against status variables on each
|
||||
/// update. Null when `--status` was not supplied.
|
||||
std::unique_ptr<EvalString> status_eval_;
|
||||
|
||||
template <size_t S>
|
||||
void SnprintfRate(double rate, char (&buf)[S], const char* format) const {
|
||||
if (rate == -1)
|
||||
|
||||
Reference in New Issue
Block a user