blob: bd334ed2aa83228ee7aa2f35418a2c77a3780771 [file] [log] [blame]
// 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 <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdlib>
#include <memory>
#ifdef _WIN32
#include "getopt.h"
#include <direct.h>
#include <windows.h>
#elif defined(_AIX)
#include "getopt.h"
#include <unistd.h>
#else
#include <getopt.h>
#include <unistd.h>
#endif
#include "browse.h"
#include "build.h"
#include "build_config.h"
#include "build_event_streamer.h"
#include "build_log.h"
#include "clean.h"
#include "debug_flags.h"
#include "deps_log.h"
#include "disk_interface.h"
#include "exit_status.h"
#include "graph.h"
#include "graphviz.h"
#include "ipc_utils.h"
#include "jobserver.h"
#include "jobserver_pool.h"
#include "json.h"
#include "manifest_parser.h"
#include "metrics.h"
#include "missing_deps.h"
#include "output_stream.h"
#include "output_stream_gzip.h"
#include "persistent_mode.h"
#include "resultstore_streamer.h"
#include "state.h"
#include "status.h"
#include "status_listener_group.h"
#include "status_to_chrome_trace.h"
#include "status_to_error_log.h"
#include "string_piece.h"
#include "subprocess.h"
#include "util.h"
#include "version.h"
using namespace std;
#ifdef _WIN32
// Defined in msvc_helper_main-win32.cc.
int MSVCHelperMain(int argc, char** argv);
// Defined in minidump-win32.cc.
void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep);
#endif
namespace {
struct Tool;
/// Command-line options.
struct Options {
/// Directory to change into before running.
const char* working_dir = nullptr;
/// Tool to run rather than building.
const Tool* tool = nullptr;
/// Whether duplicate rules for one target should warn or print an error.
bool dupe_edges_should_err = true;
/// Whether phony cycles should warn or print an error.
bool phony_cycle_should_err = false;
};
/// A special value return by NinjaMain::RunBeforeBuild() to
/// indicate that the build can proceed after loading the manifest
/// and the logs. Must be strictly negative.
static const int kBuildCanProceed = -1;
/// The Ninja main() loads up a series of data structures; various tools need
/// to poke into these, so store them as fields on an object.
struct NinjaMain : public BuildLogUser {
NinjaMain(const std::vector<std::string>& original_ninja_command,
const BuildConfig& config) :
original_ninja_command_(original_ninja_command), config_(&config),
status_(std::make_unique<StatusListenerGroup>()),
path_recording_disk_interface_(&disk_interface_),
start_time_millis_(GetTimeMillis()) {
status_->AddListener(std::unique_ptr<Status>(Status::factory(*config_)));
}
/// Command line used to run Ninja.
const std::vector<std::string> original_ninja_command_;
/// Build configuration set from flags (e.g. parallelism).
const BuildConfig* config_;
/// The Status instance to send update status messages.
Status* status() const { return status_.get(); }
std::unique_ptr<StatusListenerGroup> status_;
/// Path to the ninja tool from the full command.
const char* ninja_path() const {
return original_ninja_command_.front().c_str();
}
/// Completely reset state. This gets rid of the current build graph,
/// rule definitions, logs and timestamp caches.
void Reset() {
// Note: state_.Reset() does not fully reset the variable!
build_log_ = BuildLog();
// deps_log_ contains pointers into Node instance owned by state_, so
// reset it before it.
deps_log_ = DepsLog();
state_ = State();
start_time_millis_ = GetTimeMillis();
path_recording_disk_interface_.Reset();
disk_interface_.FlushCache();
}
/// Reset just enough state to allow a new (possibly incremental) build.
/// This keeps the build graph in memory, but resets its state properly
/// to avoid relying on previously computed data from a previous build
/// (e.g. timestamps, or undrained pools when the build was
/// user-interrupted).
void PrepareBuild(const BuildConfig& config) {
state_.Reset();
start_time_millis_ = GetTimeMillis();
config_ = &config;
// Reset status, since stdio was redirected when in persistent
// server process.
status_->Reset();
status_->AddListener(std::unique_ptr<Status>(Status::factory(config)));
disk_interface_.Sync();
}
/// Loaded state (rules, nodes).
State state_;
/// Functions for accessing the disk.
RealDiskInterface disk_interface_;
/// A DiskInterface instance that records which input files were opened
/// when loading manifests. Used later to shutdown a persistent server
/// automatically if any one of these files changed.
PathRecordingFileReader path_recording_disk_interface_;
/// The build directory, used for storing the build log etc.
string build_dir_;
BuildLog build_log_;
DepsLog deps_log_;
/// The type of functions that are the entry points to tools (subcommands).
typedef int (NinjaMain::*ToolFunc)(const Options*, int, char**);
/// Get the Node for a given command-line path, handling features like
/// spell correction.
Node* CollectTarget(const char* cpath, string* err);
/// CollectTarget for all command-line arguments, filling in \a targets.
bool CollectTargetsFromArgs(int argc, char* argv[],
vector<Node*>* targets, string* err);
// The various subcommands, run via "-t XXX".
int ToolGraph(const Options* options, int argc, char* argv[]);
int ToolQuery(const Options* options, int argc, char* argv[]);
int ToolDeps(const Options* options, int argc, char* argv[]);
int ToolMissingDeps(const Options* options, int argc, char* argv[]);
int ToolBrowse(const Options* options, int argc, char* argv[]);
int ToolMSVC(const Options* options, int argc, char* argv[]);
int ToolTargets(const Options* options, int argc, char* argv[]);
int ToolCommands(const Options* options, int argc, char* argv[]);
int ToolInputs(const Options* options, int argc, char* argv[]);
int ToolMultiInputs(const Options* options, int argc, char* argv[]);
int ToolOutputs(const Options* options, int argc, char* argv[]);
int ToolClean(const Options* options, int argc, char* argv[]);
int ToolCleanDead(const Options* options, int argc, char* argv[]);
int ToolCompilationDatabase(const Options* options, int argc, char* argv[]);
int ToolRecompact(const Options* options, int argc, char* argv[]);
int ToolRestat(const Options* options, int argc, char* argv[]);
int ToolUrtle(const Options* options, int argc, char** argv);
int ToolRules(const Options* options, int argc, char* argv[]);
int ToolWinCodePage(const Options* options, int argc, char* argv[]);
int ToolServer(const Options* options, int argc, char* argv[]);
/// Open the build log.
/// @return false on error.
bool OpenBuildLog(bool recompact_only = false);
/// Open the deps log: load it, then open for writing.
/// @return false on error.
bool OpenDepsLog(bool recompact_only = false);
/// Ensure the build directory exists, creating it if necessary.
/// @return false on error.
bool EnsureBuildDirExists();
/// Rebuild the manifest, if necessary.
/// Fills in \a err on error.
/// @return true if the manifest was rebuilt.
bool RebuildManifest(std::string* err, bool silent_dry_run = false);
/// Perform all operations before the build itself, i.e.
/// load the manifest, the build log, the deps log, and
/// any tool if needed. Return kBuildCanProceed to indicate that
/// the build can proceed after the call, or a positive process
/// exit code to tell Ninja to stop.
int RunBeforeBuild(const Options& options, int argc, char** argv);
/// For each edge, lookup in build log how long it took last time,
/// and record that in the edge itself. It will be used for ETA predicton.
void ParsePreviousElapsedTimes();
/// Build the targets listed on the command line.
/// @return an exit code.
ExitStatus RunBuild(int argc, char** argv);
/// Dump the output requested by '-d stats'.
void DumpMetrics();
virtual bool IsPathDead(StringPiece s) const {
Node* n = state_.LookupNode(s);
if (n && n->in_edge())
return false;
// Just checking n isn't enough: If an old output is both in the build log
// and in the deps log, it will have a Node object in state_. (It will also
// have an in edge if one of its inputs is another output that's in the deps
// log, but having a deps edge product an output that's input to another deps
// edge is rare, and the first recompaction will delete all old outputs from
// the deps log, and then a second recompaction will clear the build log,
// which seems good enough for this corner case.)
// Do keep entries around for files which still exist on disk, for
// generators that want to use this information.
string err;
TimeStamp mtime = disk_interface_.Stat(s.AsString(), &err);
if (mtime == -1)
Error("%s", err.c_str()); // Log and ignore Stat() errors.
return mtime == 0;
}
int64_t start_time_millis_;
};
/// Subtools, accessible via "-t foo".
struct Tool {
/// Short name of the tool.
const char* name;
/// Description (shown in "-t list").
const char* desc;
/// When to run the tool.
enum {
/// Run after parsing the command-line flags and potentially changing
/// the current working directory (as early as possible).
RUN_AFTER_FLAGS,
/// Run after loading build.ninja.
RUN_AFTER_LOAD,
/// Run after loading the build/deps logs.
RUN_AFTER_LOGS,
} when;
/// Implementation of the tool.
NinjaMain::ToolFunc func;
};
/// Print usage information.
void Usage(const BuildConfig& config) {
fprintf(
stderr,
"usage: ninja [options] [targets...]\n"
"\n"
"if targets are unspecified, builds the 'default' target (see manual).\n"
"\n"
"options:\n"
" --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"
"\n"
" -C DIR change to DIR before doing anything else\n"
" -f FILE specify input build file [default=build.ninja]\n"
"\n"
" -j N run N jobs in parallel (0 means infinity) [default=%d on "
"this system]\n"
" -k N keep going until N jobs fail (0 means infinity) [default=1]\n"
" -l N do not start new jobs if the load average is greater than N\n"
" -n dry run (don't run commands but act like they succeeded)\n"
"\n"
" -d MODE enable debugging (use '-d list' to list modes)\n"
" -t TOOL run a subtool (use '-t list' to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
" -w FLAG adjust warnings (use '-w list' to list warnings)\n"
"\n"
" --jobserver-pool enable GNU jobserver pool\n"
" --jobserver same as --jobserver-pool, deprecated, do not use\n"
"\n"
" --edge_weights_list FILE Read edge weights from FILE, for more\n"
" optimal scheduling of jobs.\n"
"\n"
"Build event streaming options:\n\n"
" These options generate files describing build events in various "
"formats.\n"
" File paths are relative to the Ninja build directory.\n"
" Use a .gz suffix to directly generate a gzip-compressed output "
"file.\n\n"
" --bes_output FILE Generate build_event_stream.proto binary "
"output.\n\n"
" --resultstore_output FILE Generate resultstore_upload.proto binary "
"output\n\n"
" --bes_metadata KEY=VALUE User-defined metadata about the build "
"that will be\n"
" embedded into --bes_output and "
"--resultstore_output\n"
" outputs (repeatable, cumulative)\n\n"
" --error_logging_output=FILE\n"
" Name of JSON file describing last build "
"errors.\n"
" Default is '.ninja_errors.json'\n\n"
" --chrome_trace FILE Generate Chrome trace output (JSON "
"array).\n\n",
kNinjaVersion, config.parallelism);
}
/// Choose a default value for the -j (parallelism) flag.
int GuessParallelism() {
switch (int processors = GetProcessorCount()) {
case 0:
case 1:
return 2;
case 2:
return 3;
default:
return processors + 2;
}
}
/// Rebuild the build manifest, if necessary.
/// If \arg silenty_dry_run is true, do not modify anything on disk.
/// Returns true if the manifest was rebuilt.
bool NinjaMain::RebuildManifest(std::string* err, bool silent_dry_run) {
std::string path = config_->input_file;
if (path.empty()) {
*err = "empty path";
return false;
}
uint64_t slash_bits; // Unused because this path is only used for lookup.
CanonicalizePath(&path, &slash_bits);
Node* node = state_.LookupNode(path);
if (!node)
return false;
BuildConfig config = *config_;
if (silent_dry_run) {
config.verbosity = BuildConfig::QUIET;
config.dry_run = true;
}
Builder builder(&state_, config, &build_log_, &deps_log_, &disk_interface_,
status(), start_time_millis_);
if (!builder.AddTarget(node, err))
return false;
if (builder.AlreadyUpToDate())
return false; // Not an error, but we didn't rebuild.
if (builder.Build(err) != ExitSuccess)
return false;
// The manifest was only rebuilt if it is now dirty (it may have been cleaned
// by a restat).
if (!node->dirty()) {
// Reset the state to prevent problems like
// https://github.com/ninja-build/ninja/issues/874
state_.Reset();
return false;
}
return true;
}
void NinjaMain::ParsePreviousElapsedTimes() {
for (Edge* edge : state_.edges_) {
for (Node* out : edge->outputs_) {
BuildLog::LogEntry* log_entry = build_log_.LookupByOutput(out->path());
if (!log_entry)
continue; // Maybe we'll have log entry for next output of this edge?
edge->prev_elapsed_time_millis =
log_entry->end_time - log_entry->start_time;
break; // Onto next edge.
}
}
}
Node* NinjaMain::CollectTarget(const char* cpath, string* err) {
string path = cpath;
if (path.empty()) {
*err = "empty path";
return NULL;
}
uint64_t slash_bits;
CanonicalizePath(&path, &slash_bits);
// Special syntax: "foo.cc^" means "the first output of foo.cc".
bool first_dependent = false;
if (!path.empty() && path[path.size() - 1] == '^') {
path.resize(path.size() - 1);
first_dependent = true;
}
Node* node = state_.LookupNode(path);
if (node) {
if (first_dependent) {
if (node->out_edges().empty()) {
Node* rev_deps = deps_log_.GetFirstReverseDepsNode(node);
if (!rev_deps) {
*err = "'" + path + "' has no out edge";
return NULL;
}
node = rev_deps;
} else {
Edge* edge = node->out_edges()[0];
if (edge->outputs_.empty()) {
edge->Dump();
Fatal("edge has no outputs");
}
node = edge->outputs_[0];
}
}
return node;
} else {
*err =
"unknown target '" + Node::PathDecanonicalized(path, slash_bits) + "'";
if (path == "clean") {
*err += ", did you mean 'ninja -t clean'?";
} else if (path == "help") {
*err += ", did you mean 'ninja -h'?";
} else {
Node* suggestion = state_.SpellcheckNode(path);
if (suggestion) {
*err += ", did you mean '" + suggestion->path() + "'?";
}
}
return NULL;
}
}
bool NinjaMain::CollectTargetsFromArgs(int argc, char* argv[],
vector<Node*>* targets, string* err) {
if (argc == 0) {
*targets = state_.DefaultNodes(err);
return err->empty();
}
for (int i = 0; i < argc; ++i) {
Node* node = CollectTarget(argv[i], err);
if (node == NULL)
return false;
targets->push_back(node);
}
return true;
}
int NinjaMain::ToolGraph(const Options* options, int argc, char* argv[]) {
vector<Node*> nodes;
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
GraphViz graph(&state_, &disk_interface_);
graph.Start();
for (vector<Node*>::const_iterator n = nodes.begin(); n != nodes.end(); ++n)
graph.AddTarget(*n);
graph.Finish();
return 0;
}
int NinjaMain::ToolQuery(const Options* options, int argc, char* argv[]) {
if (argc == 0) {
Error("expected a target to query");
return 1;
}
DyndepLoader dyndep_loader(&state_, &disk_interface_);
for (int i = 0; i < argc; ++i) {
string err;
Node* node = CollectTarget(argv[i], &err);
if (!node) {
Error("%s", err.c_str());
return 1;
}
printf("%s:\n", node->path().c_str());
if (Edge* edge = node->in_edge()) {
if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
if (!dyndep_loader.LoadDyndeps(edge->dyndep_, &err)) {
Warning("%s\n", err.c_str());
}
}
printf(" input: %s\n", edge->rule_->name().c_str());
for (int in = 0; in < (int)edge->inputs_.size(); in++) {
const char* label = "";
if (edge->is_implicit(in))
label = "| ";
else if (edge->is_order_only(in))
label = "|| ";
printf(" %s%s\n", label, edge->inputs_[in]->path().c_str());
}
if (!edge->validations_.empty()) {
printf(" validations:\n");
for (std::vector<Node*>::iterator validation = edge->validations_.begin();
validation != edge->validations_.end(); ++validation) {
printf(" %s\n", (*validation)->path().c_str());
}
}
}
printf(" outputs:\n");
for (vector<Edge*>::const_iterator edge = node->out_edges().begin();
edge != node->out_edges().end(); ++edge) {
for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
out != (*edge)->outputs_.end(); ++out) {
printf(" %s\n", (*out)->path().c_str());
}
}
const std::vector<Edge*> validation_edges = node->validation_out_edges();
if (!validation_edges.empty()) {
printf(" validation for:\n");
for (std::vector<Edge*>::const_iterator edge = validation_edges.begin();
edge != validation_edges.end(); ++edge) {
for (vector<Node*>::iterator out = (*edge)->outputs_.begin();
out != (*edge)->outputs_.end(); ++out) {
printf(" %s\n", (*out)->path().c_str());
}
}
}
}
return 0;
}
#if defined(NINJA_HAVE_BROWSE)
int NinjaMain::ToolBrowse(const Options* options, int argc, char* argv[]) {
RunBrowsePython(&state_, ninja_path(), config_->input_file.c_str(), argc,
argv);
// If we get here, the browse failed.
return 1;
}
#else
int NinjaMain::ToolBrowse(const Options*, int, char**) {
Fatal("browse tool not supported on this platform");
return 1;
}
#endif
#if defined(_WIN32)
int NinjaMain::ToolMSVC(const Options* options, int argc, char* argv[]) {
// Reset getopt: push one argument onto the front of argv, reset optind.
argc++;
argv--;
optind = 0;
return MSVCHelperMain(argc, argv);
}
#endif
int ToolTargetsList(const vector<Node*>& nodes, int depth, int indent) {
for (vector<Node*>::const_iterator n = nodes.begin();
n != nodes.end();
++n) {
for (int i = 0; i < indent; ++i)
printf(" ");
const char* target = (*n)->path().c_str();
if ((*n)->in_edge()) {
printf("%s: %s\n", target, (*n)->in_edge()->rule_->name().c_str());
if (depth > 1 || depth <= 0)
ToolTargetsList((*n)->in_edge()->inputs_, depth - 1, indent + 1);
} else {
printf("%s\n", target);
}
}
return 0;
}
int ToolTargetsSourceList(State* state) {
for (vector<Edge*>::iterator e = state->edges_.begin();
e != state->edges_.end(); ++e) {
for (vector<Node*>::iterator inps = (*e)->inputs_.begin();
inps != (*e)->inputs_.end(); ++inps) {
if (!(*inps)->in_edge())
printf("%s\n", (*inps)->path().c_str());
}
}
return 0;
}
int ToolTargetsList(State* state, const string& rule_name) {
set<string> rules;
// Gather the outputs.
for (vector<Edge*>::iterator e = state->edges_.begin();
e != state->edges_.end(); ++e) {
if ((*e)->rule_->name() == rule_name) {
for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
out_node != (*e)->outputs_.end(); ++out_node) {
rules.insert((*out_node)->path());
}
}
}
// Print them.
for (set<string>::const_iterator i = rules.begin();
i != rules.end(); ++i) {
printf("%s\n", (*i).c_str());
}
return 0;
}
int ToolTargetsList(State* state) {
for (vector<Edge*>::iterator e = state->edges_.begin();
e != state->edges_.end(); ++e) {
for (vector<Node*>::iterator out_node = (*e)->outputs_.begin();
out_node != (*e)->outputs_.end(); ++out_node) {
printf("%s: %s\n",
(*out_node)->path().c_str(),
(*e)->rule_->name().c_str());
}
}
return 0;
}
int NinjaMain::ToolDeps(const Options* options, int argc, char** argv) {
vector<Node*> nodes;
if (argc == 0) {
for (vector<Node*>::const_iterator ni = deps_log_.nodes().begin();
ni != deps_log_.nodes().end(); ++ni) {
if (DepsLog::IsDepsEntryLiveFor(*ni))
nodes.push_back(*ni);
}
} else {
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
}
RealDiskInterface disk_interface;
for (vector<Node*>::iterator it = nodes.begin(), end = nodes.end();
it != end; ++it) {
DepsLog::Deps* deps = deps_log_.GetDeps(*it);
if (!deps) {
printf("%s: deps not found\n", (*it)->path().c_str());
continue;
}
string err;
TimeStamp mtime = disk_interface.Stat((*it)->path(), &err);
if (mtime == -1)
Error("%s", err.c_str()); // Log and ignore Stat() errors;
printf("%s: #deps %d, deps mtime %" PRId64 " (%s)\n",
(*it)->path().c_str(), deps->node_count, deps->mtime,
(!mtime || mtime > deps->mtime ? "STALE":"VALID"));
for (int i = 0; i < deps->node_count; ++i)
printf(" %s\n", deps->nodes[i]->path().c_str());
printf("\n");
}
return 0;
}
int NinjaMain::ToolMissingDeps(const Options* options, int argc, char** argv) {
vector<Node*> nodes;
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
RealDiskInterface disk_interface;
MissingDependencyPrinter printer;
MissingDependencyScanner scanner(&printer, &deps_log_, &state_,
&disk_interface);
for (vector<Node*>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
scanner.ProcessNode(*it);
}
scanner.PrintStats();
if (scanner.HadMissingDeps())
return 3;
return 0;
}
int NinjaMain::ToolTargets(const Options* options, int argc, char* argv[]) {
int depth = 1;
if (argc >= 1) {
string mode = argv[0];
if (mode == "rule") {
string rule;
if (argc > 1)
rule = argv[1];
if (rule.empty())
return ToolTargetsSourceList(&state_);
else
return ToolTargetsList(&state_, rule);
} else if (mode == "depth") {
if (argc > 1)
depth = atoi(argv[1]);
} else if (mode == "all") {
return ToolTargetsList(&state_);
} else {
const char* suggestion =
SpellcheckString(mode.c_str(), "rule", "depth", "all", NULL);
if (suggestion) {
Error("unknown target tool mode '%s', did you mean '%s'?",
mode.c_str(), suggestion);
} else {
Error("unknown target tool mode '%s'", mode.c_str());
}
return 1;
}
}
string err;
vector<Node*> root_nodes = state_.RootNodes(&err);
if (err.empty()) {
return ToolTargetsList(root_nodes, depth, 0);
} else {
Error("%s", err.c_str());
return 1;
}
}
int NinjaMain::ToolRules(const Options* options, int argc, char* argv[]) {
// Parse options.
// The rules tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "rules".
argc++;
argv--;
bool print_description = false;
optind = 1;
int opt;
while ((opt = getopt(argc, argv, const_cast<char*>("hd"))) != -1) {
switch (opt) {
case 'd':
print_description = true;
break;
case 'h':
default:
printf("usage: ninja -t rules [options]\n"
"\n"
"options:\n"
" -d also print the description of the rule\n"
" -h print this message\n"
);
return 1;
}
}
argv += optind;
argc -= optind;
// Print rules
for (const auto& pair : state_.bindings().GetRules()) {
printf("%s", pair.first.c_str());
if (print_description) {
const Rule* rule = pair.second.get();
const EvalString* description = rule->GetBinding("description");
if (description != NULL) {
printf(": %s", description->Unparse().c_str());
}
}
printf("\n");
fflush(stdout);
}
return 0;
}
#ifdef _WIN32
int NinjaMain::ToolWinCodePage(const Options* options, int argc, char* argv[]) {
if (argc != 0) {
printf("usage: ninja -t wincodepage\n");
return 1;
}
printf("Build file encoding: %s\n", GetACP() == CP_UTF8? "UTF-8" : "ANSI");
return 0;
}
#endif
enum PrintCommandMode { PCM_Single, PCM_All };
void PrintCommands(Edge* edge, EdgeSet* seen, PrintCommandMode mode) {
if (!edge)
return;
if (!seen->insert(edge).second)
return;
if (mode == PCM_All) {
for (vector<Node*>::iterator in = edge->inputs_.begin();
in != edge->inputs_.end(); ++in)
PrintCommands((*in)->in_edge(), seen, mode);
}
if (!edge->is_phony())
puts(edge->EvaluateCommand().c_str());
}
int NinjaMain::ToolCommands(const Options* options, int argc, char* argv[]) {
// The commands tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "commands".
++argc;
--argv;
PrintCommandMode mode = PCM_All;
optind = 1;
int opt;
while ((opt = getopt(argc, argv, const_cast<char*>("hs"))) != -1) {
switch (opt) {
case 's':
mode = PCM_Single;
break;
case 'h':
default:
printf("usage: ninja -t commands [options] [targets]\n"
"\n"
"options:\n"
" -s only print the final command to build [target], not the whole chain\n"
);
return 1;
}
}
argv += optind;
argc -= optind;
vector<Node*> nodes;
string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
EdgeSet seen;
for (vector<Node*>::iterator in = nodes.begin(); in != nodes.end(); ++in)
PrintCommands((*in)->in_edge(), &seen, mode);
return 0;
}
int NinjaMain::ToolInputs(const Options* options, int argc, char* argv[]) {
// The inputs tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "inputs".
argc++;
argv--;
bool print0 = false;
bool shell_escape = true;
bool dependency_order = false;
optind = 1;
int opt;
bool include_depfile_inputs = false;
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
{ "no-shell-escape", no_argument, NULL, 'E' },
{ "print0", no_argument, NULL, '0' },
{ "depfile", no_argument, NULL, 'D' },
{ "dependency-order", no_argument, NULL,
'd' },
{ NULL, 0, NULL, 0 } };
while ((opt = getopt_long(argc, argv, "h0EDd", kLongOptions, NULL)) != -1) {
switch (opt) {
case 'D':
include_depfile_inputs = true;
break;
case 'd':
dependency_order = true;
break;
case 'E':
shell_escape = false;
break;
case '0':
print0 = true;
break;
case 'h':
default:
// clang-format off
printf(
"Usage '-t inputs [options] [targets]\n"
"\n"
"List all inputs used for a set of targets, sorted in dependency order.\n"
"Note that by default, results are shell escaped, and sorted alphabetically,\n"
"and never include validation target paths.\n\n"
"Options:\n"
" -h, --help Print this message.\n"
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
" -E, --no-shell-escape Do not shell escape the result.\n"
" -d, --dependency-order Sort results by dependency order.\n"
" -D, --depfile Include depfile inputs in the result.\n"
);
// clang-format on
return 1;
}
}
argv += optind;
argc -= optind;
std::vector<Node*> nodes;
std::string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
std::unique_ptr<ImplicitDepLoader> implicit_dep_loader;
if (include_depfile_inputs) {
implicit_dep_loader.reset(new ImplicitDepLoader(
&state_, &deps_log_, &disk_interface_, nullptr, nullptr));
}
InputsCollector collector(implicit_dep_loader.get());
for (const Node* node : nodes)
collector.VisitNode(node);
std::vector<std::string> inputs =
GetNodesAsStrings(collector.inputs(), shell_escape);
if (!dependency_order)
std::sort(inputs.begin(), inputs.end());
if (print0) {
for (const std::string& input : inputs) {
fwrite(input.c_str(), input.size(), 1, stdout);
fputc('\0', stdout);
}
fflush(stdout);
} else {
for (const std::string& input : inputs)
puts(input.c_str());
}
return 0;
}
int NinjaMain::ToolMultiInputs(const Options* options, int argc, char* argv[]) {
// The inputs tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "inputs".
argc++;
argv--;
optind = 1;
int opt;
char terminator = '\n';
const char* delimiter = "\t";
bool include_depfile_inputs = false;
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
{ "delimiter", required_argument, NULL, 'd' },
{ "depfile", no_argument, NULL, 'D' },
{ "print0", no_argument, NULL, '0' },
{ NULL, 0, NULL, 0 } };
while ((opt = getopt_long(argc, argv, "Dd:h0", kLongOptions, NULL)) != -1) {
switch (opt) {
case 'D':
include_depfile_inputs = true;
break;
case 'd':
delimiter = optarg;
break;
case '0':
terminator = '\0';
break;
case 'h':
default:
// clang-format off
printf(
"Usage '-t multi-inputs [options] [targets]\n"
"\n"
"Print one or more sets of inputs required to build targets, sorted in dependency order.\n"
"The tool works like inputs tool but with addition of the target for each line.\n"
"The output will be a series of lines with the following elements:\n"
"<target> <delimiter> <input> <terminator>\n"
"Note that a given input may appear for several targets if it is used by more than one targets.\n"
"Options:\n"
" -h, --help Print this message.\n"
" -d --delimiter=DELIM Use DELIM instead of TAB for field delimiter.\n"
" -D, --depfile Include depfile inputs in the result.\n"
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
);
// clang-format on
return 1;
}
}
argv += optind;
argc -= optind;
std::vector<Node*> nodes;
std::string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
std::unique_ptr<ImplicitDepLoader> implicit_dep_loader;
if (include_depfile_inputs) {
implicit_dep_loader.reset(new ImplicitDepLoader(
&state_, &deps_log_, &disk_interface_, nullptr, nullptr));
}
for (const Node* node : nodes) {
InputsCollector collector(implicit_dep_loader.get());
collector.VisitNode(node);
std::vector<std::string> inputs = GetNodesAsStrings(collector.inputs());
for (const std::string& input : inputs) {
printf("%s%s%s", node->path().c_str(), delimiter, input.c_str());
fputc(terminator, stdout);
}
}
fflush(stdout);
return 0;
}
int NinjaMain::ToolOutputs(const Options* options, int argc, char* argv[]) {
// The tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "outputs".
argc++;
argv--;
char terminator = '\n';
bool partition = false;
const char* delimiter = "\t";
optind = 1;
int opt;
const option kLongOptions[] = { { "help", no_argument, NULL, 'h' },
{ "print0", no_argument, NULL, '0' },
{ "partition", no_argument, NULL, 'p' },
{ "delimiter", required_argument, NULL, 'd' },
{ NULL, 0, NULL, 0 } };
while ((opt = getopt_long(argc, argv, "h0pd:", kLongOptions, NULL)) != -1) {
switch (opt) {
case '0':
terminator = '\0';
break;
case 'd':
delimiter = optarg;
break;
case 'p':
partition = true;
break;
case 'h':
default:
// clang-format off
printf(
"Usage '-t outputs [options] [targets]\n"
"\n"
"List all edge output files generated when buidling a set of targets.\n"
"By default this prints all output paths, one per line in dependency order.\n"
"Using --partition changes the format of each line to:\n"
"<target> <delimiter> <path> <terminator>\n"
"Where <delimiter> is a TAB character by default.\n"
"Options:\n"
" -h, --help Print this message.\n"
" -0, --print0 Use \\0, instead of \\n as a line terminator.\n"
" -p, --partition Partition the outputs per target.\n"
" -d --delimiter=DELIM Use DELIM instead of TAB for partition delimiter.\n"
);
// clang-format on
return 1;
}
}
argv += optind;
argc -= optind;
std::vector<Node*> nodes;
std::string err;
if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) {
Error("%s", err.c_str());
return 1;
}
if (partition) {
for (const Node* node : nodes) {
OutputsCollector collector;
collector.VisitNode(node);
std::string target = node->PathDecanonicalized();
for (const Node* output : collector.outputs()) {
std::string path = output->PathDecanonicalized();
printf("%s%s%s%c", target.c_str(), delimiter, path.c_str(), terminator);
}
}
} else {
OutputsCollector collector;
for (const Node* node : nodes)
collector.VisitNode(node);
for (const Node* output : collector.outputs()) {
std::string path = output->PathDecanonicalized();
printf("%s%c", path.c_str(), terminator);
}
}
fflush(stdout);
return 0;
}
int NinjaMain::ToolClean(const Options* options, int argc, char* argv[]) {
// The clean tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "clean".
argc++;
argv--;
bool generator = false;
bool clean_rules = false;
optind = 1;
int opt;
while ((opt = getopt(argc, argv, const_cast<char*>("hgr"))) != -1) {
switch (opt) {
case 'g':
generator = true;
break;
case 'r':
clean_rules = true;
break;
case 'h':
default:
printf("usage: ninja -t clean [options] [targets]\n"
"\n"
"options:\n"
" -g also clean files marked as ninja generator output\n"
" -r interpret targets as a list of rules to clean instead\n"
);
return 1;
}
}
argv += optind;
argc -= optind;
if (clean_rules && argc == 0) {
Error("expected a rule to clean");
return 1;
}
Cleaner cleaner(&state_, *config_, &disk_interface_);
if (argc >= 1) {
if (clean_rules)
return cleaner.CleanRules(argc, argv);
else
return cleaner.CleanTargets(argc, argv);
} else {
return cleaner.CleanAll(generator);
}
}
int NinjaMain::ToolCleanDead(const Options* options, int argc, char* argv[]) {
Cleaner cleaner(&state_, *config_, &disk_interface_);
return cleaner.CleanDead(build_log_.entries());
}
enum EvaluateCommandMode {
ECM_NORMAL,
ECM_EXPAND_RSPFILE
};
std::string EvaluateCommandWithRspfile(const Edge* edge,
const EvaluateCommandMode mode) {
string command = edge->EvaluateCommand();
if (mode == ECM_NORMAL)
return command;
string rspfile = edge->GetUnescapedRspfile();
if (rspfile.empty())
return command;
size_t index = command.find(rspfile);
if (index == 0 || index == string::npos || command[index - 1] != '@')
return command;
string rspfile_content = edge->GetBinding("rspfile_content");
size_t newline_index = 0;
while ((newline_index = rspfile_content.find('\n', newline_index)) !=
string::npos) {
rspfile_content.replace(newline_index, 1, 1, ' ');
++newline_index;
}
command.replace(index - 1, rspfile.length() + 1, rspfile_content);
return command;
}
void printCompdb(const char* const directory, const Edge* const edge,
const EvaluateCommandMode eval_mode) {
printf("\n {\n \"directory\": \"");
PrintJSONString(directory);
printf("\",\n \"command\": \"");
PrintJSONString(EvaluateCommandWithRspfile(edge, eval_mode));
printf("\",\n \"file\": \"");
PrintJSONString(edge->inputs_[0]->path());
printf("\",\n \"output\": \"");
PrintJSONString(edge->outputs_[0]->path());
printf("\"\n }");
}
int NinjaMain::ToolCompilationDatabase(const Options* options, int argc,
char* argv[]) {
// The compdb tool uses getopt, and expects argv[0] to contain the name of
// the tool, i.e. "compdb".
argc++;
argv--;
EvaluateCommandMode eval_mode = ECM_NORMAL;
optind = 1;
int opt;
while ((opt = getopt(argc, argv, const_cast<char*>("hx"))) != -1) {
switch(opt) {
case 'x':
eval_mode = ECM_EXPAND_RSPFILE;
break;
case 'h':
default:
printf(
"usage: ninja -t compdb [options] [rules]\n"
"\n"
"options:\n"
" -x expand @rspfile style response file invocations\n"
);
return 1;
}
}
argv += optind;
argc -= optind;
bool first = true;
vector<char> cwd;
char* success = NULL;
do {
cwd.resize(cwd.size() + 1024);
errno = 0;
success = getcwd(&cwd[0], cwd.size());
} while (!success && errno == ERANGE);
if (!success) {
Error("cannot determine working directory: %s", strerror(errno));
return 1;
}
putchar('[');
for (vector<Edge*>::iterator e = state_.edges_.begin();
e != state_.edges_.end(); ++e) {
if ((*e)->inputs_.empty())
continue;
if (argc == 0) {
if (!first) {
putchar(',');
}
printCompdb(&cwd[0], *e, eval_mode);
first = false;
} else {
for (int i = 0; i != argc; ++i) {
if ((*e)->rule_->name() == argv[i]) {
if (!first) {
putchar(',');
}
printCompdb(&cwd[0], *e, eval_mode);
first = false;
}
}
}
}
puts("\n]");
return 0;
}
int NinjaMain::ToolRecompact(const Options* options, int argc, char* argv[]) {
if (!EnsureBuildDirExists())
return 1;
if (!OpenBuildLog(/*recompact_only=*/true) ||
!OpenDepsLog(/*recompact_only=*/true))
return 1;
return 0;
}
int NinjaMain::ToolRestat(const Options* options, int argc, char* argv[]) {
// The restat tool uses getopt, and expects argv[0] to contain the name of the
// tool, i.e. "restat"
argc++;
argv--;
optind = 1;
int opt;
while ((opt = getopt(argc, argv, const_cast<char*>("h"))) != -1) {
switch (opt) {
case 'h':
default:
printf("usage: ninja -t restat [outputs]\n");
return 1;
}
}
argv += optind;
argc -= optind;
if (!EnsureBuildDirExists())
return 1;
string log_path = ".ninja_log";
if (!build_dir_.empty())
log_path = build_dir_ + "/" + log_path;
string err;
const LoadStatus status = build_log_.Load(log_path, &err);
if (status == LOAD_ERROR) {
Error("loading build log %s: %s", log_path.c_str(), err.c_str());
return EXIT_FAILURE;
}
if (status == LOAD_NOT_FOUND) {
// Nothing to restat, ignore this
return EXIT_SUCCESS;
}
if (!err.empty()) {
// Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
Warning("%s", err.c_str());
err.clear();
}
bool success = build_log_.Restat(log_path, disk_interface_, argc, argv, &err);
if (!success) {
Error("failed recompaction: %s", err.c_str());
return EXIT_FAILURE;
}
if (!config_->dry_run) {
if (!build_log_.OpenForWrite(log_path, *this, &err)) {
Error("opening build log: %s", err.c_str());
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
int NinjaMain::ToolUrtle(const Options* options, int argc, char** argv) {
// RLE encoded.
const char* urtle =
" 13 ,3;2!2;\n8 ,;<11!;\n5 `'<10!(2`'2!\n11 ,6;, `\\. `\\9 .,c13$ec,.\n6 "
",2;11!>; `. ,;!2> .e8$2\".2 \"?7$e.\n <:<8!'` 2.3,.2` ,3!' ;,(?7\";2!2'<"
"; `?6$PF ,;,\n2 `'4!8;<!3'`2 3! ;,`'2`2'3!;4!`2.`!;2 3,2 .<!2'`).\n5 3`5"
"'2`9 `!2 `4!><3;5! J2$b,`!>;2!:2!`,d?b`!>\n26 `'-;,(<9!> $F3 )3.:!.2 d\""
"2 ) !>\n30 7`2'<3!- \"=-='5 .2 `2-=\",!>\n25 .ze9$er2 .,cd16$bc.'\n22 .e"
"14$,26$.\n21 z45$c .\n20 J50$c\n20 14$P\"`?34$b\n20 14$ dbc `2\"?22$?7$c"
"\n20 ?18$c.6 4\"8?4\" c8$P\n9 .2,.8 \"20$c.3 ._14 J9$\n .2,2c9$bec,.2 `?"
"21$c.3`4%,3%,3 c8$P\"\n22$c2 2\"?21$bc2,.2` .2,c7$P2\",cb\n23$b bc,.2\"2"
"?14$2F2\"5?2\",J5$P\" ,zd3$\n24$ ?$3?%3 `2\"2?12$bcucd3$P3\"2 2=7$\n23$P"
"\" ,3;<5!>2;,. `4\"6?2\"2 ,9;, `\"?2$\n";
int count = 0;
for (const char* p = urtle; *p; p++) {
if ('0' <= *p && *p <= '9') {
count = count*10 + *p - '0';
} else {
for (int i = 0; i < max(count, 1); ++i)
printf("%c", *p);
count = 0;
}
}
return 0;
}
int NinjaMain::ToolServer(const Options* options, int argc, char* argv[]) {
if (argc == 0 || !strcmp(argv[0], "help")) {
printf(
"usage: ninja -t server <command>\n"
"\n"
"commands:\n"
" help print this message\n"
" status print server status\n"
" stop stop server\n"
" pid print server pid, or -1\n");
return 1;
}
std::string work_dir = GetCurrentDir();
const char* command = argv[0];
if (!strcmp(command, "status")) {
PersistentMode::Status status = PersistentMode::GetCurrentProcessStatus();
if (status == PersistentMode::Disabled)
printf("persistent mode disabled\n");
else if (status == PersistentMode::IsClient) {
bool running = PersistentMode::Client().IsServerRunning(work_dir);
if (running)
printf("server is running for %s\n", work_dir.c_str());
else
printf("no server found for %s\n", work_dir.c_str());
} else {
printf("this is a server process for %s\n", work_dir.c_str());
}
return 0;
}
if (!strcmp(command, "stop")) {
std::string err;
bool stopped = PersistentMode::Client().StopServer(work_dir, &err);
if (stopped)
printf("server stopped for %s\n", work_dir.c_str());
else
printf("no server found for %s\n", work_dir.c_str());
return 0;
}
if (!strcmp(command, "pid")) {
int server_pid = PersistentMode::Client().GetServerPidFor(work_dir);
printf("%d\n", server_pid);
return 0;
}
fprintf(stderr, "Invalid command %s, see `-t server help`.\n", command);
return 1;
}
/// Find the function to execute for \a tool_name and return it via \a func.
/// Returns a Tool, or NULL if Ninja should exit.
const Tool* ChooseTool(const string& tool_name) {
static const Tool kTools[] = {
{ "browse", "browse dependency graph in a web browser",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse },
#ifdef _WIN32
{ "msvc", "build helper for MSVC cl.exe (DEPRECATED)",
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC },
#endif
{ "clean", "clean built files", Tool::RUN_AFTER_LOAD,
&NinjaMain::ToolClean },
{ "commands", "list all commands required to rebuild given targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands },
{ "inputs", "list all inputs required to rebuild given targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolInputs },
{ "multi-inputs",
"print one or more sets of inputs required to build targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolMultiInputs },
{ "outputs",
"list all edge outputs generated when building a given set of targets",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolOutputs },
{ "deps", "show dependencies stored in the deps log", Tool::RUN_AFTER_LOGS,
&NinjaMain::ToolDeps },
{ "missingdeps", "check deps log dependencies on generated files",
Tool::RUN_AFTER_LOGS, &NinjaMain::ToolMissingDeps },
{ "graph", "output graphviz dot file for targets", Tool::RUN_AFTER_LOAD,
&NinjaMain::ToolGraph },
{ "query", "show inputs/outputs for a path", Tool::RUN_AFTER_LOGS,
&NinjaMain::ToolQuery },
{ "targets", "list targets by their rule or depth in the DAG",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolTargets },
{ "compdb", "dump JSON compilation database to stdout",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase },
{ "recompact", "recompacts ninja-internal data structures",
Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRecompact },
{ "restat", "restats all outputs in the build log", Tool::RUN_AFTER_FLAGS,
&NinjaMain::ToolRestat },
{ "rules", "list all rules", Tool::RUN_AFTER_LOAD, &NinjaMain::ToolRules },
{ "cleandead",
"clean built files that are no longer produced by the manifest",
Tool::RUN_AFTER_LOGS, &NinjaMain::ToolCleanDead },
{ "urtle", NULL, Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle },
{ "server", "interact with persistent server", Tool::RUN_AFTER_FLAGS,
&NinjaMain::ToolServer },
#ifdef _WIN32
{ "wincodepage", "print the Windows code page used by ninja",
Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolWinCodePage },
#endif
{ NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
};
if (tool_name == "list") {
printf("ninja subtools:\n");
for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
if (tool->desc)
printf("%11s %s\n", tool->name, tool->desc);
}
return NULL;
}
for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
if (tool->name == tool_name)
return tool;
}
vector<const char*> words;
for (const Tool* tool = &kTools[0]; tool->name; ++tool)
words.push_back(tool->name);
const char* suggestion = SpellcheckStringV(tool_name, words);
if (suggestion) {
Fatal("unknown tool '%s', did you mean '%s'?",
tool_name.c_str(), suggestion);
} else {
Fatal("unknown tool '%s'", tool_name.c_str());
}
return NULL; // Not reached.
}
/// Enable a debugging mode. Returns false if Ninja should exit instead
/// of continuing.
bool DebugEnable(const string& name) {
if (name == "list") {
printf("debugging modes:\n"
" stats print operation counts/timing info\n"
" explain explain what caused a command to execute\n"
" keepdepfile don't delete depfiles after they're read by ninja\n"
" keeprsp don't delete @response files on success\n"
#ifdef _WIN32
" nostatcache don't batch stat() calls per directory and cache them\n"
#endif
"multiple modes can be enabled via -d FOO -d BAR\n");
return false;
} else if (name == "stats") {
g_metrics = new Metrics;
return true;
} else if (name == "explain") {
g_explaining = true;
return true;
} else if (name == "keepdepfile") {
g_keep_depfile = true;
return true;
} else if (name == "keeprsp") {
g_keep_rsp = true;
return true;
} else if (name == "nostatcache") {
g_experimental_statcache = false;
return true;
} else {
const char* suggestion =
SpellcheckString(name.c_str(),
"stats", "explain", "keepdepfile", "keeprsp",
"nostatcache", NULL);
if (suggestion) {
Error("unknown debug setting '%s', did you mean '%s'?",
name.c_str(), suggestion);
} else {
Error("unknown debug setting '%s'", name.c_str());
}
return false;
}
}
/// Set a warning flag. Returns false if Ninja should exit instead of
/// continuing.
bool WarningEnable(const string& name, Options* options) {
if (name == "list") {
printf("warning flags:\n"
" phonycycle={err,warn} phony build statement references itself\n"
);
return false;
} else if (name == "dupbuild=err") {
options->dupe_edges_should_err = true;
return true;
} else if (name == "dupbuild=warn") {
options->dupe_edges_should_err = false;
return true;
} else if (name == "phonycycle=err") {
options->phony_cycle_should_err = true;
return true;
} else if (name == "phonycycle=warn") {
options->phony_cycle_should_err = false;
return true;
} else if (name == "depfilemulti=err" || name == "depfilemulti=warn") {
Warning("deprecated warning 'depfilemulti'");
return true;
} else {
const char* suggestion =
SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
"phonycycle=err", "phonycycle=warn", NULL);
if (suggestion) {
Error("unknown warning flag '%s', did you mean '%s'?",
name.c_str(), suggestion);
} else {
Error("unknown warning flag '%s'", name.c_str());
}
return false;
}
}
bool NinjaMain::OpenBuildLog(bool recompact_only) {
string log_path = ".ninja_log";
if (!build_dir_.empty())
log_path = build_dir_ + "/" + log_path;
string err;
const LoadStatus status = build_log_.Load(log_path, &err);
if (status == LOAD_ERROR) {
Error("loading build log %s: %s", log_path.c_str(), err.c_str());
return false;
}
if (!err.empty()) {
// Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
Warning("%s", err.c_str());
err.clear();
}
if (recompact_only) {
if (status == LOAD_NOT_FOUND) {
return true;
}
bool success = build_log_.Recompact(log_path, *this, &err);
if (!success)
Error("failed recompaction: %s", err.c_str());
return success;
}
if (!config_->dry_run) {
if (!build_log_.OpenForWrite(log_path, *this, &err)) {
Error("opening build log: %s", err.c_str());
return false;
}
}
return true;
}
/// Open the deps log: load it, then open for writing.
/// @return false on error.
bool NinjaMain::OpenDepsLog(bool recompact_only) {
string path = ".ninja_deps";
if (!build_dir_.empty())
path = build_dir_ + "/" + path;
string err;
const LoadStatus status = deps_log_.Load(path, &state_, &err);
if (status == LOAD_ERROR) {
Error("loading deps log %s: %s", path.c_str(), err.c_str());
return false;
}
if (!err.empty()) {
// Hack: Load() can return a warning via err by returning LOAD_SUCCESS.
Warning("%s", err.c_str());
err.clear();
}
if (recompact_only) {
if (status == LOAD_NOT_FOUND) {
return true;
}
bool success = deps_log_.Recompact(path, &err);
if (!success)
Error("failed recompaction: %s", err.c_str());
return success;
}
if (!config_->dry_run) {
if (!deps_log_.OpenForWrite(path, &err)) {
Error("opening deps log: %s", err.c_str());
return false;
}
}
return true;
}
void NinjaMain::DumpMetrics() {
g_metrics->Report();
printf("\n");
int count = (int)state_.paths_.size();
int buckets = (int)state_.paths_.bucket_count();
printf("path->node hash load %.2f (%d entries / %d buckets)\n",
count / (double) buckets, count, buckets);
printf("paths: %zu, edges: %zu\n", static_cast<size_t>(state_.paths_.size()),
state_.edges_.size());
printf("build_log_entries %zu, deps_log_paths %zu\n", build_log_.size(),
deps_log_.size());
}
bool NinjaMain::EnsureBuildDirExists() {
build_dir_ = state_.bindings().LookupVariable("builddir");
if (!build_dir_.empty() && !config_->dry_run) {
if (!disk_interface_.MakeDirs(build_dir_ + "/.") && errno != EEXIST) {
Error("creating build directory %s: %s",
build_dir_.c_str(), strerror(errno));
return false;
}
}
return true;
}
int NinjaMain::RunBeforeBuild(const Options& options, int argc, char** argv) {
// Fully reset NinjaMain instance on each run. This gets rid of
// the currently loaded manifest, if there is one, avoiding unexpected
// errors like:
//
// ```
// [0/1](1) Regenerating ninja files
// ninja: error: build.ninja:3: duplicate rule 'gn'
// rule gn
// ^ near here
// ```
Reset();
ManifestParserOptions parser_opts;
if (options.phony_cycle_should_err) {
parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
}
ManifestParser parser(&state_, &path_recording_disk_interface_, parser_opts);
std::string err;
if (!parser.Load(config_->input_file, &err)) {
status_->Error("%s", err.c_str());
return 1;
}
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD)
return (this->*options.tool->func)(&options, argc, argv);
if (!EnsureBuildDirExists() || !OpenBuildLog() || !OpenDepsLog())
return 1;
if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS)
return (this->*options.tool->func)(&options, argc, argv);
return kBuildCanProceed;
}
// Convenience OutputStream derived class that writes to a FILE*
// handle, possibly using gzip compression. Takes ownership of file.
// Usage is:
// 1) Create instance.
// 2) Call Init() method, which can report an error.
// 3) Use the instance as an OutputStream through Write() and Flush() calls.
// 4) Destroy instance to flush everything to the file and close it.
struct FileOrGzipOutputStream : public OutputStream {
FileOrGzipOutputStream() = default;
~FileOrGzipOutputStream() {
gzip_output_.reset();
file_output_.reset();
fclose(file_);
}
bool Init(const char* file_path, std::string* error) {
assert(file_path != nullptr && "Invalid null file path");
assert(file_ == nullptr && "Cannot call Init() method twice!");
file_ = ::fopen(file_path, "wt");
if (!file_) {
*error =
StringFormat("Cannot create file %s: %s", file_path, strerror(errno));
return false;
}
auto endsWith = [](StringPiece str, StringPiece needle) {
return str.len_ >= needle.len_ &&
!memcmp(str.str_ + str.len_ - needle.len_, needle.str_,
needle.len_);
return false;
};
file_output_.reset(new StdioOutputStream(file_));
output_ = file_output_.get();
if (endsWith(file_path, ".gz")) {
if (!GzipOutputStream::IsSupported()) {
*error = "This version of Ninja cannot write gzip files!";
return false;
}
gzip_output_.reset(new GzipOutputStream(*file_output_));
output_ = gzip_output_.get();
}
return true;
}
void Write(const char* str, size_t len) override { output_->Write(str, len); }
void Flush() override { output_->Flush(); }
FILE* file_ = nullptr;
std::string error_;
std::unique_ptr<StdioOutputStream> file_output_;
std::unique_ptr<GzipOutputStream> gzip_output_;
OutputStream* output_ = nullptr;
};
// Convenience class used to manage the build event streaming support.
// Usage is simply:
//
// - Create instance.
//
// - Call Init() method. If --bes-output is used, this will modify
// the state of the NinjaMain instance to active the feature
// by opening an output file stream and connecting it to
// BuildEventStreamer instance that will receive build status
// events during the build.
//
// - Destroy the instance, which will restore the state of
// the NinjaMain instance properly if the constructor modified
// it.
//
class BuildEventStreamState {
public:
explicit BuildEventStreamState(NinjaMain& ninja) : ninja_(ninja) {}
bool Init(std::string* error) {
const BuildConfig& config = *(ninja_.config_);
if (config.bes_output.empty())
return true;
const char* build_id = getenv("NINJA_BUILD_ID");
out_stream_.reset(new FileOrGzipOutputStream());
if (!out_stream_->Init(config.bes_output.c_str(), error))
return false;
auto bes = std::make_unique<BuildEventStreamer>(
// TODO: pass full command, not just the tool path
ninja_.ninja_path(), config.ToString(), config.build_metadata,
build_id != nullptr ? build_id : "", ninja_.start_time_millis_,
out_stream_.get());
// Add the streamer to the status listener group.
bes_status_ptr_ = bes.get();
ninja_.status_->AddListener(std::move(bes));
return true;
}
~BuildEventStreamState() {
// Remove the streamer from the NinjaMain status listener group.
(void)ninja_.status_->Remove(bes_status_ptr_);
}
private:
NinjaMain& ninja_;
std::unique_ptr<FileOrGzipOutputStream> out_stream_;
// Non-owning pointer to BuildEventStreamer instance.
Status* bes_status_ptr_ = nullptr;
};
// Convenience class used to manage the ResultStore streaming support.
// Usage:
//
// - Create instance, if --rs-output is used, this will modify
// the state of the NinjaMain instance to active the feature
// by opening an output file stream and connecting it to
// ResultStoreStreamer instance that will receive build status
// events during the build.
//
// - Destroy the instance, which will restore the state of
// the NinjaMain instance properly if the constructor modified
// it.
//
class ResultStoreStreamState {
public:
explicit ResultStoreStreamState(NinjaMain& ninja) : ninja_(ninja) {}
bool Init(std::string* error) {
const BuildConfig& config = *(ninja_.config_);
if (config.resultstore_output.empty())
return true;
out_stream_.reset(new FileOrGzipOutputStream());
if (!out_stream_->Init(config.resultstore_output.c_str(), error))
return false;
const char* build_id = getenv("NINJA_BUILD_ID");
auto rs = std::make_unique<ResultStoreStreamer>(
ninja_.original_ninja_command_,
config.ToString(),
config.build_metadata,
build_id != nullptr ? build_id : "",
ninja_.start_time_millis_,
out_stream_.get());
// Add the streamer to the status listener group.
rs_status_ptr_ = rs.get();
ninja_.status_->AddListener(std::move(rs));
return true;
}
~ResultStoreStreamState() {
// Remove the streamer from the NinjaMain status listener group.
(void)ninja_.status_->Remove(rs_status_ptr_);
}
private:
NinjaMain& ninja_;
std::unique_ptr<FileOrGzipOutputStream> out_stream_;
// Non-owning pointer to ResultStoreStreamer instance.
Status* rs_status_ptr_ = nullptr;
};
// Class holding state used to write a Chrome event trace to a file,
// possibly using gzip compression to save 20x disk size. Usage:
// 1) Create instance.
// 2) Call Init(), which can report error. This registers a Status listener.
// 3) Run the build as usual.
// 4) Close the instance, which will flush everything to the final file.
class ChromeTracingState {
public:
ChromeTracingState(NinjaMain& ninja) : ninja_(ninja) {}
// Initialize new instance. File path is taken from
// config_.chrome_trace_output.
bool Init(std::string* error) {
const BuildConfig& config = *(ninja_.config_);
if (config.chrome_trace_output.empty())
return true;
file_path_ = config.chrome_trace_output.c_str();
output_.reset(new FileOrGzipOutputStream());
if (!output_->Init(file_path_, error))
return false;
*output_ << "[\n";
auto trace_output = [this](ChromeTraceEvent&& event) {
if (!stopwatch_) {
Info("writing Chrome Trace to %s", file_path_);
stopwatch_.reset(new Stopwatch());
}
if (need_comma_)
*output_ << ",\n";
need_comma_ = true;
*output_ << event.ToJson();
};
auto chrome_trace = NewChromeTracingStatus(std::move(trace_output));
chrome_trace_ptr_ = chrome_trace.get();
ninja_.status_->AddListener(std::move(chrome_trace));
return true;
}
~ChromeTracingState() {
if (chrome_trace_ptr_) {
(void)ninja_.status_->Remove(chrome_trace_ptr_);
*output_ << "\n]";
if (stopwatch_) {
int64_t duration_ms =
static_cast<int64_t>(stopwatch_->Elapsed() / 1000.);
Info("chrome trace generated in %s",
StringFormatDurationMs(duration_ms).c_str());
}
}
}
private:
NinjaMain& ninja_;
const char* file_path_ = nullptr;
std::unique_ptr<FileOrGzipOutputStream> output_;
bool need_comma_ = false;
Status* chrome_trace_ptr_ = nullptr;
std::unique_ptr<Stopwatch> stopwatch_;
};
// Convenience class used to manage the JSON error log support.
// Usage is simply:
//
// - Create instance.
//
// - Call Init() method. If --error_logging_output is used, this will modify
// the state of the NinjaMain instance to active the feature
// by opening an output file stream and connecting it to an
// ErrorLoggingStatus instance that will receive build status
// events during the build.
//
// - Destroy the instance, which will restore the state of
// the NinjaMain instance properly if the constructor modified
// it.
//
class ErrorLoggingState {
public:
ErrorLoggingState(NinjaMain& ninja) : ninja_(ninja) {}
bool Init(std::string* error) {
const BuildConfig& config = *(ninja_.config_);
if (config.error_logging_output.empty())
return true;
out_stream_.reset(new FileOrGzipOutputStream());
if (!out_stream_->Init(config.error_logging_output.c_str(), error))
return false;
auto error_log = ErrorLoggingStatus::New(*out_stream_);
error_log->SetBuildMetadata(config.build_metadata);
error_log_ptr_ = error_log.get();
ninja_.status_->AddListener(std::move(error_log));
return true;
}
~ErrorLoggingState() { (void)ninja_.status_->Remove(error_log_ptr_); }
NinjaMain& ninja_;
const char* file_path_ = nullptr;
std::unique_ptr<FileOrGzipOutputStream> out_stream_;
Status* error_log_ptr_ = nullptr;
};
ExitStatus NinjaMain::RunBuild(int argc, char** argv) {
// Enable build event or ResultStore stream support if needed. This may
// modify the state of *self so must be performed first.
std::string err;
BuildEventStreamState build_event_stream_state(*this);
if (!build_event_stream_state.Init(&err)) {
status_->Error("%s", err.c_str());
return ExitFailure;
}
ResultStoreStreamState resultstore_stream_state(*this);
if (!resultstore_stream_state.Init(&err)) {
status_->Error("%s", err.c_str());
return ExitFailure;
}
// Do the same for writing build events to a Chrome trace.
ChromeTracingState chrome_tracing_state(*this);
if (!chrome_tracing_state.Init(&err)) {
status_->Error("%s", err.c_str());
return ExitFailure;
}
ErrorLoggingState error_logging_state(*this);
if (!error_logging_state.Init(&err)) {
status_->Error("%s", err.c_str());
return ExitFailure;
}
ParsePreviousElapsedTimes();
disk_interface_.AllowStatCache(g_experimental_statcache);
Builder builder(&state_, *config_, &build_log_, &deps_log_, &disk_interface_,
status(), start_time_millis_);
// If MAKEFLAGS is set, only setup a Jobserver client if needed.
// (this means that an empty MAKEFLAGS value disables the feature).
std::unique_ptr<Jobserver::Client> jobserver_client;
// Determine whether to use a Jobserver client in this build.
// Disallowed for dry-runs, and for explicit -j1. Note that
// `-j0` means "infinite" parallelism but is translated as
// `config_.parallelism == INT_MAX` in ReadFlags().
bool use_jobserver = !config_->dry_run && config_->parallelism > 1;
if (use_jobserver) {
do {
const char* makeflags = getenv("MAKEFLAGS");
if (!makeflags) {
// MAKEFLAGS is not defined.
break;
}
Jobserver::Config jobserver_config;
if (!Jobserver::ParseNativeMakeFlagsValue(makeflags, &jobserver_config,
&err)) {
// MAKEFLAGS is defined but could not be parsed correctly.
if (config_->verbosity > BuildConfig::QUIET)
status_->Warning("Unsupported MAKEFLAGS value: %s [%s]", err.c_str(),
makeflags);
break;
}
if (!jobserver_config.HasMode()) {
// MAKEFLAGS is defined, but does not describe a jobserver mode.
break;
}
if (config_->verbosity > BuildConfig::NO_STATUS_UPDATE)
status_->Info("Jobserver mode detected: %s", makeflags);
jobserver_client = Jobserver::Client::Create(jobserver_config, &err);
if (!jobserver_client.get()) {
// Jobserver client initialization failed !?
if (config_->verbosity > BuildConfig::QUIET)
status_->Error("Could not initialize jobserver: %s", err.c_str());
break;
}
// Initialization succeeded, setup builder to use it.
builder.SetJobserverClient(std::move(jobserver_client));
} while (0);
err.clear();
}
std::vector<Node*> targets;
if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) {
status_->Error("%s", err.c_str());
return ExitFailure;
}
{
METRIC_RECORD("dependency scan");
for (size_t i = 0; i < targets.size(); ++i) {
if (!builder.AddTarget(targets[i], &err)) {
if (!err.empty()) {
status_->Error("%s", err.c_str());
return ExitFailure;
} else {
// Added a target that is already up-to-date; not really
// an error.
}
}
}
if (builder.AlreadyUpToDate()) {
if (config_->verbosity != BuildConfig::NO_STATUS_UPDATE)
status_->Info("no work to do.");
return ExitSuccess;
}
}
ExitStatus exit_status = builder.Build(&err);
if (exit_status != ExitSuccess) {
status_->Info("build stopped: %s.", err.c_str());
if (err.find("interrupted by user") != string::npos) {
return ExitInterrupted;
}
}
return exit_status;
}
#ifdef _MSC_VER
/// This handler processes fatal crashes that you can't catch
/// Test example: C++ exception in a stack-unwind-block
/// Real-world example: ninja launched a compiler to process a tricky
/// C++ input file. The compiler got itself into a state where it
/// generated 3 GB of output and caused ninja to crash.
void TerminateHandler() {
CreateWin32MiniDump(NULL);
Fatal("terminate handler called");
}
/// On Windows, we want to prevent error dialogs in case of exceptions.
/// This function handles the exception, and writes a minidump.
int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
Error("exception: 0x%X", code); // e.g. EXCEPTION_ACCESS_VIOLATION
fflush(stderr);
CreateWin32MiniDump(ep);
return EXCEPTION_EXECUTE_HANDLER;
}
#endif // _MSC_VER
class DeferGuessParallelism {
public:
bool needGuess;
BuildConfig* config;
DeferGuessParallelism(BuildConfig* config)
: needGuess(true), config(config) {}
void Refresh() {
if (needGuess) {
needGuess = false;
config->parallelism = GuessParallelism();
}
}
~DeferGuessParallelism() { Refresh(); }
};
/// Parse argv for command-line options.
/// Returns an exit code, or -1 if Ninja should continue.
int ReadFlags(int* argc, char*** argv,
Options* options, BuildConfig* config) {
DeferGuessParallelism deferGuessParallelism(config);
// Case values for long-options.
enum {
OPT_VERSION = 1,
OPT_QUIET = 2,
OPT_JOBSERVER_POOL = 3,
OPT_BES_OUTPUT = 4,
OPT_BES_METADATA = 5,
OPT_CHROME_TRACE = 6,
OPT_RESULTSTORE_OUTPUT = 7,
OPT_ERROR_LOGGING_OUTPUT = 8,
OPT_WEIGHT_LIST = 9,
};
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 },
{ "jobserver", no_argument, NULL, OPT_JOBSERVER_POOL },
{ "jobserver-pool", no_argument, NULL, OPT_JOBSERVER_POOL },
{ "bes_output", required_argument, NULL, OPT_BES_OUTPUT },
{ "bes_metadata", required_argument, NULL, OPT_BES_METADATA },
{ "resultstore_output", required_argument, NULL, OPT_RESULTSTORE_OUTPUT },
{ "chrome_trace", required_argument, NULL, OPT_CHROME_TRACE },
{ "error_logging_output", required_argument, NULL,
OPT_ERROR_LOGGING_OUTPUT},
{"edge_weights_list", optional_argument, NULL, OPT_WEIGHT_LIST},
{ NULL, 0, NULL, 0 }
};
// First grab values from environment variables.
config->environment = EnvironmentBlock::CreateFromCurrentEnvironment();
int jobserver_pool = -1;
int opt;
while (!options->tool &&
(opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
if (!DebugEnable(optarg))
return 1;
break;
case 'f':
config->input_file = optarg;
break;
case 'j': {
char* end;
int value = strtol(optarg, &end, 10);
if (*end != 0 || value < 0)
Fatal("invalid -j parameter");
// We want to run N jobs in parallel. For N = 0, INT_MAX
// is close enough to infinite for most sane builds.
config->parallelism = value > 0 ? value : INT_MAX;
deferGuessParallelism.needGuess = false;
break;
}
case 'k': {
char* end;
int value = strtol(optarg, &end, 10);
if (*end != 0)
Fatal("-k parameter not numeric; did you mean -k 0?");
// We want to go until N jobs fail, which means we should allow
// N failures and then stop. For N <= 0, INT_MAX is close enough
// to infinite for most sane builds.
config->failures_allowed = value > 0 ? value : INT_MAX;
break;
}
case 'l': {
char* end;
double value = strtod(optarg, &end);
if (end == optarg)
Fatal("-l parameter not numeric: did you mean -l 0.0?");
config->max_load_average = value;
break;
}
case 'n':
config->dry_run = true;
break;
case 't':
options->tool = ChooseTool(optarg);
if (!options->tool)
return 0;
break;
case 'v':
config->verbosity = BuildConfig::VERBOSE;
break;
case OPT_QUIET:
config->verbosity = BuildConfig::NO_STATUS_UPDATE;
break;
case 'w':
if (!WarningEnable(optarg, options))
return 1;
break;
case 'C':
options->working_dir = optarg;
break;
case OPT_VERSION:
printf("%s\n", kNinjaVersion);
return 0;
case OPT_JOBSERVER_POOL:
jobserver_pool = 1;
break;
case OPT_BES_OUTPUT:
// required arg is not null, but maybe empty
config->bes_output = optarg;
break;
case OPT_RESULTSTORE_OUTPUT:
// required arg is not null, but maybe empty
config->resultstore_output = optarg;
break;
case OPT_ERROR_LOGGING_OUTPUT:
// required arg is not null, but maybe empty
config->error_logging_output = optarg;
break;
case OPT_BES_METADATA:
{
// parse KEY=VALUE and store into build metadata map
const std::string key_value(optarg);
const auto sep = key_value.find('=');
if (sep == std::string::npos) {
Fatal("Expected build metadata in the form KEY=VALUE, but got \'%s\'", optarg);
}
const auto begin = key_value.begin();
const std::string key(begin, begin + sep);
const std::string value(begin + sep + 1, key_value.end());
config->build_metadata[key] = value;
}
break;
case OPT_WEIGHT_LIST:
config->weight_list_path = optarg;
break;
case OPT_CHROME_TRACE:
config->chrome_trace_output = optarg;
break;
case 'h':
default:
deferGuessParallelism.Refresh();
Usage(*config);
return 1;
}
}
*argv += optind;
*argc -= optind;
// If an explicit --jobserver-pool has not been used, lookup the
// NINJA_JOBSERVER environment variable. Ignore it if parallelism was set
// explicitly on the command line though (and warn about it).
if (jobserver_pool < 0) {
const char* jobserver_env = getenv("NINJA_JOBSERVER");
if (jobserver_env && !deferGuessParallelism.needGuess) {
if (!config->dry_run && config->verbosity > BuildConfig::QUIET)
Warning(
"Explicit parallelism (-j), ignoring NINJA_JOBSERVER environment "
"variable.");
jobserver_env = nullptr;
}
if (jobserver_env)
jobserver_pool = atoi(jobserver_env);
}
config->jobserver_pool = jobserver_pool > 0;
return -1;
}
NORETURN void real_main(int argc, char** argv) {
// Use exit() instead of return in this function to avoid potentially
// expensive cleanup when destructing NinjaMain.
BuildConfig config;
Options options;
// Save original arguments and current directory for persistent mode since
// ReadFlags() will modify them.
const auto original_args = RemoteArguments(argc, argv).args();
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
int exit_code = ReadFlags(&argc, &argv, &options, &config);
if (exit_code >= 0)
exit(exit_code);
// Always enable metrics when in persistent server mode,
// since the METRIC_RECORD macro probes the value of g_metrics
// once per execution, it must be initialized before any
// use of that macro to ensure everything works correctly.
PersistentMode::Status persistence =
PersistentMode::GetCurrentProcessStatus();
bool dump_metrics = !!g_metrics;
if (persistence == PersistentMode::IsServer && !g_metrics)
g_metrics = new Metrics();
NinjaMain ninja(original_args, 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
// subsequent commands.
// 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 && config.verbosity != BuildConfig::NO_STATUS_UPDATE)
ninja.status()->Info("Entering directory `%s'", options.working_dir);
if (chdir(options.working_dir) < 0) {
Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno));
}
}
if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) {
// None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed
// by other tools.
exit((ninja.*options.tool->func)(&options, argc, argv));
}
PersistentMode::BuildQuery build_query;
build_query.config = config;
build_query.debug_explaining = g_explaining;
build_query.debug_keep_depfile = g_keep_depfile;
build_query.debug_keep_rsp = g_keep_rsp;
build_query.debug_experimental_statcache = g_experimental_statcache;
build_query.dump_metrics = dump_metrics;
if (options.tool)
build_query.tool = options.tool->name;
for (int n = 0; n < argc; ++n) {
build_query.args.push_back(argv[n]);
}
// NOTE: For now, do not run tools in persistent client/server mode.
bool use_persistent_mode =
!options.tool && (persistence != PersistentMode::Disabled);
// Note: since the working dir has already been changed here, do not
// use options.working_dir, which is no longer valid if it is a relative
// path. Compatibility will use the current working directory as the
// build dir by default.
PersistentMode::Compatibility compatibility;
compatibility.SetInputFile(config.input_file);
compatibility.SetFlagPhonyCycleShouldErr(options.phony_cycle_should_err);
if (use_persistent_mode) {
if (persistence == PersistentMode::IsServer) {
// Persistent server mode. Load the build graph and all other expensive
// things first. Exit immediately if there is a problem.
PersistentMode::Server server(compatibility);
#ifndef _WIN32
// Disallow SIGPIPE from terminating the current process. This can happen
// during client/server communication when either one exits unexpectedly.
SigPipeBlocker sig_pipe_blocker;
#endif // !_WIN32
// Enable server logs to stdout, which has already been redirected
// when this process was spawned from the client one.
server.SetLogger(Logger::CreateForStdio(stdout));
std::string err;
if (!server.StartLocalServer(&err)) {
printf("Could not start local server, aborting: %s\n", err.c_str());
exit(1);
}
printf(
"\n***********************************************\n"
"Server starting, loading build graph...\n");
int result = ninja.RunBeforeBuild(options, argc, argv);
if (result != kBuildCanProceed)
exit(result);
// Print load metrics
printf("Manifest loading metrics\n");
g_metrics->load_.Report();
printf(
"%zu nodes, %zu edges, %zu pools\n%zu build log entries, %zu deps "
"log paths\n",
static_cast<size_t>(ninja.state_.paths_.size()), ninja.state_.edges_.size(),
ninja.state_.pools_.size(), ninja.build_log_.size(),
ninja.deps_log_.size());
// This callable returns true if the manifest(s) changed since the
// last call. Which will force a server restart.
auto restart_check = [&]() -> bool {
// A fast check that returns true if any input .ninja file was
// modified. Happens when the generator was called by the user
// before Ninja.
return ninja.path_recording_disk_interface_.CheckOutOfDate();
};
// This callable runs the part of the build and returns an exit status.
// or the special value PersistentMode::kServerExit to indicate that
// the server should exit.
auto do_build = [&](const PersistentMode::BuildQuery& query) -> ExitStatus {
if (query.dump_metrics) {
// Do not reset the load metrics here, only the build ones.
g_metrics->build_.Reset();
}
ninja.PrepareBuild(query.config);
if (!query.tool.empty()) {
// Note that tools are always run on the client process because they
// disable persistent mode support (see below). This currently happens
// in the run_before_build lambda above.
// TODO(digit): Implement tool in the server if it makes sense.
fprintf(stderr,
"UNIMPLEMENTED FEATURE: RUNNING TOOLS IN PERSISTENT MODE!\n");
return ExitFailure;
}
std::string err;
if (ninja.RebuildManifest(&err)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return with 0.
if (query.config.dry_run)
return ExitSuccess;
// The manifest has been regenerated, so the build graph in this
// server is no longer valid. Tell it to exit so that the next
// client invocation will start a new instance that will read
// the new manifest.
return ExitStatus(
PersistentMode::kServerExitAfterRegeneration); // int exit code
}
if (!err.empty()) {
// Regenerating the manifest failed. Notify the client without
// exiting the server. This could be a transient error, and if
// the build plan was updated, the next restart_check() will
// detect that.
ninja.status()->Error("rebuilding '%s': %s",
query.config.input_file.c_str(), err.c_str());
return ExitStatus(PersistentMode::kServerExitAfterRegenerationError);
}
RemoteArguments targets;
targets.Reset(query.args);
ExitStatus result = ninja.RunBuild(targets.argc(), targets.argv());
if (query.dump_metrics)
ninja.DumpMetrics();
return result;
};
printf("Server mode waiting for client requests\n");
// Now serve client requests in a loop with |do_build| as the request
// handler. This exits the process on completion.
server.RunServerThenExit(restart_check, do_build);
ninja.status()->Error("Server could not run or exit properly!\n");
exit(1);
} else {
assert(persistence == PersistentMode::IsClient);
}
}
// For regular and persistent client builds.
// Determine whether to setup a Jobserver pool. This depends on
// --jobserver-pool or --jobserver=MODE being passed on the command-line,
// or NINJA_JOBSERVER=MODE being set in the environment.
//
// This must be ignored if a tool is being used, or no/infinite
// parallelism is being asked.
//
// At the moment, this overrides any MAKEFLAGS definition in
// the environment.
std::unique_ptr<JobserverPool> jobserver_pool;
do {
if (options.tool) // Do not setup pool when a tool is used.
break;
if (config.parallelism == 1) {
// No-parallelism (-j1) was specified. Note that -j0 is
// "infinite" parallelism, which does not disable the pool.
break;
}
if (!config.jobserver_pool) {
// --jobserver-pool was not used, NINJA_JOBSERVER is not set,
// or NINJA_JOBSERVER=0 was used.
break;
}
if (config.verbosity >= BuildConfig::VERBOSE)
ninja.status()->Info("Creating jobserver pool for %d parallel jobs",
config.parallelism);
std::string err;
jobserver_pool = JobserverPool::Create(
static_cast<size_t>(config.parallelism),
config.jobserver_pool ? JobserverPool::Config::kModeDefault
: JobserverPool::Config::kModeNone,
&err);
if (!jobserver_pool.get()) {
if (config.verbosity > BuildConfig::QUIET)
ninja.status()->Warning("Jobserver pool creation failed: %s",
err.c_str());
break;
}
std::string makeflags = jobserver_pool->GetEnvMakeFlagsValue();
// Set or override the MAKEFLAGS environment variable in
// the current process. This ensures it is passed to sub-commands
// as well.
#ifdef _WIN32
// TODO(digit): Verify that this works correctly on Win32.
// this code assumes that _putenv(), unlike Posix putenv()
// does create a copy of the input string, and that the
// resulting environment is passed to processes launched
// with CreateProcess (the documentation only mentions
// _spawn() and _exec()).
std::string env = "MAKEFLAGS=" + makeflags;
_putenv(env.c_str());
#else // !_WIN32
setenv("MAKEFLAGS", makeflags.c_str(), 1);
#endif // !_WIN32
config.environment.Insert("MAKEFLAGS", makeflags.c_str());
} while (0);
// Unset NINJA_JOBSERVER unconditionally in subprocesses
// to avoid multiple sub-pools to be started by mistake.
#ifdef _WIN32
_putenv("NINJA_JOBSERVER=");
#else // !_WIN32
unsetenv("NINJA_JOBSERVER");
#endif // !_WIN32
config.environment.Remove("NINJA_JOBSERVER");
// Limit number of rebuilds, to prevent infinite loops.
const int kCycleLimit = 100;
for (int cycle = 1; cycle <= kCycleLimit; ++cycle) {
std::string err;
//////////////////////////////////////////////////////////
// Persistent client build.
if (use_persistent_mode) {
PersistentMode::Client client;
#ifndef _WIN32
// Disallow SIGPIPE from terminating the current process. This can happen
// during client/server communication when either one exits unexpectedly.
SigPipeBlocker sig_pipe_blocker;
#endif // !_WIN32
// If NINJA_PERSISTENT_LOG_FILE is defined in the environment, use this
// to write persistent server logs. Otherwise, write them to the
// ".ninja_persistent_log" in the build directory.
{
const char* server_log_file = ".ninja_persistent_log";
const char* env = getenv("NINJA_PERSISTENT_LOG_FILE");
if (env && env[0])
server_log_file = env;
client.SetServerLogFile(server_log_file);
}
int result = 0;
if (!client.RunQuery(compatibility, build_query, &result, &err)) {
ninja.status()->Error(
"Error contacting server, falling back to local build: %s\n",
err.c_str());
// Try again with a regular build.
use_persistent_mode = false;
continue;
}
if (result == PersistentMode::kServerExitAfterRegeneration) {
// The server exited after rebuilding the manifest, loop to
// create a new server instance that loads the new manifest
// then build with it.
continue;
}
if (result == PersistentMode::kServerExitAfterRegenerationError) {
// The server exited after an error during manifest regeneration.
// Stop there, the next invocation will start a new server.
result = 1;
}
// Normal termination, exit with status code from server.
_exit(result);
}
//////////////////////////////////////////////////////////
// Normal (Non-persistent) builds.
int result = ninja.RunBeforeBuild(options, argc, argv);
if (result != kBuildCanProceed)
_exit(result);
// Attempt to rebuild the manifest before building anything else
if (ninja.RebuildManifest(&err)) {
// In dry_run mode the regeneration will succeed without changing the
// manifest forever. Better to return immediately.
if (config.dry_run)
_exit(0);
// Start the build over with the new manifest.
continue;
} else if (!err.empty()) {
ninja.status()->Error("rebuilding '%s': %s", config.input_file.c_str(),
err.c_str());
exit(1);
}
result = ninja.RunBuild(argc, argv);
if (g_metrics)
ninja.DumpMetrics();
_exit(result);
}
ninja.status()->Error(
"manifest '%s' still dirty after %d tries, perhaps system time is not "
"set",
config.input_file.c_str(), kCycleLimit);
exit(1);
}
} // anonymous namespace
int main(int argc, char** argv) {
#if defined(_MSC_VER)
// Set a handler to catch crashes not caught by the __try..__except
// block (e.g. an exception in a stack-unwind-block).
std::set_terminate(TerminateHandler);
__try {
// Running inside __try ... __except suppresses any Windows error
// dialogs for errors such as bad_alloc.
real_main(argc, argv);
}
__except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
// Common error situations return exitCode=1. 2 was chosen to
// indicate a more serious problem.
return 2;
}
#else
real_main(argc, argv);
#endif
}