diff options
-rw-r--r-- | HACKING.md | 2 | ||||
-rw-r--r-- | RELEASING | 2 | ||||
-rwxr-xr-x | bootstrap.py | 6 | ||||
-rwxr-xr-x | configure.py | 5 | ||||
-rw-r--r-- | platform_helper.py | 4 | ||||
-rwxr-xr-x | src/browse.py | 8 | ||||
-rw-r--r-- | src/build_test.cc | 64 | ||||
-rw-r--r-- | src/graph.cc | 1 | ||||
-rw-r--r-- | src/includes_normalize-win32.cc | 4 | ||||
-rw-r--r-- | src/includes_normalize_test.cc | 20 | ||||
-rw-r--r-- | src/msvc_helper-win32.cc | 7 | ||||
-rw-r--r-- | src/msvc_helper.h | 5 | ||||
-rw-r--r-- | src/msvc_helper_test.cc | 1 | ||||
-rw-r--r-- | src/ninja.cc | 473 | ||||
-rw-r--r-- | src/subprocess-posix.cc | 6 | ||||
-rw-r--r-- | src/util.cc | 5 | ||||
-rw-r--r-- | src/util.h | 2 | ||||
-rw-r--r-- | src/version.cc | 2 |
18 files changed, 389 insertions, 228 deletions
@@ -62,7 +62,7 @@ googletest (gtest) library. * On newer Ubuntus it's only distributed as source apt-get install libgtest-dev - ./configure --with-gtest=/usr/src/gtest + ./configure.py --with-gtest=/usr/src/gtest * Otherwise you need to download it, unpack it, and pass `--with-gtest` to `configure.py`. Get it from [its downloads @@ -8,3 +8,5 @@ Notes to myself on all the steps to make for a Ninja release. 6. commit, tag, push (don't forget to push --tags) 7. construct release notes from prior notes credits: git shortlog -s --no-merges REV.. +8. update home page mention of latest version. + diff --git a/bootstrap.py b/bootstrap.py index cff10ba..5682bf1 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -36,6 +36,8 @@ parser.add_option('--x64', action='store_true', parser.add_option('--platform', help='target platform (' + '/'.join(platform_helper.platforms()) + ')', choices=platform_helper.platforms()) +parser.add_option('--force-pselect', action='store_true', + help="ppoll() is used by default on Linux and OpenBSD, but older versions might need to use pselect instead",) (options, conf_args) = parser.parse_args() @@ -107,6 +109,10 @@ else: cflags.append('-D_WIN32_WINNT=0x0501') if options.x64: cflags.append('-m64') +if (platform.is_linux() or platform.is_openbsd()) and not options.force_pselect: + cflags.append('-DUSE_PPOLL') +if options.force_pselect: + conf_args.append("--force-pselect") args.extend(cflags) args.extend(ldflags) binary = 'ninja.bootstrap' diff --git a/configure.py b/configure.py index 7c90e66..22eb1e5 100755 --- a/configure.py +++ b/configure.py @@ -47,6 +47,8 @@ parser.add_option('--with-gtest', metavar='PATH', parser.add_option('--with-python', metavar='EXE', help='use EXE as the Python interpreter', default=os.path.basename(sys.executable)) +parser.add_option('--force-pselect', action='store_true', + help="ppoll() is used by default on Linux and OpenBSD, but older versions might need to use pselect instead",) (options, args) = parser.parse_args() if args: print('ERROR: extra unparsed command-line arguments:', args) @@ -163,6 +165,9 @@ else: cflags.append('-fno-omit-frame-pointer') libs.extend(['-Wl,--no-as-needed', '-lprofiler']) +if (platform.is_linux() or platform.is_openbsd()) and not options.force_pselect: + cflags.append('-DUSE_PPOLL') + def shell_escape(str): """Escape str such that it's interpreted as a single argument by the shell.""" diff --git a/platform_helper.py b/platform_helper.py index 97827c3..5097f49 100644 --- a/platform_helper.py +++ b/platform_helper.py @@ -19,7 +19,7 @@ import sys def platforms(): return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5', - 'mingw', 'msvc'] + 'mingw', 'msvc', 'gnukfreebsd8'] class Platform( object ): def __init__( self, platform): @@ -31,6 +31,8 @@ class Platform( object ): self._platform = 'linux' elif self._platform.startswith('freebsd'): self._platform = 'freebsd' + elif self._platform.startswith('gnukfreebsd8'): + self._platform = 'freebsd' elif self._platform.startswith('openbsd'): self._platform = 'openbsd' elif self._platform.startswith('solaris'): diff --git a/src/browse.py b/src/browse.py index 652bac2..9e59bd8 100755 --- a/src/browse.py +++ b/src/browse.py @@ -26,6 +26,8 @@ try: import http.server as httpserver except ImportError: import BaseHTTPServer as httpserver +import os +import socket import subprocess import sys import webbrowser @@ -183,8 +185,10 @@ class RequestHandler(httpserver.BaseHTTPRequestHandler): port = 8000 httpd = httpserver.HTTPServer(('',port), RequestHandler) try: - print('Web server running on port %d, ctl-C to abort...' % port) - webbrowser.open_new('http://localhost:%s' % port) + hostname = socket.gethostname() + print('Web server running on %s:%d, ctl-C to abort...' % (hostname,port) ) + print('Web server pid %d' % os.getpid(), file=sys.stderr ) + webbrowser.open_new('http://%s:%s' % (hostname, port) ) httpd.serve_forever() except KeyboardInterrupt: print() diff --git a/src/build_test.cc b/src/build_test.cc index 90c328a..ed9ade3 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1602,3 +1602,67 @@ TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) { builder.command_runner_.release(); } + +/// Check that a restat rule generating a header cancels compilations correctly. +TEST_F(BuildWithDepsLogTest, RestatDepfileDependency) { + string err; + // Note: in1 was created by the superclass SetUp(). + const char* manifest = + "rule true\n" + " command = true\n" // Would be "write if out-of-date" in reality. + " restat = 1\n" + "build header.h: true header.in\n" + "build out: cat in1\n" + " deps = gcc\n" + " depfile = in1.d\n"; + { + State state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // Run the build once, everything should be ok. + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + EXPECT_TRUE(builder.AddTarget("out", &err)); + ASSERT_EQ("", err); + fs_.Create("in1.d", "out: header.h"); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + deps_log.Close(); + builder.command_runner_.release(); + } + + { + State state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // Touch the input of the restat rule. + fs_.Tick(); + fs_.Create("header.in", ""); + + // Run the build again. + DepsLog deps_log; + ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err)); + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + command_runner_.commands_ran_.clear(); + EXPECT_TRUE(builder.AddTarget("out", &err)); + ASSERT_EQ("", err); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + // Rule "true" should have run again, but the build of "out" should have + // been cancelled due to restat propagating through the depfile header. + EXPECT_EQ(1u, command_runner_.commands_ran_.size()); + + builder.command_runner_.release(); + } +} diff --git a/src/graph.cc b/src/graph.cc index b245e52..7a57753 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -418,6 +418,7 @@ bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, TimeStamp* deps_mtime, PreallocateSpace(edge, deps->node_count); for (int i = 0; i < deps->node_count; ++i, ++implicit_dep) { *implicit_dep = deps->nodes[i]; + deps->nodes[i]->AddOutEdge(edge); CreatePhonyInEdge(*implicit_dep); } return true; diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc index 4bc8756..05ce75d 100644 --- a/src/includes_normalize-win32.cc +++ b/src/includes_normalize-win32.cc @@ -110,6 +110,6 @@ string IncludesNormalize::Normalize(const string& input, } StringPiece partially_fixed(copy, len); if (!SameDrive(partially_fixed, relative_to)) - return ToLower(partially_fixed.AsString()); - return ToLower(Relativize(partially_fixed, relative_to)); + return partially_fixed.AsString(); + return Relativize(partially_fixed, relative_to); } diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc index 29e6755..1713d5d 100644 --- a/src/includes_normalize_test.cc +++ b/src/includes_normalize_test.cc @@ -52,11 +52,11 @@ TEST(IncludesNormalize, WithRelative) { TEST(IncludesNormalize, Case) { EXPECT_EQ("b", IncludesNormalize::Normalize("Abc\\..\\b", NULL)); - EXPECT_EQ("bdef", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\.\\b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\./b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\.\\B", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("A\\./B", NULL)); + EXPECT_EQ("BdEf", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL)); + EXPECT_EQ("A\\b", IncludesNormalize::Normalize("A\\.\\b", NULL)); + EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL)); + EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\.\\B", NULL)); + EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\./B", NULL)); } TEST(IncludesNormalize, Join) { @@ -91,12 +91,12 @@ TEST(IncludesNormalize, DifferentDrive) { EXPECT_EQ("stuff.h", IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "p:\\vs08")); EXPECT_EQ("stuff.h", - IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "p:\\vs08")); - EXPECT_EQ("p:\\vs08\\stuff.h", - IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "c:\\vs08")); - EXPECT_EQ("p:\\vs08\\stuff.h", - IncludesNormalize::Normalize("P:\\vs08\\stuff.h", "D:\\stuff/things")); + IncludesNormalize::Normalize("P:\\Vs08\\stuff.h", "p:\\vs08")); EXPECT_EQ("p:\\vs08\\stuff.h", + IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "c:\\vs08")); + EXPECT_EQ("P:\\vs08\\stufF.h", + IncludesNormalize::Normalize("P:\\vs08\\stufF.h", "D:\\stuff/things")); + EXPECT_EQ("P:\\vs08\\stuff.h", IncludesNormalize::Normalize("P:/vs08\\stuff.h", "D:\\stuff/things")); // TODO: this fails; fix it. //EXPECT_EQ("P:\\wee\\stuff.h", diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc index be2a5e0..7c45029 100644 --- a/src/msvc_helper-win32.cc +++ b/src/msvc_helper-win32.cc @@ -14,6 +14,7 @@ #include "msvc_helper.h" +#include <algorithm> #include <stdio.h> #include <string.h> #include <windows.h> @@ -63,14 +64,16 @@ string CLParser::FilterShowIncludes(const string& line) { } // static -bool CLParser::IsSystemInclude(const string& path) { +bool CLParser::IsSystemInclude(string path) { + transform(path.begin(), path.end(), path.begin(), ::tolower); // TODO: this is a heuristic, perhaps there's a better way? return (path.find("program files") != string::npos || path.find("microsoft visual studio") != string::npos); } // static -bool CLParser::FilterInputFilename(const string& line) { +bool CLParser::FilterInputFilename(string line) { + transform(line.begin(), line.end(), line.begin(), ::tolower); // TODO: other extensions, like .asm? return EndsWith(line, ".c") || EndsWith(line, ".cc") || diff --git a/src/msvc_helper.h b/src/msvc_helper.h index 32ab606..e207485 100644 --- a/src/msvc_helper.h +++ b/src/msvc_helper.h @@ -30,15 +30,14 @@ struct CLParser { static string FilterShowIncludes(const string& line); /// Return true if a mentioned include file is a system path. - /// Expects the path to already by normalized (including lower case). /// Filtering these out reduces dependency information considerably. - static bool IsSystemInclude(const string& path); + static bool IsSystemInclude(string path); /// Parse a line of cl.exe output and return true if it looks like /// it's printing an input filename. This is a heuristic but it appears /// to be the best we can do. /// Exposed for testing. - static bool FilterInputFilename(const string& line); + static bool FilterInputFilename(string line); /// Parse the full output of cl, returning the output (if any) that /// should printed. diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc index 1e1cbde..02f2863 100644 --- a/src/msvc_helper_test.cc +++ b/src/msvc_helper_test.cc @@ -35,6 +35,7 @@ TEST(CLParserTest, FilterInputFilename) { ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc")); ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc")); ASSERT_TRUE(CLParser::FilterInputFilename("baz.c")); + ASSERT_TRUE(CLParser::FilterInputFilename("FOOBAR.CC")); ASSERT_FALSE(CLParser::FilterInputFilename( "src\\cl_helper.cc(166) : fatal error C1075: end " diff --git a/src/ninja.cc b/src/ninja.cc index b4797ed..3b381b7 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -42,34 +42,100 @@ #include "util.h" #include "version.h" +#ifdef _MSC_VER // 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 { -/// Global information passed into subtools. -struct Globals { - Globals() : state(new State()) {} - ~Globals() { - delete state; - } +struct Tool; - /// Deletes and recreates state so it is empty. - void ResetState() { - delete state; - state = new State(); - } +/// 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; +}; + +/// 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 { + NinjaMain(const char* ninja_command, const BuildConfig& config) : + ninja_command_(ninja_command), config_(config) {} /// Command line used to run Ninja. - const char* ninja_command; + const char* ninja_command_; + /// 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; -}; + const BuildConfig& config_; + + /// Loaded state (rules, nodes). + State state_; + + /// Functions for accesssing 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)(int, char**); + + /// Get the Node for a given command-line path, handling features like + /// spell correction. + Node* CollectTarget(const char* cpath, string* err); + + /// CollectTarget for all command-line arguments, filling in \a targets. + bool CollectTargetsFromArgs(int argc, char* argv[], + vector<Node*>* targets, string* err); + + // The various subcommands, run via "-t XXX". + int ToolGraph(int argc, char* argv[]); + int ToolQuery(int argc, char* argv[]); + int ToolBrowse(int argc, char* argv[]); + int ToolMSVC(int argc, char* argv[]); + int ToolTargets(int argc, char* argv[]); + int ToolCommands(int argc, char* argv[]); + int ToolClean(int argc, char* argv[]); + int ToolCompilationDatabase(int argc, char* argv[]); + int ToolUrtle(int argc, char** argv); -/// The type of functions that are the entry points to tools (subcommands). -typedef int (*ToolFunc)(Globals*, int, char**); + /// Open the build log. + /// @return false on error. + bool OpenBuildLog(); + + /// Open the deps log: load it, then open for writing. + /// @return false on error. + bool OpenDepsLog(); + + /// 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); + + /// Build the targets listed on the command line. + /// @return an exit code. + int RunBuild(int argc, char** argv); + + /// Dump the output requested by '-d stats'. + void DumpMetrics(); +}; /// Subtools, accessible via "-t foo". struct Tool { @@ -86,10 +152,13 @@ struct Tool { /// Run after loading build.ninja. RUN_AFTER_LOAD, + + /// Run after loading the build/deps logs. + RUN_AFTER_LOGS, } when; /// Implementation of the tool. - ToolFunc func; + NinjaMain::ToolFunc func; }; /// Print usage information. @@ -143,20 +212,21 @@ struct RealFileReader : public ManifestParser::FileReader { /// Rebuild the build manifest, if necessary. /// Returns true if the manifest was rebuilt. -bool RebuildManifest(Builder* builder, const char* input_file, string* err) { +bool NinjaMain::RebuildManifest(const char* input_file, string* err) { string path = input_file; if (!CanonicalizePath(&path, err)) return false; - Node* node = builder->state_->LookupNode(path); + Node* node = state_.LookupNode(path); if (!node) return false; - if (!builder->AddTarget(node, err)) + Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_); + if (!builder.AddTarget(node, err)) return false; - if (builder->AlreadyUpToDate()) + if (builder.AlreadyUpToDate()) return false; // Not an error, but we didn't rebuild. - if (!builder->Build(err)) + if (!builder.Build(err)) return false; // The manifest was only rebuilt if it is now dirty (it may have been cleaned @@ -164,7 +234,7 @@ bool RebuildManifest(Builder* builder, const char* input_file, string* err) { return node->dirty(); } -Node* CollectTarget(State* state, const char* cpath, string* err) { +Node* NinjaMain::CollectTarget(const char* cpath, string* err) { string path = cpath; if (!CanonicalizePath(&path, err)) return NULL; @@ -176,7 +246,7 @@ Node* CollectTarget(State* state, const char* cpath, string* err) { first_dependent = true; } - Node* node = state->LookupNode(path); + Node* node = state_.LookupNode(path); if (node) { if (first_dependent) { if (node->out_edges().empty()) { @@ -199,7 +269,7 @@ Node* CollectTarget(State* state, const char* cpath, string* err) { } else if (path == "help") { *err += ", did you mean 'ninja -h'?"; } else { - Node* suggestion = state->SpellcheckNode(path); + Node* suggestion = state_.SpellcheckNode(path); if (suggestion) { *err += ", did you mean '" + suggestion->path() + "'?"; } @@ -208,15 +278,15 @@ Node* CollectTarget(State* state, const char* cpath, string* err) { } } -bool CollectTargetsFromArgs(State* state, int argc, char* argv[], - vector<Node*>* targets, string* err) { +bool NinjaMain::CollectTargetsFromArgs(int argc, char* argv[], + vector<Node*>* targets, string* err) { if (argc == 0) { - *targets = state->DefaultNodes(err); + *targets = state_.DefaultNodes(err); return err->empty(); } for (int i = 0; i < argc; ++i) { - Node* node = CollectTarget(state, argv[i], err); + Node* node = CollectTarget(argv[i], err); if (node == NULL) return false; targets->push_back(node); @@ -224,10 +294,10 @@ bool CollectTargetsFromArgs(State* state, int argc, char* argv[], return true; } -int ToolGraph(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolGraph(int argc, char* argv[]) { vector<Node*> nodes; string err; - if (!CollectTargetsFromArgs(globals->state, argc, argv, &nodes, &err)) { + if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { Error("%s", err.c_str()); return 1; } @@ -241,14 +311,15 @@ int ToolGraph(Globals* globals, int argc, char* argv[]) { return 0; } -int ToolQuery(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolQuery(int argc, char* argv[]) { if (argc == 0) { Error("expected a target to query"); return 1; } + for (int i = 0; i < argc; ++i) { string err; - Node* node = CollectTarget(globals->state, argv[i], &err); + Node* node = CollectTarget(argv[i], &err); if (!node) { Error("%s", err.c_str()); return 1; @@ -279,19 +350,19 @@ int ToolQuery(Globals* globals, int argc, char* argv[]) { } #if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) -int ToolBrowse(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolBrowse(int argc, char* argv[]) { if (argc < 1) { Error("expected a target to browse"); return 1; } - RunBrowsePython(globals->state, globals->ninja_command, argv[0]); + RunBrowsePython(&state_, ninja_command_, argv[0]); // If we get here, the browse failed. return 1; } #endif // _WIN32 -#if defined(_WIN32) -int ToolMSVC(Globals* globals, int argc, char* argv[]) { +#if defined(_MSC_VER) +int NinjaMain::ToolMSVC(int argc, char* argv[]) { // Reset getopt: push one argument onto the front of argv, reset optind. argc++; argv--; @@ -366,7 +437,7 @@ int ToolTargetsList(State* state) { return 0; } -int ToolTargets(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolTargets(int argc, char* argv[]) { int depth = 1; if (argc >= 1) { string mode = argv[0]; @@ -375,17 +446,17 @@ int ToolTargets(Globals* globals, int argc, char* argv[]) { if (argc > 1) rule = argv[1]; if (rule.empty()) - return ToolTargetsSourceList(globals->state); + return ToolTargetsSourceList(&state_); else - return ToolTargetsList(globals->state, rule); + return ToolTargetsList(&state_, rule); } else if (mode == "depth") { if (argc > 1) depth = atoi(argv[1]); } else if (mode == "all") { - return ToolTargetsList(globals->state); + return ToolTargetsList(&state_); } else { const char* suggestion = - SpellcheckString(mode, "rule", "depth", "all", NULL); + SpellcheckString(mode.c_str(), "rule", "depth", "all", NULL); if (suggestion) { Error("unknown target tool mode '%s', did you mean '%s'?", mode.c_str(), suggestion); @@ -397,7 +468,7 @@ int ToolTargets(Globals* globals, int argc, char* argv[]) { } string err; - vector<Node*> root_nodes = globals->state->RootNodes(&err); + vector<Node*> root_nodes = state_.RootNodes(&err); if (err.empty()) { return ToolTargetsList(root_nodes, depth, 0); } else { @@ -420,10 +491,10 @@ void PrintCommands(Edge* edge, set<Edge*>* seen) { puts(edge->EvaluateCommand().c_str()); } -int ToolCommands(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolCommands(int argc, char* argv[]) { vector<Node*> nodes; string err; - if (!CollectTargetsFromArgs(globals->state, argc, argv, &nodes, &err)) { + if (!CollectTargetsFromArgs(argc, argv, &nodes, &err)) { Error("%s", err.c_str()); return 1; } @@ -435,7 +506,7 @@ int ToolCommands(Globals* globals, int argc, char* argv[]) { return 0; } -int ToolClean(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolClean(int argc, char* argv[]) { // The clean tool uses getopt, and expects argv[0] to contain the name of // the tool, i.e. "clean". argc++; @@ -473,7 +544,7 @@ int ToolClean(Globals* globals, int argc, char* argv[]) { return 1; } - Cleaner cleaner(globals->state, *globals->config); + Cleaner cleaner(&state_, config_); if (argc >= 1) { if (clean_rules) return cleaner.CleanRules(argc, argv); @@ -493,25 +564,29 @@ void EncodeJSONString(const char *str) { } } -int ToolCompilationDatabase(Globals* globals, int argc, char* argv[]) { +int NinjaMain::ToolCompilationDatabase(int argc, char* argv[]) { bool first = true; - char cwd[PATH_MAX]; + vector<char> cwd; - if (!getcwd(cwd, PATH_MAX)) { + do { + cwd.resize(cwd.size() + 1024); + errno = 0; + } while (!getcwd(&cwd[0], cwd.size()) && errno == ERANGE); + if (errno != 0 && errno != ERANGE) { Error("cannot determine working directory: %s", strerror(errno)); return 1; } putchar('['); - for (vector<Edge*>::iterator e = globals->state->edges_.begin(); - e != globals->state->edges_.end(); ++e) { + for (vector<Edge*>::iterator e = state_.edges_.begin(); + e != state_.edges_.end(); ++e) { for (int i = 0; i != argc; ++i) { if ((*e)->rule_->name() == argv[i]) { if (!first) putchar(','); printf("\n {\n \"directory\": \""); - EncodeJSONString(cwd); + EncodeJSONString(&cwd[0]); printf("\",\n \"command\": \""); EncodeJSONString((*e)->EvaluateCommand().c_str()); printf("\",\n \"file\": \""); @@ -527,7 +602,7 @@ int ToolCompilationDatabase(Globals* globals, int argc, char* argv[]) { return 0; } -int ToolUrtle(Globals* globals, int argc, char** argv) { +int NinjaMain::ToolUrtle(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 " @@ -554,31 +629,31 @@ int ToolUrtle(Globals* globals, int argc, char** argv) { } /// 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) { +/// Returns a Tool, or NULL if Ninja should exit. +const Tool* ChooseTool(const string& tool_name) { static const Tool kTools[] = { #if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) { "browse", "browse dependency graph in a web browser", - Tool::RUN_AFTER_LOAD, ToolBrowse }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse }, #endif -#if defined(_WIN32) +#if defined(_MSC_VER) { "msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)", - Tool::RUN_AFTER_FLAGS, ToolMSVC }, + Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolMSVC }, #endif { "clean", "clean built files", - Tool::RUN_AFTER_LOAD, ToolClean }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolClean }, { "commands", "list all commands required to rebuild given targets", - Tool::RUN_AFTER_LOAD, ToolCommands }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCommands }, { "graph", "output graphviz dot file for targets", - Tool::RUN_AFTER_LOAD, ToolGraph }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolGraph }, { "query", "show inputs/outputs for a path", - Tool::RUN_AFTER_LOAD, ToolQuery }, + Tool::RUN_AFTER_LOGS, &NinjaMain::ToolQuery }, { "targets", "list targets by their rule or depth in the DAG", - Tool::RUN_AFTER_LOAD, ToolTargets }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolTargets }, { "compdb", "dump JSON compilation database to stdout", - Tool::RUN_AFTER_LOAD, ToolCompilationDatabase }, + Tool::RUN_AFTER_LOAD, &NinjaMain::ToolCompilationDatabase }, { "urtle", NULL, - Tool::RUN_AFTER_FLAGS, ToolUrtle }, + Tool::RUN_AFTER_FLAGS, &NinjaMain::ToolUrtle }, { NULL, NULL, Tool::RUN_AFTER_FLAGS, NULL } }; @@ -588,14 +663,12 @@ int ChooseTool(const string& tool_name, const Tool** tool_out) { if (tool->desc) printf("%10s %s\n", tool->name, tool->desc); } - return 0; + return NULL; } for (const Tool* tool = &kTools[0]; tool->name; ++tool) { - if (tool->name == tool_name) { - *tool_out = tool; - return 0; - } + if (tool->name == tool_name) + return tool; } vector<const char*> words; @@ -603,17 +676,17 @@ int ChooseTool(const string& tool_name, const Tool** tool_out) { words.push_back(tool->name); const char* suggestion = SpellcheckStringV(tool_name, words); if (suggestion) { - Error("unknown tool '%s', did you mean '%s'?", + Fatal("unknown tool '%s', did you mean '%s'?", tool_name.c_str(), suggestion); } else { - Error("unknown tool '%s'", tool_name.c_str()); + Fatal("unknown tool '%s'", tool_name.c_str()); } - return 1; + return NULL; // Not reached. } /// Enable a debugging mode. Returns false if Ninja should exit instead /// of continuing. -bool DebugEnable(const string& name, Globals* globals) { +bool DebugEnable(const string& name) { if (name == "list") { printf("debugging modes:\n" " stats print operation counts/timing info\n" @@ -628,7 +701,7 @@ bool DebugEnable(const string& name, Globals* globals) { return true; } else { const char* suggestion = - SpellcheckString(name, "stats", "explain", NULL); + SpellcheckString(name.c_str(), "stats", "explain", NULL); if (suggestion) { Error("unknown debug setting '%s', did you mean '%s'?", name.c_str(), suggestion); @@ -639,16 +712,13 @@ bool DebugEnable(const string& name, Globals* globals) { } } -/// Open the build log. -/// @return false on error. -bool OpenBuildLog(BuildLog* build_log, const string& build_dir, - Globals* globals, DiskInterface* disk_interface) { +bool NinjaMain::OpenBuildLog() { string log_path = ".ninja_log"; - if (!build_dir.empty()) - log_path = build_dir + "/" + log_path; + if (!build_dir_.empty()) + log_path = build_dir_ + "/" + log_path; string err; - if (!build_log->Load(log_path, &err)) { + if (!build_log_.Load(log_path, &err)) { Error("loading build log %s: %s", log_path.c_str(), err.c_str()); return false; } @@ -658,8 +728,8 @@ bool OpenBuildLog(BuildLog* build_log, const string& build_dir, err.clear(); } - if (!globals->config->dry_run) { - if (!build_log->OpenForWrite(log_path, &err)) { + if (!config_.dry_run) { + if (!build_log_.OpenForWrite(log_path, &err)) { Error("opening build log: %s", err.c_str()); return false; } @@ -670,14 +740,13 @@ bool OpenBuildLog(BuildLog* build_log, const string& build_dir, /// Open the deps log: load it, then open for writing. /// @return false on error. -bool OpenDepsLog(DepsLog* deps_log, const string& build_dir, - Globals* globals, DiskInterface* disk_interface) { +bool NinjaMain::OpenDepsLog() { string path = ".ninja_deps"; - if (!build_dir.empty()) - path = build_dir + "/" + path; + if (!build_dir_.empty()) + path = build_dir_ + "/" + path; string err; - if (!deps_log->Load(path, globals->state, &err)) { + if (!deps_log_.Load(path, &state_, &err)) { Error("loading deps log %s: %s", path.c_str(), err.c_str()); return false; } @@ -687,8 +756,8 @@ bool OpenDepsLog(DepsLog* deps_log, const string& build_dir, err.clear(); } - if (!globals->config->dry_run) { - if (!deps_log->OpenForWrite(path, &err)) { + if (!config_.dry_run) { + if (!deps_log_.OpenForWrite(path, &err)) { Error("opening deps log: %s", err.c_str()); return false; } @@ -697,28 +766,39 @@ bool OpenDepsLog(DepsLog* deps_log, const string& build_dir, return true; } - -/// Dump the output requested by '-d stats'. -void DumpMetrics(Globals* globals) { +void NinjaMain::DumpMetrics() { g_metrics->Report(); printf("\n"); - int count = (int)globals->state->paths_.size(); - int buckets = (int)globals->state->paths_.bucket_count(); + 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); } -int RunBuild(Builder* builder, int argc, char** argv) { +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) { string err; vector<Node*> targets; - if (!CollectTargetsFromArgs(builder->state_, argc, argv, &targets, &err)) { + if (!CollectTargetsFromArgs(argc, argv, &targets, &err)) { Error("%s", err.c_str()); return 1; } + Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_); 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; @@ -729,15 +809,15 @@ int RunBuild(Builder* builder, 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()); if (err.find("interrupted by user") != string::npos) { - return 2; + return 2; } return 1; } @@ -747,13 +827,6 @@ int RunBuild(Builder* builder, int argc, char** argv) { #ifdef _MSC_VER -} // 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 @@ -775,18 +848,11 @@ int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) { #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_name; - - setvbuf(stdout, NULL, _IOLBF, BUFSIZ); - - config.parallelism = GuessParallelism(); +/// 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) { + config->parallelism = GuessParallelism(); enum { OPT_VERSION = 1 }; const option kLongOptions[] = { @@ -796,20 +862,25 @@ int NinjaMain(int argc, char** argv) { }; int opt; - while (tool_name.empty() && - (opt = getopt_long(argc, argv, "d:f:j:k:l:nt:vC:h", kLongOptions, + while (!options->tool && + (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vC:h", kLongOptions, NULL)) != -1) { switch (opt) { case 'd': - if (!DebugEnable(optarg, &globals)) + if (!DebugEnable(optarg)) return 1; break; case 'f': - input_file = optarg; + options->input_file = optarg; break; - case 'j': - config.parallelism = atoi(optarg); + case 'j': { + char* end; + int value = strtol(optarg, &end, 10); + if (*end != 0 || value <= 0) + Fatal("invalid -j parameter"); + config->parallelism = value; break; + } case 'k': { char* end; int value = strtol(optarg, &end, 10); @@ -819,7 +890,7 @@ int NinjaMain(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. - config.failures_allowed = value > 0 ? value : INT_MAX; + config->failures_allowed = value > 0 ? value : INT_MAX; break; } case 'l': { @@ -827,113 +898,113 @@ int NinjaMain(int argc, char** argv) { double value = strtod(optarg, &end); if (end == optarg) Fatal("-l parameter not numeric: did you mean -l 0.0?"); - config.max_load_average = value; + config->max_load_average = value; break; } case 'n': - config.dry_run = true; + config->dry_run = true; break; case 't': - tool_name = optarg; + options->tool = ChooseTool(optarg); + if (!options->tool) + return 0; break; case 'v': - config.verbosity = BuildConfig::VERBOSE; + config->verbosity = BuildConfig::VERBOSE; break; case 'C': - working_dir = optarg; + options->working_dir = optarg; break; case OPT_VERSION: printf("%s\n", kNinjaVersion); return 0; case 'h': default: - Usage(config); + Usage(*config); return 1; } } - argv += optind; - argc -= optind; + *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; - } + return -1; +} + +int real_main(int argc, char** argv) { + BuildConfig config; + Options options = {}; + options.input_file = "build.ninja"; + + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); - if (tool && tool->when == Tool::RUN_AFTER_FLAGS) - return tool->func(&globals, argc, argv); + int exit_code = ReadFlags(&argc, &argv, &options, &config); + if (exit_code >= 0) + return exit_code; - if (working_dir) { + 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(argv[0], config); + return (ninja.*options.tool->func)(argc, argv); + } + + 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 (!tool) - printf("ninja: Entering directory `%s'\n", working_dir); - if (chdir(working_dir) < 0) { - Fatal("chdir to '%s' - %s", working_dir, strerror(errno)); + if (!options.tool) + printf("ninja: Entering directory `%s'\n", options.working_dir); + if (chdir(options.working_dir) < 0) { + Fatal("chdir to '%s' - %s", options.working_dir, strerror(errno)); } } - bool rebuilt_manifest = false; - -reload: - RealFileReader file_reader; - ManifestParser parser(globals.state, &file_reader); - string err; - if (!parser.Load(input_file, &err)) { - Error("%s", err.c_str()); - return 1; - } - - if (tool && tool->when == Tool::RUN_AFTER_LOAD) - return tool->func(&globals, argc, argv); + // The build can take up to 2 passes: one to rebuild the manifest, then + // another to build the desired target. + for (int cycle = 0; cycle < 2; ++cycle) { + NinjaMain ninja(argv[0], config); - RealDiskInterface disk_interface; - - // Create the build dir if it doesn't exist. - const string build_dir = globals.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)); + RealFileReader file_reader; + ManifestParser parser(&ninja.state_, &file_reader); + string err; + if (!parser.Load(options.input_file, &err)) { + Error("%s", err.c_str()); return 1; } - } - BuildLog build_log; - if (!OpenBuildLog(&build_log, build_dir, &globals, &disk_interface)) - return 1; + if (options.tool && options.tool->when == Tool::RUN_AFTER_LOAD) + return (ninja.*options.tool->func)(argc, argv); - DepsLog deps_log; - if (!OpenDepsLog(&deps_log, build_dir, &globals, &disk_interface)) - return 1; + if (!ninja.EnsureBuildDirExists()) + return 1; - if (!rebuilt_manifest) { // Don't get caught in an infinite loop by a rebuild - // target that is never up to date. - Builder manifest_builder(globals.state, config, &build_log, &deps_log, - &disk_interface); - if (RebuildManifest(&manifest_builder, input_file, &err)) { - rebuilt_manifest = true; - globals.ResetState(); - goto reload; - } else if (!err.empty()) { - Error("rebuilding '%s': %s", input_file, err.c_str()); + if (!ninja.OpenBuildLog() || !ninja.OpenDepsLog()) return 1; + + if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS) + return (ninja.*options.tool->func)(argc, argv); + + // The first time through, attempt to rebuild the manifest before + // building anything else. + if (cycle == 0) { + if (ninja.RebuildManifest(options.input_file, &err)) { + // Start the build over with the new manifest. + continue; + } else if (!err.empty()) { + Error("rebuilding '%s': %s", options.input_file, err.c_str()); + return 1; + } } + + int result = ninja.RunBuild(argc, argv); + if (g_metrics) + ninja.DumpMetrics(); + return result; } - Builder builder(globals.state, config, &build_log, &deps_log, - &disk_interface); - int result = RunBuild(&builder, argc, argv); - if (g_metrics) - DumpMetrics(&globals); - return result; + return 1; // Shouldn't be reached. } } // anonymous namespace @@ -946,7 +1017,7 @@ int main(int argc, char** argv) { __try { // Running inside __try ... __except suppresses any Windows error // dialogs for errors such as bad_alloc. - return NinjaMain(argc, argv); + return real_main(argc, argv); } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { // Common error situations return exitCode=1. 2 was chosen to @@ -954,6 +1025,6 @@ int main(int argc, char** argv) { return 2; } #else - return NinjaMain(argc, argv); + return real_main(argc, argv); #endif } diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index 339edfe..b396f84 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -40,12 +40,12 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { if (pipe(output_pipe) < 0) Fatal("pipe: %s", strerror(errno)); fd_ = output_pipe[0]; -#if !defined(linux) && !defined(__OpenBSD__) +#if !defined(USE_PPOLL) // On Linux and OpenBSD, we use ppoll in DoWork(); elsewhere we use pselect // and so must avoid overly-large FDs. if (fd_ >= static_cast<int>(FD_SETSIZE)) Fatal("pipe: %s", strerror(EMFILE)); -#endif // !linux && !__OpenBSD__ +#endif // !USE_PPOLL SetCloseOnExec(fd_); pid_ = fork(); @@ -178,7 +178,7 @@ Subprocess *SubprocessSet::Add(const string& command) { return subprocess; } -#if defined(linux) || defined(__OpenBSD__) +#ifdef USE_PPOLL bool SubprocessSet::DoWork() { vector<pollfd> fds; nfds_t nfds = 0; diff --git a/src/util.cc b/src/util.cc index fa72dd2..b9c2c0d 100644 --- a/src/util.cc +++ b/src/util.cc @@ -234,13 +234,16 @@ const char* SpellcheckStringV(const string& text, return result; } -const char* SpellcheckString(const string& text, ...) { +const char* SpellcheckString(const char* text, ...) { + // Note: This takes a const char* instead of a string& because using + // va_start() with a reference parameter is undefined behavior. va_list ap; va_start(ap, text); vector<const char*> words; const char* word; while ((word = va_arg(ap, const char*))) words.push_back(word); + va_end(ap); return SpellcheckStringV(text, words); } @@ -59,7 +59,7 @@ const char* SpellcheckStringV(const string& text, const vector<const char*>& words); /// Like SpellcheckStringV, but takes a NULL-terminated list. -const char* SpellcheckString(const string& text, ...); +const char* SpellcheckString(const char* text, ...); /// Removes all Ansi escape codes (http://www.termsys.demon.co.uk/vtansi.htm). string StripAnsiEscapeCodes(const string& in); diff --git a/src/version.cc b/src/version.cc index 54818a0..28280aa 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.3.3"; +const char* kNinjaVersion = "1.3.4"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.'); |