summaryrefslogtreecommitdiffstats
path: root/src/ninja.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninja.cc')
-rw-r--r--src/ninja.cc449
1 files changed, 273 insertions, 176 deletions
diff --git a/src/ninja.cc b/src/ninja.cc
index 4ce16e7..5a3c530 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -19,12 +19,6 @@
#include <sys/stat.h>
#include <sys/types.h>
-#if defined(__APPLE__) || defined(__FreeBSD__)
-#include <sys/sysctl.h>
-#elif defined(linux)
-#include <sys/sysinfo.h>
-#endif
-
#ifdef _WIN32
#include "getopt.h"
#include <direct.h>
@@ -38,6 +32,7 @@
#include "build.h"
#include "build_log.h"
#include "clean.h"
+#include "disk_interface.h"
#include "edit_distance.h"
#include "explain.h"
#include "graph.h"
@@ -47,11 +42,14 @@
#include "state.h"
#include "util.h"
+// Defined in msvc_helper_main-win32.cc.
+int MSVCHelperMain(int argc, char** argv);
+
namespace {
/// The version number of the current Ninja release. This will always
/// be "git" on trunk.
-const char* kVersion = "120715";
+const char* kVersion = "1.0.0";
/// Global information passed into subtools.
struct Globals {
@@ -68,61 +66,67 @@ struct Globals {
/// Command line used to run Ninja.
const char* ninja_command;
- /// Build configuration (e.g. parallelism).
- BuildConfig config;
+ /// Build configuration set from flags (e.g. parallelism).
+ BuildConfig* config;
/// Loaded state (rules, nodes). This is a pointer so it can be reset.
State* state;
};
+/// The type of functions that are the entry points to tools (subcommands).
+typedef int (*ToolFunc)(Globals*, int, char**);
+
+/// 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 (as early as possible).
+ RUN_AFTER_FLAGS,
+
+ /// Run after loading build.ninja.
+ RUN_AFTER_LOAD,
+ } when;
+
+ /// Implementation of the tool.
+ 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"
-"targets are paths, with additional special syntax:\n"
-" 'target^' means 'the first output that uses target'.\n"
-" example: 'ninja foo.cc^' will likely build foo.o.\n"
"\n"
"options:\n"
+" --version print ninja version (\"%s\")\n"
+"\n"
" -C DIR change to DIR before doing anything else\n"
" -f FILE specify input build file [default=build.ninja]\n"
-" -V print ninja version (\"%s\")\n"
"\n"
" -j N run N jobs in parallel [default=%d]\n"
" -l N do not start new jobs if the load average is greater than N\n"
+#ifdef _WIN32
+" (not yet implemented on Windows)\n"
+#endif
" -k N keep going until N jobs fail [default=1]\n"
-" -n dry run (don't run commands but pretend they succeeded)\n"
+" -n dry run (don't run commands but act like they succeeded)\n"
" -v show all command lines while building\n"
"\n"
" -d MODE enable debugging (use -d list to list modes)\n"
-" -t TOOL run a subtool\n"
-" use '-t list' to list subtools.\n"
-" terminates toplevel options; further flags are passed to the tool.\n",
+" -t TOOL run a subtool (use -t list to list subtools)\n"
+" terminates toplevel options; further flags are passed to the tool\n",
kVersion, config.parallelism);
}
/// Choose a default value for the -j (parallelism) flag.
int GuessParallelism() {
- int processors = 0;
-
-#if defined(linux)
- processors = get_nprocs();
-#elif defined(__APPLE__) || defined(__FreeBSD__)
- size_t processors_size = sizeof(processors);
- int name[] = {CTL_HW, HW_NCPU};
- if (sysctl(name, sizeof(name) / sizeof(int),
- &processors, &processors_size,
- NULL, 0) < 0) {
- processors = 1;
- }
-#elif defined(_WIN32)
- SYSTEM_INFO info;
- GetSystemInfo(&info);
- processors = info.dwNumberOfProcessors;
-#endif
-
- switch (processors) {
+ switch (int processors = GetProcessorCount()) {
case 0:
case 1:
return 2;
@@ -143,22 +147,20 @@ struct RealFileReader : public ManifestParser::FileReader {
/// Rebuild the build manifest, if necessary.
/// Returns true if the manifest was rebuilt.
-bool RebuildManifest(State* state, const BuildConfig& config,
- const char* input_file, string* err) {
+bool RebuildManifest(Builder* builder, const char* input_file, string* err) {
string path = input_file;
if (!CanonicalizePath(&path, err))
return false;
- Node* node = state->LookupNode(path);
+ Node* node = builder->state_->LookupNode(path);
if (!node)
return false;
- Builder manifest_builder(state, config);
- if (!manifest_builder.AddTarget(node, err))
+ if (!builder->AddTarget(node, err))
return false;
- if (manifest_builder.AlreadyUpToDate())
+ if (builder->AlreadyUpToDate())
return false; // Not an error, but we didn't rebuild.
- if (!manifest_builder.Build(err))
+ if (!builder->Build(err))
return false;
// The manifest was only rebuilt if it is now dirty (it may have been cleaned
@@ -170,51 +172,50 @@ bool CollectTargetsFromArgs(State* state, int argc, char* argv[],
vector<Node*>* targets, string* err) {
if (argc == 0) {
*targets = state->DefaultNodes(err);
- if (!err->empty())
+ return err->empty();
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ string path = argv[i];
+ if (!CanonicalizePath(&path, err))
return false;
- } else {
- for (int i = 0; i < argc; ++i) {
- string path = argv[i];
- if (!CanonicalizePath(&path, err))
- return false;
-
- // 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()) {
- *err = "'" + path + "' has no out edge";
- return false;
- }
- Edge* edge = node->out_edges()[0];
- if (edge->outputs_.empty()) {
- edge->Dump();
- Fatal("edge has no outputs");
- }
- node = edge->outputs_[0];
+ // 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()) {
+ *err = "'" + path + "' has no out edge";
+ return false;
+ }
+ Edge* edge = node->out_edges()[0];
+ if (edge->outputs_.empty()) {
+ edge->Dump();
+ Fatal("edge has no outputs");
}
- targets->push_back(node);
+ node = edge->outputs_[0];
+ }
+ targets->push_back(node);
+ } else {
+ *err = "unknown target '" + path + "'";
+
+ if (path == "clean") {
+ *err += ", did you mean 'ninja -t clean'?";
+ } else if (path == "help") {
+ *err += ", did you mean 'ninja -h'?";
} else {
- *err = "unknown target '" + path + "'";
-
- 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() + "'?";
- }
+ Node* suggestion = state->SpellcheckNode(path);
+ if (suggestion) {
+ *err += ", did you mean '" + suggestion->path() + "'?";
}
- return false;
}
+ return false;
}
}
return true;
@@ -291,6 +292,16 @@ int ToolBrowse(Globals* globals, int argc, char* argv[]) {
}
#endif // _WIN32
+#if defined(WIN32)
+int ToolMSVC(Globals* globals, int argc, char* argv[]) {
+ // Reset getopt: push one argument onto the front of argv, reset optind.
+ argc++;
+ argv--;
+ optind = 0;
+ return MSVCHelperMain(argc, argv);
+}
+#endif
+
int ToolTargetsList(const vector<Node*>& nodes, int depth, int indent) {
for (vector<Node*>::const_iterator n = nodes.begin();
n != nodes.end();
@@ -481,7 +492,7 @@ int ToolClean(Globals* globals, int argc, char* argv[]) {
return 1;
}
- Cleaner cleaner(globals->state, globals->config);
+ Cleaner cleaner(globals->state, *globals->config);
if (argc >= 1) {
if (clean_rules)
return cleaner.CleanRules(argc, argv);
@@ -492,7 +503,7 @@ int ToolClean(Globals* globals, int argc, char* argv[]) {
}
}
-void ToolUrtle() {
+int ToolUrtle(Globals* globals, 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 "
@@ -515,58 +526,63 @@ void ToolUrtle() {
count = 0;
}
}
+ return 0;
}
-int RunTool(const string& tool, Globals* globals, int argc, char** argv) {
- typedef int (*ToolFunc)(Globals*, int, char**);
- struct Tool {
- const char* name;
- const char* desc;
- ToolFunc func;
- } tools[] = {
+/// Find the function to execute for \a tool_name and return it via \a func.
+/// If there is no tool to run (e.g.: unknown tool), returns an exit code.
+int ChooseTool(const string& tool_name, const Tool** tool_out) {
+ static const Tool kTools[] = {
#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP)
{ "browse", "browse dependency graph in a web browser",
- ToolBrowse },
+ Tool::RUN_AFTER_LOAD, ToolBrowse },
+#endif
+#if defined(WIN32)
+ { "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",
+ Tool::RUN_AFTER_FLAGS, ToolMSVC },
#endif
{ "clean", "clean built files",
- ToolClean },
+ Tool::RUN_AFTER_LOAD, ToolClean },
{ "commands", "list all commands required to rebuild given targets",
- ToolCommands },
+ Tool::RUN_AFTER_LOAD, ToolCommands },
{ "graph", "output graphviz dot file for targets",
- ToolGraph },
+ Tool::RUN_AFTER_LOAD, ToolGraph },
{ "query", "show inputs/outputs for a path",
- ToolQuery },
+ Tool::RUN_AFTER_LOAD, ToolQuery },
{ "rules", "list all rules",
- ToolRules },
+ Tool::RUN_AFTER_LOAD, ToolRules },
{ "targets", "list targets by their rule or depth in the DAG",
- ToolTargets },
- { NULL, NULL, NULL }
+ Tool::RUN_AFTER_LOAD, ToolTargets },
+ { "urtle", NULL,
+ Tool::RUN_AFTER_FLAGS, ToolUrtle },
+ { NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL }
};
- if (tool == "list") {
+ if (tool_name == "list") {
printf("ninja subtools:\n");
- for (int i = 0; tools[i].name; ++i) {
- printf("%10s %s\n", tools[i].name, tools[i].desc);
+ for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
+ if (tool->desc)
+ printf("%10s %s\n", tool->name, tool->desc);
}
return 0;
- } else if (tool == "urtle") {
- ToolUrtle();
- return 0;
}
- for (int i = 0; tools[i].name; ++i) {
- if (tool == tools[i].name)
- return tools[i].func(globals, argc, argv);
+ for (const Tool* tool = &kTools[0]; tool->name; ++tool) {
+ if (tool->name == tool_name) {
+ *tool_out = tool;
+ return 0;
+ }
}
vector<const char*> words;
- for (int i = 0; tools[i].name; ++i)
- words.push_back(tools[i].name);
- const char* suggestion = SpellcheckStringV(tool, words);
+ for (const Tool* tool = &kTools[0]; tool->name; ++tool)
+ words.push_back(tool->name);
+ const char* suggestion = SpellcheckStringV(tool_name, words);
if (suggestion) {
- Error("unknown tool '%s', did you mean '%s'?", tool.c_str(), suggestion);
+ Error("unknown tool '%s', did you mean '%s'?",
+ tool_name.c_str(), suggestion);
} else {
- Error("unknown tool '%s'", tool.c_str());
+ Error("unknown tool '%s'", tool_name.c_str());
}
return 1;
}
@@ -592,17 +608,63 @@ bool DebugEnable(const string& name, Globals* globals) {
}
}
-int RunBuild(Globals* globals, int argc, char** argv) {
+bool OpenLog(BuildLog* build_log, Globals* globals,
+ DiskInterface* disk_interface) {
+ const string build_dir =
+ globals->state->bindings_.LookupVariable("builddir");
+ const char* kLogPath = ".ninja_log";
+ string log_path = kLogPath;
+ if (!build_dir.empty()) {
+ log_path = build_dir + "/" + kLogPath;
+ if (!disk_interface->MakeDirs(log_path) && errno != EEXIST) {
+ Error("creating build directory %s: %s",
+ build_dir.c_str(), strerror(errno));
+ return false;
+ }
+ }
+
+ string err;
+ if (!build_log->Load(log_path, &err)) {
+ 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 true.
+ Warning("%s", err.c_str());
+ err.clear();
+ }
+
+ if (!globals->config->dry_run) {
+ if (!build_log->OpenForWrite(log_path, &err)) {
+ Error("opening build log: %s", err.c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/// Dump the output requested by '-d stats'.
+void DumpMetrics(Globals* globals) {
+ g_metrics->Report();
+
+ printf("\n");
+ int count = (int)globals->state->paths_.size();
+ int buckets = (int)globals->state->paths_.bucket_count();
+ printf("path->node hash load %.2f (%d entries / %d buckets)\n",
+ count / (double) buckets, count, buckets);
+}
+
+int RunBuild(Builder* builder, int argc, char** argv) {
string err;
vector<Node*> targets;
- if (!CollectTargetsFromArgs(globals->state, argc, argv, &targets, &err)) {
+ if (!CollectTargetsFromArgs(builder->state_, argc, argv, &targets, &err)) {
Error("%s", err.c_str());
return 1;
}
- Builder builder(globals->state, globals->config);
for (size_t i = 0; i < targets.size(); ++i) {
- if (!builder.AddTarget(targets[i], &err)) {
+ if (!builder->AddTarget(targets[i], &err)) {
if (!err.empty()) {
Error("%s", err.c_str());
return 1;
@@ -613,12 +675,12 @@ int RunBuild(Globals* globals, int argc, char** argv) {
}
}
- if (builder.AlreadyUpToDate()) {
+ if (builder->AlreadyUpToDate()) {
printf("ninja: no work to do.\n");
return 0;
}
- if (!builder.Build(&err)) {
+ if (!builder->Build(&err)) {
printf("ninja: build stopped: %s.\n", err.c_str());
return 1;
}
@@ -626,26 +688,58 @@ int RunBuild(Globals* globals, int argc, char** argv) {
return 0;
}
-} // anonymous namespace
+#ifdef _MSC_VER
-int main(int argc, char** argv) {
+} // anonymous namespace
+
+// Defined in minidump-win32.cc.
+void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep);
+
+namespace {
+
+/// 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
+
+int NinjaMain(int argc, char** argv) {
+ BuildConfig config;
Globals globals;
globals.ninja_command = argv[0];
+ globals.config = &config;
const char* input_file = "build.ninja";
const char* working_dir = NULL;
- string tool;
+ string tool_name;
setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
- globals.config.parallelism = GuessParallelism();
+ config.parallelism = GuessParallelism();
+ enum { OPT_VERSION = 1 };
const option kLongOptions[] = {
{ "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, OPT_VERSION },
{ NULL, 0, NULL, 0 }
};
int opt;
- while (tool.empty() &&
+ while (tool_name.empty() &&
(opt = getopt_long(argc, argv, "d:f:hj:k:l:nt:vC:V", kLongOptions,
NULL)) != -1) {
switch (opt) {
@@ -657,14 +751,14 @@ int main(int argc, char** argv) {
input_file = optarg;
break;
case 'j':
- globals.config.parallelism = atoi(optarg);
+ config.parallelism = atoi(optarg);
break;
case 'l': {
char* end;
double value = strtod(optarg, &end);
if (end == optarg)
Fatal("-l parameter not numeric: did you mean -l 0.0?");
- globals.config.max_load_average = value;
+ config.max_load_average = value;
break;
}
case 'k': {
@@ -676,43 +770,54 @@ int main(int argc, char** argv) {
// 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.
- globals.config.failures_allowed = value > 0 ? value : INT_MAX;
+ config.failures_allowed = value > 0 ? value : INT_MAX;
break;
}
case 'n':
- globals.config.dry_run = true;
+ config.dry_run = true;
break;
case 'v':
- globals.config.verbosity = BuildConfig::VERBOSE;
+ config.verbosity = BuildConfig::VERBOSE;
break;
case 't':
- tool = optarg;
+ tool_name = optarg;
break;
case 'C':
working_dir = optarg;
break;
- case 'V':
+ case OPT_VERSION:
printf("%s\n", kVersion);
return 0;
case 'h':
default:
- Usage(globals.config);
+ Usage(config);
return 1;
}
}
argv += optind;
argc -= optind;
+ // If specified, select a tool as early as possible, so commands like
+ // -t list can run before we attempt to load build.ninja etc.
+ const Tool* tool = NULL;
+ if (!tool_name.empty()) {
+ int exit_code = ChooseTool(tool_name, &tool);
+ if (!tool)
+ return exit_code;
+ }
+
+ if (tool && tool->when == Tool::RUN_AFTER_FLAGS)
+ return tool->func(&globals, argc, argv);
+
if (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.
- printf("ninja: Entering directory `%s'\n", working_dir);
-#ifdef _WIN32
- if (_chdir(working_dir) < 0) {
-#else
+ // 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 (!tool)
+ printf("ninja: Entering directory `%s'\n", working_dir);
if (chdir(working_dir) < 0) {
-#endif
Fatal("chdir to '%s' - %s", working_dir, strerror(errno));
}
}
@@ -720,6 +825,7 @@ int main(int argc, char** argv) {
bool rebuilt_manifest = false;
reload:
+ RealDiskInterface disk_interface;
RealFileReader file_reader;
ManifestParser parser(globals.state, &file_reader);
string err;
@@ -728,38 +834,18 @@ reload:
return 1;
}
- if (!tool.empty())
- return RunTool(tool, &globals, argc, argv);
+ if (tool && tool->when == Tool::RUN_AFTER_LOAD)
+ return tool->func(&globals, argc, argv);
BuildLog build_log;
- build_log.SetConfig(&globals.config);
- globals.state->build_log_ = &build_log;
-
- const string build_dir = globals.state->bindings_.LookupVariable("builddir");
- const char* kLogPath = ".ninja_log";
- string log_path = kLogPath;
- if (!build_dir.empty()) {
- if (MakeDir(build_dir) < 0 && errno != EEXIST) {
- Error("creating build directory %s: %s",
- build_dir.c_str(), strerror(errno));
- return 1;
- }
- log_path = build_dir + "/" + kLogPath;
- }
-
- if (!build_log.Load(log_path, &err)) {
- Error("loading build log %s: %s", log_path.c_str(), err.c_str());
+ if (!OpenLog(&build_log, &globals, &disk_interface))
return 1;
- }
-
- if (!build_log.OpenForWrite(log_path, &err)) {
- Error("opening build log: %s", err.c_str());
- return 1;
- }
if (!rebuilt_manifest) { // Don't get caught in an infinite loop by a rebuild
// target that is never up to date.
- if (RebuildManifest(globals.state, globals.config, input_file, &err)) {
+ Builder manifest_builder(globals.state, config, &build_log,
+ &disk_interface);
+ if (RebuildManifest(&manifest_builder, input_file, &err)) {
rebuilt_manifest = true;
globals.ResetState();
goto reload;
@@ -769,20 +855,31 @@ reload:
}
}
- int result = RunBuild(&globals, argc, argv);
- if (g_metrics) {
- g_metrics->Report();
+ Builder builder(globals.state, config, &build_log, &disk_interface);
+ int result = RunBuild(&builder, argc, argv);
+ if (g_metrics)
+ DumpMetrics(&globals);
+ return result;
+}
- printf("\n");
- int count = (int)globals.state->paths_.size();
- int buckets =
-#ifdef _MSC_VER
- (int)globals.state->paths_.comp.bucket_size;
+} // anonymous namespace
+
+int main(int argc, char** argv) {
+#if !defined(NINJA_BOOTSTRAP) && defined(_MSC_VER)
+ // Set a handler to catch crashes not caught by the __try..__except
+ // block (e.g. an exception in a stack-unwind-block).
+ set_terminate(TerminateHandler);
+ __try {
+ // Running inside __try ... __except suppresses any Windows error
+ // dialogs for errors such as bad_alloc.
+ return NinjaMain(argc, argv);
+ }
+ __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
+ // Common error situations return exitCode=1. 2 was chosen to
+ // indicate a more serious problem.
+ return 2;
+ }
#else
- (int)globals.state->paths_.bucket_count();
+ return NinjaMain(argc, argv);
#endif
- printf("path->node hash load %.2f (%d entries / %d buckets)\n",
- count / (double) buckets, count, buckets);
- }
- return result;
}