| // 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 |
| } |