From 0319c0f71768164e9b4140f821f426ff4599fbc0 Mon Sep 17 00:00:00 2001 From: Yuwei Sun Date: Wed, 11 Dec 2024 23:43:48 -0500 Subject: [PATCH] Add new graph. Demonstrate how ninja construct graph and how the parser should be involved. --- .circleci/config.yml | 15 +- .gitignore | 12 +- new-graph/CMakeLists.txt | 11 + new-graph/easybuild.sh | 75 ++ new-graph/hello_world.cpp | 10 + new-graph/simulate-ninja-graph.cc | 116 ++ src/getopt.h | 54 +- src/ninja.cc | 1622 +--------------------------- src/ninja.h | 1636 +++++++++++++++++++++++++++++ 9 files changed, 1901 insertions(+), 1650 deletions(-) create mode 100644 new-graph/CMakeLists.txt create mode 100755 new-graph/easybuild.sh create mode 100644 new-graph/hello_world.cpp create mode 100644 new-graph/simulate-ninja-graph.cc create mode 100644 src/ninja.h diff --git a/.circleci/config.yml b/.circleci/config.yml index 60cafc4593..1da9a1aa6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,20 @@ jobs: echo "Running $test" ./$test done - + - run: + name: Test New Graph + command: | + echo "Testing new graph" + cd new-graph + bash easybuild.sh + OUTPUT=$(./hello_world) + if [ "$OUTPUT" = "Hello World! " ]; then + echo "Output is correct: $OUTPUT" + else + echo "Test failed: Expected 'Hello World!', got '$OUTPUT'" + exit 1 + fi + echo "New graph test completed successfully" workflows: build-test-workflow: diff --git a/.gitignore b/.gitignore index 219fca3098..fc858a495d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,14 @@ /.vs/ /out/ -commands.md \ No newline at end of file +# general dev files for Shadowdash +*commands.md +*CMakeFiles +*.so +*.dylib +*.dll + +# compile hello_world +*hello_world +*hello_world.o +*simulate-ninja-graph \ No newline at end of file diff --git a/new-graph/CMakeLists.txt b/new-graph/CMakeLists.txt new file mode 100644 index 0000000000..a0fb25cf22 --- /dev/null +++ b/new-graph/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) + +# Set the project name +project(HelloWorld) + +# Specify the C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Add the executable +add_executable(hello_world hello_world.cpp) diff --git a/new-graph/easybuild.sh b/new-graph/easybuild.sh new file mode 100755 index 0000000000..6fcc244d24 --- /dev/null +++ b/new-graph/easybuild.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# Check OS +os_type=$(uname) +# Set default compiler and flags +export CC=gcc +export CXX=g++ + +if [[ "$os_type" == "Linux" ]]; then + lib_extension="so" + shared_flag="-shared" + rpath_flag="-Wl,-rpath=." +elif [[ "$os_type" == "Darwin" ]]; then + # Check gcc-11 on MacOS + if command -v gcc-11 &> /dev/null && command -v g++-11 &> /dev/null; then + echo "GNU g++-11 detected. Using g++-11 as the compiler." + export CC=gcc-11 + export CXX=g++-11 + else + echo "GNU g++-11 not found. Please install it using Homebrew with 'brew install gcc@11'." + echo "If another version of GCC (not Clang) is installed, please update this script accordingly." + exit 1 + fi + lib_extension="dylib" + shared_flag="-dynamiclib" + + # Add MacOS SDK path for standard headers + export SDK_PATH=$(xcrun --sdk macosx --show-sdk-path) + extra_flags="-isysroot $SDK_PATH" + rpath_flag="-Wl,-rpath,@loader_path" +else + # Assume is Windows + lib_extension="dll" + shared_flag="-shared" + rpath_flag="-Wl,-rpath=." +fi + +# Build dynamic library +echo "Building libninja.${lib_extension}..." +$CXX -fPIC ${shared_flag} -I../src ${extra_flags} ../src/build_log.cc \ + ../src/build.cc \ + ../src/clean.cc \ + ../src/clparser.cc \ + ../src/dyndep.cc \ + ../src/dyndep_parser.cc \ + ../src/debug_flags.cc \ + ../src/deps_log.cc \ + ../src/disk_interface.cc \ + ../src/edit_distance.cc \ + ../src/elide_middle.cc \ + ../src/eval_env.cc \ + ../src/graph.cc \ + ../src/graphviz.cc \ + ../src/json.cc \ + ../src/line_printer.cc \ + ../src/manifest_parser.cc \ + ../src/metrics.cc \ + ../src/missing_deps.cc \ + ../src/parser.cc \ + ../src/state.cc \ + ../src/status_printer.cc \ + ../src/string_piece_util.cc \ + ../src/util.cc \ + ../src/subprocess-posix.cc \ + ../src/lexer.cc \ + ../src/depfile_parser.cc \ + ../src/version.cc -o "libninja.${lib_extension}" + +# Compile simulate-ninja-graph.cc +echo "Compiling simulate-ninja-graph.cc..." +$CXX -fPIC simulate-ninja-graph.cc -I../src -L./ -lninja ${rpath_flag} ${extra_flags} -o simulate-ninja-graph + +# Compile hello world using simulate-ninja-graph +echo "Running simulate-ninja-graph..." +./simulate-ninja-graph hello_world \ No newline at end of file diff --git a/new-graph/hello_world.cpp b/new-graph/hello_world.cpp new file mode 100644 index 0000000000..27bacf1f8f --- /dev/null +++ b/new-graph/hello_world.cpp @@ -0,0 +1,10 @@ +#include + +#ifndef N +#define N "World" +#endif + +int main() { + printf("Hello %s! \n", N); + return 0; +} \ No newline at end of file diff --git a/new-graph/simulate-ninja-graph.cc b/new-graph/simulate-ninja-graph.cc new file mode 100644 index 0000000000..9680fd7bf1 --- /dev/null +++ b/new-graph/simulate-ninja-graph.cc @@ -0,0 +1,116 @@ +#include "ninja.h" + +using namespace std; + +void CreateHelloWorldGraph(State* state, NinjaMain& ninja) { + BindingEnv env; + env.AddBinding("inputFile", "hello_world.cpp"); + env.AddBinding("linkFile", "hello_world.o"); + env.AddBinding("outputFile", "hello_world"); + + // Create rules + Rule* cxx_compiler = new Rule("CXX_COMPILER__hello_world_"); + + // Create command + EvalString command; + command.AddText("g++ -c "); + command.AddSpecial("in"); + command.AddText(" -o "); + command.AddSpecial("out"); + cxx_compiler->AddBinding("command", command); + + // Create description + EvalString description; + description.AddText("Building CXX object "); + description.AddSpecial("out"); + cxx_compiler->AddBinding("description", description); + + // Add rule to state + state->bindings_.AddRule(cxx_compiler); + + Rule* cxx_linker = new Rule("CXX_EXECUTABLE_LINKER__hello_world_"); + EvalString link; + link.AddText("g++ "); + link.AddSpecial("in"); + link.AddText(" -o "); + link.AddSpecial("out"); + cxx_linker->AddBinding("command", link); + + // Link description + EvalString linkDescription; + linkDescription.AddText("Linking CXX object "); + linkDescription.AddSpecial("$out"); + cxx_linker->AddBinding("description", linkDescription); + + state->bindings_.AddRule(cxx_linker); + + // Create executable node + Node* executable = state->GetNode(env.LookupVariable("outputFile"), 0); + + // Create compilation edges + string err; + Edge* compile_edge = ninja.GetState().AddEdge(cxx_compiler); + + // Initialize the environment and bind the necessary variables. + if (compile_edge->env_ == nullptr) { + compile_edge->env_ = new BindingEnv(&state->bindings_); + } + compile_edge->env_->AddBinding("in", env.LookupVariable("inputFile")); + compile_edge->env_->AddBinding("out", env.LookupVariable("linkFile")); + + if (!ninja.state_.AddOut(compile_edge, env.LookupVariable("linkFile"), 0, + &err)) { + Error(("Failed to add output to compile edge: " + err).c_str()); + return; + } + ninja.state_.AddIn(compile_edge, env.LookupVariable("inputFile"), 0); + + // Create link edges + Edge* link_edge = ninja.GetState().AddEdge(cxx_linker); + + if (link_edge->env_ == nullptr) { + link_edge->env_ = new BindingEnv(&state->bindings_); + } + link_edge->env_->AddBinding("in", env.LookupVariable("linkFile")); + link_edge->env_->AddBinding("out", env.LookupVariable("outputFile")); + + if (!ninja.state_.AddOut(link_edge, env.LookupVariable("outputFile"), 0, + &err)) { + Error(("Failed to add output to link edge: " + err).c_str()); + return; + } + ninja.state_.AddIn(link_edge, env.LookupVariable("linkFile"), 0); + + // Add default target + if (!state->AddDefault(env.LookupVariable("outputFile"), &err)) { + Error(("Failed to add default target: " + err).c_str()); + return; + } +} + +void Error(const string& message) { + cerr << "Error: " << message << endl; +} + +int main(int argc, char** argv) { + const char* ninja_command = argv[0]; + + cout << "Build start" << endl; + + // Config with Verbose output and 1 process + BuildConfig config; + config.verbosity = config.VERBOSE; + config.parallelism = 1; // 1 process + State state; + NinjaMain ninja(ninja_command, config); + + // Create Graph + CreateHelloWorldGraph(&state, ninja); + + Status* status = Status::factory(config); + int result = ninja.RunBuild(argc - 1, &argv[1], status); + + cout << "Build completed" << endl; + delete status; + return 0; +} \ No newline at end of file diff --git a/src/getopt.h b/src/getopt.h index 965dc29003..c7bff7abe9 100644 --- a/src/getopt.h +++ b/src/getopt.h @@ -4,48 +4,46 @@ /* include files needed by this include file */ /* macros defined by this include file */ -#define no_argument 0 +#define no_argument 0 #define required_argument 1 -#define OPTIONAL_ARG 2 +#define OPTIONAL_ARG 2 /* types defined by this include file */ /* GETOPT_LONG_OPTION_T: The type of long option */ -typedef struct GETOPT_LONG_OPTION_T -{ - const char *name; /* the name of the long option */ - int has_arg; /* one of the above macros */ - int *flag; /* determines if getopt_long() returns a - * value for a long option; if it is - * non-NULL, 0 is returned as a function - * value and the value of val is stored in - * the area pointed to by flag. Otherwise, - * val is returned. */ - int val; /* determines the value to return if flag is - * NULL. */ +typedef struct GETOPT_LONG_OPTION_T { + const char* name; /* the name of the long option */ + int has_arg; /* one of the above macros */ + int* flag; /* determines if getopt_long() returns a + * value for a long option; if it is + * non-NULL, 0 is returned as a function + * value and the value of val is stored in + * the area pointed to by flag. Otherwise, + * val is returned. */ + int val; /* determines the value to return if flag is + * NULL. */ } GETOPT_LONG_OPTION_T; typedef GETOPT_LONG_OPTION_T option; #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - /* externally-defined variables */ - extern char *optarg; - extern int optind; - extern int opterr; - extern int optopt; +/* externally-defined variables */ +extern char* optarg; +extern int optind; +extern int opterr; +extern int optopt; - /* function prototypes */ -#ifndef _AIX - int getopt (int argc, char **argv, char *optstring); +/* function prototypes */ +#if !defined(linux) && !defined(APPLE) +int getopt(int argc, char** argv, char* optstring); #endif - int getopt_long (int argc, char **argv, const char *shortopts, - const GETOPT_LONG_OPTION_T * longopts, int *longind); - int getopt_long_only (int argc, char **argv, const char *shortopts, - const GETOPT_LONG_OPTION_T * longopts, int *longind); +int getopt_long(int argc, char** argv, const char* shortopts, + const GETOPT_LONG_OPTION_T* longopts, int* longind); +int getopt_long_only(int argc, char** argv, const char* shortopts, + const GETOPT_LONG_OPTION_T* longopts, int* longind); #ifdef __cplusplus }; diff --git a/src/ninja.cc b/src/ninja.cc index 7885bb3682..5d9cd55a6f 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -11,1626 +11,9 @@ // 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 -#include -#include -#include -#include - -#include -#include - -#ifdef _WIN32 -#include "getopt.h" -#include -#include -#elif defined(_AIX) -#include "getopt.h" -#include -#else -#include -#include -#endif - -#include "browse.h" -#include "build.h" -#include "build_log.h" -#include "deps_log.h" -#include "clean.h" -#include "debug_flags.h" -#include "depfile_parser.h" -#include "disk_interface.h" -#include "graph.h" -#include "graphviz.h" -#include "json.h" -#include "manifest_parser.h" -#include "metrics.h" -#include "missing_deps.h" -#include "state.h" -#include "status.h" -#include "util.h" -#include "version.h" - +#include "ninja.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 { - /// Build file to load. - const char* input_file; - - /// Directory to change into before running. - const char* working_dir; - - /// Tool to run rather than building. - const Tool* tool; - - /// Whether phony cycles should warn or print an error. - bool phony_cycle_should_err; -}; - -/// 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 char* ninja_command, const BuildConfig& config) : - ninja_command_(ninja_command), config_(config), - start_time_millis_(GetTimeMillis()) {} - - /// Command line used to run Ninja. - const char* ninja_command_; - - /// Build configuration set from flags (e.g. parallelism). - const BuildConfig& config_; - - /// Loaded state (rules, nodes). - State state_; - - /// Functions for accessing the disk. - RealDiskInterface 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* 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 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[]); - - /// 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(const char* input_file, string* err, Status* status); - - /// 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 prediction. - void ParsePreviousElapsedTimes(); - - /// Build the targets listed on the command line. - /// @return an exit code. - int RunBuild(int argc, char** argv, Status* status); - - /// 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", - 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. -/// Returns true if the manifest was rebuilt. -bool NinjaMain::RebuildManifest(const char* input_file, string* err, - Status* status) { - string path = 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; - - 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)) - 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* 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 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::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::iterator validation = edge->validations_.begin(); - validation != edge->validations_.end(); ++validation) { - printf(" %s\n", (*validation)->path().c_str()); - } - } - } - printf(" outputs:\n"); - for (vector::const_iterator edge = node->out_edges().begin(); - edge != node->out_edges().end(); ++edge) { - for (vector::iterator out = (*edge)->outputs_.begin(); - out != (*edge)->outputs_.end(); ++out) { - printf(" %s\n", (*out)->path().c_str()); - } - } - const std::vector validation_edges = node->validation_out_edges(); - if (!validation_edges.empty()) { - printf(" validation for:\n"); - for (std::vector::const_iterator edge = validation_edges.begin(); - edge != validation_edges.end(); ++edge) { - for (vector::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_command_, options->input_file, 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& nodes, int depth, int indent) { - for (vector::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::iterator e = state->edges_.begin(); - e != state->edges_.end(); ++e) { - for (vector::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 rules; - - // Gather the outputs. - for (vector::iterator e = state->edges_.begin(); - e != state->edges_.end(); ++e) { - if ((*e)->rule_->name() == rule_name) { - for (vector::iterator out_node = (*e)->outputs_.begin(); - out_node != (*e)->outputs_.end(); ++out_node) { - rules.insert((*out_node)->path()); - } - } - } - - // Print them. - for (set::const_iterator i = rules.begin(); - i != rules.end(); ++i) { - printf("%s\n", (*i).c_str()); - } - - return 0; -} - -int ToolTargetsList(State* state) { - for (vector::iterator e = state->edges_.begin(); - e != state->edges_.end(); ++e) { - for (vector::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 nodes; - if (argc == 0) { - for (vector::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::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 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::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 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("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 - - typedef map Rules; - const Rules& rules = state_.bindings_.GetRules(); - for (Rules::const_iterator i = rules.begin(); i != rules.end(); ++i) { - printf("%s", i->first.c_str()); - if (print_description) { - const Rule* rule = i->second; - 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::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("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 nodes; - string err; - if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { - Error("%s", err.c_str()); - return 1; - } - - EdgeSet seen; - for (vector::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; - const option kLongOptions[] = { { "help", no_argument, NULL, 'h' }, - { "no-shell-escape", no_argument, NULL, 'E' }, - { "print0", no_argument, NULL, '0' }, - { "dependency-order", no_argument, NULL, - 'd' }, - { NULL, 0, NULL, 0 } }; - while ((opt = getopt_long(argc, argv, "h0Ed", kLongOptions, NULL)) != -1) { - switch (opt) { - 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" - ); - // clang-format on - return 1; - } - } - argv += optind; - argc -= optind; - - std::vector nodes; - std::string err; - if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { - Error("%s", err.c_str()); - return 1; - } - - InputsCollector collector; - for (const Node* node : nodes) - collector.VisitNode(node); - - std::vector inputs = collector.GetInputsAsStrings(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::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("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] != '@' && - command.find("--option-file=") != index - 14 && - command.find("-f ") != index - 3)) - 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; - } - if (command[index - 1] == '@') { - command.replace(index - 1, rspfile.length() + 1, rspfile_content); - } else if (command.find("-f ") == index - 3) { - command.replace(index - 3, rspfile.length() + 3, rspfile_content); - } else { // --option-file syntax - command.replace(index - 14, rspfile.length() + 14, 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("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 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::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("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;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; -} - -/// 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}, - { "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 }, -#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 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 == "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(), "phonycycle=err", - "phonycycle=warn", nullptr); - 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); -} - -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::RunBuild(int argc, char** argv, Status* status) { - string err; - vector targets; - if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) { - status->Error("%s", err.c_str()); - return 1; - } - - disk_interface_.AllowStatCache(g_experimental_statcache); - - Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_, - status, start_time_millis_); - 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 1; - } else { - // Added a target that is already up-to-date; not really - // an error. - } - } - } - - // Make sure restat rules do not see stale timestamps. - disk_interface_.AllowStatCache(false); - - if (builder.AlreadyUpToDate()) { - if (config_.verbosity != BuildConfig::NO_STATUS_UPDATE) { - status->Info("no work to do."); - } - return 0; - } - - if (!builder.Build(&err)) { - status->Info("build stopped: %s.", err.c_str()); - if (err.find("interrupted by user") != string::npos) { - return 130; - } - return 1; - } - - return 0; -} - -#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); - - enum { OPT_VERSION = 1, OPT_QUIET = 2 }; - 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 }, - { NULL, 0, NULL, 0 } - }; - - 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': - options->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 'h': - default: - deferGuessParallelism.Refresh(); - Usage(*config); - return 1; - } - } - *argv += optind; - *argc -= optind; - - 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 = {}; - options.input_file = "build.ninja"; - - setvbuf(stdout, NULL, _IOLBF, BUFSIZ); - const char* ninja_command = argv[0]; - - int exit_code = ReadFlags(&argc, &argv, &options, &config); - if (exit_code >= 0) - exit(exit_code); - - Status* status = Status::factory(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) - 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. - NinjaMain ninja(ninja_command, config); - exit((ninja.*options.tool->func)(&options, argc, argv)); - } - - // Limit number of rebuilds, to prevent infinite loops. - const int kCycleLimit = 100; - for (int cycle = 1; cycle <= kCycleLimit; ++cycle) { - NinjaMain ninja(ninja_command, config); - - ManifestParserOptions parser_opts; - if (options.phony_cycle_should_err) { - parser_opts.phony_cycle_action_ = kPhonyCycleActionError; - } - ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts); - string err; - if (!parser.Load(options.input_file, &err)) { - status->Error("%s", err.c_str()); - exit(1); - } - - if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD) - exit((ninja.*options.tool->func)(&options, argc, argv)); - - if (!ninja.EnsureBuildDirExists()) - exit(1); - - if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog()) - exit(1); - - if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS) - exit((ninja.*options.tool->func)(&options, argc, argv)); - - // Attempt to rebuild the manifest before building anything else - if (ninja.RebuildManifest(options.input_file, &err, status)) { - // 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()) { - status->Error("rebuilding '%s': %s", options.input_file, err.c_str()); - exit(1); - } - - ninja.ParsePreviousElapsedTimes(); - - int result = ninja.RunBuild(argc, argv, status); - if (g_metrics) - ninja.DumpMetrics(); - exit(result); - } - - status->Error("manifest '%s' still dirty after %d tries, perhaps system time is not set", - options.input_file, 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 @@ -1640,8 +23,7 @@ int main(int argc, char** argv) { // Running inside __try ... __except suppresses any Windows error // dialogs for errors such as bad_alloc. real_main(argc, argv); - } - __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { + } __except (ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { // Common error situations return exitCode=1. 2 was chosen to // indicate a more serious problem. return 2; diff --git a/src/ninja.h b/src/ninja.h new file mode 100644 index 0000000000..86720723bf --- /dev/null +++ b/src/ninja.h @@ -0,0 +1,1636 @@ +// 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include + +#include "getopt.h" +#elif defined(_AIX) +#include + +#include "getopt.h" +#else +#include +#include +#endif + +#include "browse.h" +#include "build.h" +#include "build_log.h" +#include "clean.h" +#include "debug_flags.h" +#include "depfile_parser.h" +#include "deps_log.h" +#include "disk_interface.h" +#include "graph.h" +#include "graphviz.h" +#include "json.h" +#include "manifest_parser.h" +#include "metrics.h" +#include "missing_deps.h" +#include "state.h" +#include "status.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 { + /// Build file to load. + const char* input_file; + + /// Directory to change into before running. + const char* working_dir; + + /// Tool to run rather than building. + const Tool* tool; + + /// Whether phony cycles should warn or print an error. + bool phony_cycle_should_err; +}; + +/// 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 char* ninja_command, const BuildConfig& config) + : ninja_command_(ninja_command), config_(config), + start_time_millis_(GetTimeMillis()) {} + + /// Command line used to run Ninja. + const char* ninja_command_; + + /// Build configuration set from flags (e.g. parallelism). + const BuildConfig& config_; + + /// Loaded state (rules, nodes). + State state_; + + /// Functions for accessing the disk. + RealDiskInterface 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* 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 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[]); + + /// 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(const char* input_file, string* err, Status* status); + + /// 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 prediction. + void ParsePreviousElapsedTimes(); + + /// Build the targets listed on the command line. + /// @return an exit code. + int RunBuild(int argc, char** argv, Status* status); + + /// 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; + } + + /// @brief Getter for state_ + /// @return state_ + State& GetState() { return state_; } + + 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", + 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. +/// Returns true if the manifest was rebuilt. +bool NinjaMain::RebuildManifest(const char* input_file, string* err, + Status* status) { + string path = 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; + + 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)) + 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* 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 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::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::iterator validation = + edge->validations_.begin(); + validation != edge->validations_.end(); ++validation) { + printf(" %s\n", (*validation)->path().c_str()); + } + } + } + printf(" outputs:\n"); + for (vector::const_iterator edge = node->out_edges().begin(); + edge != node->out_edges().end(); ++edge) { + for (vector::iterator out = (*edge)->outputs_.begin(); + out != (*edge)->outputs_.end(); ++out) { + printf(" %s\n", (*out)->path().c_str()); + } + } + const std::vector validation_edges = node->validation_out_edges(); + if (!validation_edges.empty()) { + printf(" validation for:\n"); + for (std::vector::const_iterator edge = validation_edges.begin(); + edge != validation_edges.end(); ++edge) { + for (vector::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_command_, options->input_file, 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& nodes, int depth, int indent) { + for (vector::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::iterator e = state->edges_.begin(); + e != state->edges_.end(); ++e) { + for (vector::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 rules; + + // Gather the outputs. + for (vector::iterator e = state->edges_.begin(); + e != state->edges_.end(); ++e) { + if ((*e)->rule_->name() == rule_name) { + for (vector::iterator out_node = (*e)->outputs_.begin(); + out_node != (*e)->outputs_.end(); ++out_node) { + rules.insert((*out_node)->path()); + } + } + } + + // Print them. + for (set::const_iterator i = rules.begin(); i != rules.end(); ++i) { + printf("%s\n", (*i).c_str()); + } + + return 0; +} + +int ToolTargetsList(State* state) { + for (vector::iterator e = state->edges_.begin(); + e != state->edges_.end(); ++e) { + for (vector::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 nodes; + if (argc == 0) { + for (vector::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::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 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::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 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("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 + + typedef map Rules; + const Rules& rules = state_.bindings_.GetRules(); + for (Rules::const_iterator i = rules.begin(); i != rules.end(); ++i) { + printf("%s", i->first.c_str()); + if (print_description) { + const Rule* rule = i->second; + 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::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("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 nodes; + string err; + if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { + Error("%s", err.c_str()); + return 1; + } + + EdgeSet seen; + for (vector::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; + const option kLongOptions[] = { { "help", no_argument, NULL, 'h' }, + { "no-shell-escape", no_argument, NULL, 'E' }, + { "print0", no_argument, NULL, '0' }, + { "dependency-order", no_argument, NULL, + 'd' }, + { NULL, 0, NULL, 0 } }; + while ((opt = getopt_long(argc, argv, "h0Ed", kLongOptions, NULL)) != -1) { + switch (opt) { + 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" + ); + // clang-format on + return 1; + } + } + argv += optind; + argc -= optind; + + std::vector nodes; + std::string err; + if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { + Error("%s", err.c_str()); + return 1; + } + + InputsCollector collector; + for (const Node* node : nodes) + collector.VisitNode(node); + + std::vector inputs = collector.GetInputsAsStrings(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::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("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] != '@' && + command.find("--option-file=") != index - 14 && + command.find("-f ") != index - 3)) + 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; + } + if (command[index - 1] == '@') { + command.replace(index - 1, rspfile.length() + 1, rspfile_content); + } else if (command.find("-f ") == index - 3) { + command.replace(index - 3, rspfile.length() + 3, rspfile_content); + } else { // --option-file syntax + command.replace(index - 14, rspfile.length() + 14, 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("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 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::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("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;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; +} + +/// 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 }, + { "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 }, +#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 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 == "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(), "phonycycle=err", + "phonycycle=warn", nullptr); + 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); +} + +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::RunBuild(int argc, char** argv, Status* status) { + string err; + vector targets; + if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) { + status->Error("%s", err.c_str()); + return 1; + } + + disk_interface_.AllowStatCache(g_experimental_statcache); + + Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_, + status, start_time_millis_); + 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 1; + } else { + // Added a target that is already up-to-date; not really + // an error. + } + } + } + + // Make sure restat rules do not see stale timestamps. + disk_interface_.AllowStatCache(false); + + if (builder.AlreadyUpToDate()) { + if (config_.verbosity != BuildConfig::NO_STATUS_UPDATE) { + status->Info("no work to do."); + } + return 0; + } + + if (!builder.Build(&err)) { + status->Info("build stopped: %s.", err.c_str()); + if (err.find("interrupted by user") != string::npos) { + return 130; + } + return 1; + } + + return 0; +} + +#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); + + enum { OPT_VERSION = 1, OPT_QUIET = 2 }; + 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 }, + { NULL, 0, NULL, 0 } }; + + 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': + options->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 'h': + default: + deferGuessParallelism.Refresh(); + Usage(*config); + return 1; + } + } + *argv += optind; + *argc -= optind; + + 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 = {}; + options.input_file = "build.ninja"; + + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); + const char* ninja_command = argv[0]; + + int exit_code = ReadFlags(&argc, &argv, &options, &config); + if (exit_code >= 0) + exit(exit_code); + + Status* status = Status::factory(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) + 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. + NinjaMain ninja(ninja_command, config); + exit((ninja.*options.tool->func)(&options, argc, argv)); + } + + // Limit number of rebuilds, to prevent infinite loops. + const int kCycleLimit = 100; + for (int cycle = 1; cycle <= kCycleLimit; ++cycle) { + NinjaMain ninja(ninja_command, config); + + ManifestParserOptions parser_opts; + if (options.phony_cycle_should_err) { + parser_opts.phony_cycle_action_ = kPhonyCycleActionError; + } + ManifestParser parser(&ninja.state_, &ninja.disk_interface_, parser_opts); + string err; + if (!parser.Load(options.input_file, &err)) { + status->Error("%s", err.c_str()); + exit(1); + } + + if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD) + exit((ninja.*options.tool->func)(&options, argc, argv)); + + if (!ninja.EnsureBuildDirExists()) + exit(1); + + if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog()) + exit(1); + + if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS) + exit((ninja.*options.tool->func)(&options, argc, argv)); + + // Attempt to rebuild the manifest before building anything else + if (ninja.RebuildManifest(options.input_file, &err, status)) { + // 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()) { + status->Error("rebuilding '%s': %s", options.input_file, err.c_str()); + exit(1); + } + + ninja.ParsePreviousElapsedTimes(); + + int result = ninja.RunBuild(argc, argv, status); + if (g_metrics) + ninja.DumpMetrics(); + exit(result); + } + + status->Error( + "manifest '%s' still dirty after %d tries, perhaps system time is not " + "set", + options.input_file, kCycleLimit); + exit(1); +} + +} // anonymous namespace