diff options
-rwxr-xr-x | bootstrap.py | 20 | ||||
-rwxr-xr-x | configure.py | 14 | ||||
-rw-r--r-- | doc/manual.asciidoc | 32 | ||||
-rw-r--r-- | misc/ninja_syntax.py | 6 | ||||
-rw-r--r-- | src/build.cc | 65 | ||||
-rw-r--r-- | src/build.h | 3 | ||||
-rw-r--r-- | src/build_log.cc | 2 | ||||
-rw-r--r-- | src/build_log.h | 5 | ||||
-rw-r--r-- | src/build_log_test.cc | 4 | ||||
-rw-r--r-- | src/build_test.cc | 101 | ||||
-rw-r--r-- | src/clean.cc | 4 | ||||
-rw-r--r-- | src/clean_test.cc | 30 | ||||
-rw-r--r-- | src/depfile_parser.cc | 32 | ||||
-rw-r--r-- | src/depfile_parser.in.cc | 18 | ||||
-rw-r--r-- | src/depfile_parser_test.cc | 30 | ||||
-rw-r--r-- | src/disk_interface.cc | 5 | ||||
-rw-r--r-- | src/disk_interface_test.cc | 33 | ||||
-rw-r--r-- | src/graph.cc | 8 | ||||
-rw-r--r-- | src/ninja.cc | 39 | ||||
-rw-r--r-- | src/subprocess.cc | 61 | ||||
-rw-r--r-- | src/subprocess.h | 4 | ||||
-rw-r--r-- | src/subprocess_test.cc | 30 | ||||
-rw-r--r-- | src/util.cc | 8 |
23 files changed, 455 insertions, 99 deletions
diff --git a/bootstrap.py b/bootstrap.py index ca47b19..1df423d 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from optparse import OptionParser import sys import os import glob @@ -20,6 +21,11 @@ import errno import shlex import subprocess +parser = OptionParser() +parser.add_option('--verbose', action='store_true', + help='enable verbose build',) +(options, conf_args) = parser.parse_args() + def run(*args, **kwargs): try: subprocess.check_call(*args, **kwargs) @@ -65,7 +71,7 @@ if sys.platform.startswith('win32'): vcdir = os.environ.get('VCINSTALLDIR') if vcdir: - args = [os.path.join(vcdir, 'bin', 'cl.exe'), '/nologo', '/EHsc'] + args = [os.path.join(vcdir, 'bin', 'cl.exe'), '/nologo', '/EHsc', '/DNOMINMAX'] else: args = shlex.split(os.environ.get('CXX', 'g++')) args.extend(['-Wno-deprecated', @@ -81,11 +87,19 @@ if vcdir: args.extend(['/link', '/out:' + binary]) else: args.extend(['-o', binary]) + +if options.verbose: + print ' '.join(args) + run(args) +verbose = [] +if options.verbose: + verbose = ['-v'] + print 'Building ninja using itself...' -run([sys.executable, 'configure.py'] + sys.argv[1:]) -run(['./' + binary]) +run([sys.executable, 'configure.py'] + conf_args) +run(['./' + binary] + verbose) os.unlink(binary) print 'Done!' diff --git a/configure.py b/configure.py index ca8ba74..b5c986d 100755 --- a/configure.py +++ b/configure.py @@ -104,7 +104,7 @@ else: if platform == 'windows': cflags = ['/nologo', '/Zi', '/W4', '/WX', '/wd4530', '/wd4100', '/wd4706', '/wd4512', '/wd4800', '/wd4702', '/wd4819', - '/D_CRT_SECURE_NO_WARNINGS', + '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS', "/DNINJA_PYTHON=\"%s\"" % (options.with_python,)] ldflags = ['/DEBUG', '/libpath:$builddir'] if not options.debug: @@ -114,6 +114,7 @@ else: cflags = ['-g', '-Wall', '-Wextra', '-Wno-deprecated', '-Wno-unused-parameter', + '-fno-rtti', '-fno-exceptions', '-fvisibility=hidden', '-pipe', "'-DNINJA_PYTHON=\"%s\"'" % (options.with_python,)] @@ -153,7 +154,7 @@ if platform == 'windows': description='CXX $out') else: n.rule('cxx', - command='$cxx -MMD -MF $out.d $cflags -c $in -o $out', + command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', depfile='$out.d', description='CXX $out') n.newline() @@ -258,11 +259,14 @@ if options.with_gtest: path = options.with_gtest gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include')) - gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs - objs += n.build(built('gtest-all.o'), 'cxx', + if platform == 'windows': + gtest_cflags = '/nologo /EHsc ' + gtest_all_incs + else: + gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs + objs += n.build(built('gtest-all' + objext), 'cxx', os.path.join(path, 'src/gtest-all.cc'), variables=[('cflags', gtest_cflags)]) - objs += n.build(built('gtest_main.o'), 'cxx', + objs += n.build(built('gtest_main' + objext), 'cxx', os.path.join(path, 'src/gtest_main.cc'), variables=[('cflags', gtest_cflags)]) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 0387c97..a330095 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -492,19 +492,19 @@ aborting due to a missing input. rebuilt if the command line changes; and secondly, they are not cleaned by default. -`restat`:: if present, causes Ninja to re-stat the command's outputs after - execution of the command. Each output whose modification time the command - did not change will be treated as though it had never needed to be built. - This may cause the output's reverse dependencies to be removed from the - list of pending build actions. - -`rspfile` -`rspfile_content`:: if present (both), Ninja will use a response file - for the given command, i.e. write the selected string (`rspfile_content`) - to the given file (`rspfile`) before calling the command and delete - the file after successful execution of the command. +`restat`:: if present, causes Ninja to re-stat the command's outputs + after execution of the command. Each output whose modification time + the command did not change will be treated as though it had never + needed to be built. This may cause the output's reverse + dependencies to be removed from the list of pending build actions. + +`rspfile`, `rspfile_content`:: if present (both), Ninja will use a + response file for the given command, i.e. write the selected string + (`rspfile_content`) to the given file (`rspfile`) before calling the + command and delete the file after successful execution of the + command. + -This is particularly useful on Windows OS, where the maximal length of +This is particularly useful on Windows OS, where the maximal length of a command line is limited and response files must be used instead. + Use it like in the following example: @@ -514,13 +514,13 @@ rule link command = link.exe /OUT$out [usual link flags here] @$out.rsp rspfile = $out.rsp rspfile_content = $in - + build myapp.exe: link a.obj b.obj [possibly many other .obj files] ---- -Additionally, the special `$in` and `$out` variables expand to the -space-separated list of files provided to the `build` line referencing -this `rule`. +Finally, the special `$in` and `$out` variables expand to the +shell-quoted space-separated list of files provided to the `build` +line referencing this `rule`. Build dependencies ~~~~~~~~~~~~~~~~~~ diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index ccb38a8..97bd82b 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -33,7 +33,7 @@ class Writer(object): self._line('%s = %s' % (key, value), indent) def rule(self, name, command, description=None, depfile=None, - generator=False, restat=False): + generator=False, restat=False, rspfile=None, rspfile_content=None): self._line('rule %s' % name) self.variable('command', command, indent=1) if description: @@ -44,6 +44,10 @@ class Writer(object): self.variable('generator', '1', indent=1) if restat: self.variable('restat', '1', indent=1) + if rspfile: + self.variable('rspfile', rspfile, indent=1) + if rspfile_content: + self.variable('rspfile_content', rspfile_content, indent=1) def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): diff --git a/src/build.cc b/src/build.cc index d3e88ee..a2e4f64 100644 --- a/src/build.cc +++ b/src/build.cc @@ -20,6 +20,7 @@ #ifdef _WIN32 #include <windows.h> #else +#include <unistd.h> #include <sys/ioctl.h> #include <sys/time.h> #include <sys/termios.h> @@ -61,6 +62,10 @@ struct BuildStatus { /// Whether we can do fancy terminal control codes. bool smart_terminal_; + +#ifdef _WIN32 + HANDLE console_; +#endif }; BuildStatus::BuildStatus(const BuildConfig& config) @@ -73,12 +78,14 @@ BuildStatus::BuildStatus(const BuildConfig& config) const char* term = getenv("TERM"); smart_terminal_ = isatty(1) && term && string(term) != "dumb"; #else - smart_terminal_ = false; // Disable output buffer. It'd be nice to use line buffering but // MSDN says: "For some systems, [_IOLBF] provides line // buffering. However, for Win32, the behavior is the same as _IOFBF // - Full Buffering." setvbuf(stdout, NULL, _IONBF, 0); + console_ = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi); #endif // Don't do anything fancy in verbose mode. @@ -174,13 +181,24 @@ void BuildStatus::PrintStatus(Edge* edge) { if (to_print.empty() || force_full_command) to_print = edge->EvaluateCommand(); - if (smart_terminal_) +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(console_, &csbi); +#endif + + if (smart_terminal_) { +#ifndef _WIN32 printf("\r"); // Print over previous line, if any. +#else + csbi.dwCursorPosition.X = 0; + SetConsoleCursorPosition(console_, csbi.dwCursorPosition); +#endif + } int progress_chars = printf("[%d/%d] ", started_edges_, total_edges_); -#ifndef _WIN32 if (smart_terminal_ && !force_full_command) { +#ifndef _WIN32 // Limit output to width of the terminal if provided so we don't cause // line-wrapping. winsize size; @@ -193,17 +211,33 @@ void BuildStatus::PrintStatus(Edge* edge) { + to_print.substr(to_print.size() - elide_size, elide_size); } } - } #else - NINJA_UNUSED_ARG(progress_chars); + const int kMargin = progress_chars + 3; // Space for [xx/yy] and "...". + // Don't use the full width or console with move to next line. + size_t width = static_cast<size_t>(csbi.dwSize.X) - 1; + if (to_print.size() + kMargin > width) { + int elide_size = (width - kMargin) / 2; + to_print = to_print.substr(0, elide_size) + + "..." + + to_print.substr(to_print.size() - elide_size, elide_size); + } #endif + } printf("%s", to_print.c_str()); if (smart_terminal_ && !force_full_command) { +#ifndef _WIN32 printf("\x1B[K"); // Clear to end of line. fflush(stdout); have_blank_line_ = false; +#else + // Clear to end of line. + GetConsoleScreenBufferInfo(console_, &csbi); + int num_spaces = csbi.dwSize.X - 1 - csbi.dwCursorPosition.X; + printf("%*s", num_spaces, ""); + have_blank_line_ = false; +#endif } else { printf("\n"); } @@ -488,16 +522,31 @@ Builder::Builder(State* state, const BuildConfig& config) } Builder::~Builder() { + Cleanup(); +} + +void Builder::Cleanup() { if (command_runner_.get()) { vector<Edge*> active_edges = command_runner_->GetActiveEdges(); command_runner_->Abort(); for (vector<Edge*>::iterator i = active_edges.begin(); i != active_edges.end(); ++i) { + bool has_depfile = !(*i)->rule_->depfile().empty(); for (vector<Node*>::iterator ni = (*i)->outputs_.begin(); - ni != (*i)->outputs_.end(); ++ni) - disk_interface_->RemoveFile((*ni)->path()); - if (!(*i)->rule_->depfile().empty()) + ni != (*i)->outputs_.end(); ++ni) { + // Only delete this output if it was actually modified. This is + // important for things like the generator where we don't want to + // delete the manifest file if we can avoid it. But if the rule + // uses a depfile, always delete. (Consider the case where we + // need to rebuild an output because of a modified header file + // mentioned in a depfile, and the command touches its depfile + // but is interrupted before it touches its output file.) + if (has_depfile || + (*ni)->mtime() != disk_interface_->Stat((*ni)->path())) + disk_interface_->RemoveFile((*ni)->path()); + } + if (has_depfile) disk_interface_->RemoveFile((*i)->EvaluateDepFile()); } } diff --git a/src/build.h b/src/build.h index 778d59d..179fca6 100644 --- a/src/build.h +++ b/src/build.h @@ -116,6 +116,9 @@ struct Builder { Builder(State* state, const BuildConfig& config); ~Builder(); + /// Clean up after interrupted commands by deleting output files. + void Cleanup(); + Node* AddTarget(const string& name, string* err); /// Add a target to the build, scanning dependencies. diff --git a/src/build_log.cc b/src/build_log.cc index 4b93931..0cecd70 100644 --- a/src/build_log.cc +++ b/src/build_log.cc @@ -17,6 +17,7 @@ #include <errno.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include "build.h" #include "graph.h" @@ -221,6 +222,7 @@ bool BuildLog::Recompact(const string& path, string* err) { if (fprintf(f, kFileSignature, kCurrentVersion) < 0) { *err = strerror(errno); + fclose(f); return false; } diff --git a/src/build_log.h b/src/build_log.h index 040609d..da8e726 100644 --- a/src/build_log.h +++ b/src/build_log.h @@ -71,10 +71,11 @@ struct BuildLog { /// Rewrite the known log entries, throwing away old data. bool Recompact(const string& path, string* err); - // TODO: make these private. typedef ExternalStringHashMap<LogEntry*>::Type Log; + const Log& log() const { return log_; } + + private: Log log_; -private: FILE* log_file_; BuildConfig* config_; bool needs_recompaction_; diff --git a/src/build_log_test.cc b/src/build_log_test.cc index c6d6bc3..9b729c7 100644 --- a/src/build_log_test.cc +++ b/src/build_log_test.cc @@ -54,8 +54,8 @@ TEST_F(BuildLogTest, WriteRead) { EXPECT_TRUE(log2.Load(kTestFilename, &err)); ASSERT_EQ("", err); - ASSERT_EQ(2u, log1.log_.size()); - ASSERT_EQ(2u, log2.log_.size()); + ASSERT_EQ(2u, log1.log().size()); + ASSERT_EQ(2u, log2.log().size()); BuildLog::LogEntry* e1 = log1.LookupByOutput("out"); ASSERT_TRUE(e1); BuildLog::LogEntry* e2 = log2.LookupByOutput("out"); diff --git a/src/build_test.cc b/src/build_test.cc index 78bc918..c015bc9 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -202,6 +202,8 @@ struct BuildTest : public StateTestWithBuiltinRules, virtual bool CanRunMore(); virtual bool StartCommand(Edge* edge); virtual Edge* WaitForCommand(ExitStatus* status, string* output); + virtual vector<Edge*> GetActiveEdges(); + virtual void Abort(); BuildConfig MakeConfig() { BuildConfig config; @@ -240,13 +242,15 @@ bool BuildTest::StartCommand(Edge* edge) { if (edge->rule().name() == "cat" || edge->rule().name() == "cat_rsp" || edge->rule().name() == "cc" || - edge->rule().name() == "touch") { + edge->rule().name() == "touch" || + edge->rule().name() == "touch-interrupt") { for (vector<Node*>::iterator out = edge->outputs_.begin(); out != edge->outputs_.end(); ++out) { fs_.Create((*out)->path(), now_, ""); } } else if (edge->rule().name() == "true" || - edge->rule().name() == "fail") { + edge->rule().name() == "fail" || + edge->rule().name() == "interrupt") { // Don't do anything. } else { printf("unknown command\n"); @@ -259,6 +263,12 @@ bool BuildTest::StartCommand(Edge* edge) { Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { if (Edge* edge = last_command_) { + if (edge->rule().name() == "interrupt" || + edge->rule().name() == "touch-interrupt") { + *status = ExitInterrupted; + return NULL; + } + if (edge->rule().name() == "fail") *status = ExitFailure; else @@ -270,6 +280,17 @@ Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { return NULL; } +vector<Edge*> BuildTest::GetActiveEdges() { + vector<Edge*> edges; + if (last_command_) + edges.push_back(last_command_); + return edges; +} + +void BuildTest::Abort() { + last_command_ = NULL; +} + TEST_F(BuildTest, NoWork) { string err; EXPECT_TRUE(builder_.AlreadyUpToDate()); @@ -847,15 +868,15 @@ TEST_F(BuildTest, RspFileSuccess) size_t files_removed = fs_.files_removed_.size(); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2, commands_ran_.size()); // cat + cat_rsp + ASSERT_EQ(2u, commands_ran_.size()); // cat + cat_rsp // The RSP file was created ASSERT_EQ(files_created + 1, fs_.files_created_.size()); - ASSERT_EQ(1, fs_.files_created_.count("out2.rsp")); + ASSERT_EQ(1u, fs_.files_created_.count("out2.rsp")); // The RSP file was removed ASSERT_EQ(files_removed + 1, fs_.files_removed_.size()); - ASSERT_EQ(1, fs_.files_removed_.count("out2.rsp")); + ASSERT_EQ(1u, fs_.files_removed_.count("out2.rsp")); } // Test that RSP file is created but not removed for commands, which fail @@ -882,15 +903,15 @@ TEST_F(BuildTest, RspFileFailure) { EXPECT_FALSE(builder_.Build(&err)); ASSERT_EQ("subcommand failed", err); - ASSERT_EQ(1, commands_ran_.size()); + ASSERT_EQ(1u, commands_ran_.size()); // The RSP file was created ASSERT_EQ(files_created + 1, fs_.files_created_.size()); - ASSERT_EQ(1, fs_.files_created_.count("out.rsp")); + ASSERT_EQ(1u, fs_.files_created_.count("out.rsp")); // The RSP file was NOT removed ASSERT_EQ(files_removed, fs_.files_removed_.size()); - ASSERT_EQ(0, fs_.files_removed_.count("out.rsp")); + ASSERT_EQ(0u, fs_.files_removed_.count("out.rsp")); // The RSP file contains what it should ASSERT_EQ("Another very long command", fs_.files_["out.rsp"].contents); @@ -918,7 +939,7 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { // 1. Build for the 1st time (-> populate log) EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(1, commands_ran_.size()); + ASSERT_EQ(1u, commands_ran_.size()); // 2. Build again (no change) commands_ran_.clear(); @@ -939,5 +960,65 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { EXPECT_TRUE(builder_.AddTarget("out", &err)); EXPECT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - EXPECT_EQ(1, commands_ran_.size()); + EXPECT_EQ(1u, commands_ran_.size()); +} + +TEST_F(BuildTest, InterruptCleanup) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule interrupt\n" +" command = interrupt\n" +"rule touch-interrupt\n" +" command = touch-interrupt\n" +"build out1: interrupt in1\n" +"build out2: touch-interrupt in2\n")); + + fs_.Create("out1", now_, ""); + fs_.Create("out2", now_, ""); + now_++; + fs_.Create("in1", now_, ""); + fs_.Create("in2", now_, ""); + + // An untouched output of an interrupted command should be retained. + string err; + EXPECT_TRUE(builder_.AddTarget("out1", &err)); + EXPECT_EQ("", err); + EXPECT_FALSE(builder_.Build(&err)); + EXPECT_EQ("interrupted by user", err); + builder_.Cleanup(); + EXPECT_EQ(now_-1, fs_.Stat("out1")); + err = ""; + + // A touched output of an interrupted command should be deleted. + EXPECT_TRUE(builder_.AddTarget("out2", &err)); + EXPECT_EQ("", err); + EXPECT_FALSE(builder_.Build(&err)); + EXPECT_EQ("interrupted by user", err); + builder_.Cleanup(); + EXPECT_EQ(0, fs_.Stat("out2")); +} + +TEST_F(BuildTest, PhonyWithNoInputs) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build nonexistent: phony\n" +"build out1: cat || nonexistent\n" +"build out2: cat nonexistent\n")); + fs_.Create("out1", now_, ""); + fs_.Create("out2", now_, ""); + + // out1 should be up to date even though its input is dirty, because its + // order-only dependency has nothing to do. + string err; + EXPECT_TRUE(builder_.AddTarget("out1", &err)); + ASSERT_EQ("", err); + EXPECT_TRUE(builder_.AlreadyUpToDate()); + + // out2 should still be out of date though, because its input is dirty. + err.clear(); + commands_ran_.clear(); + state_.Reset(); + EXPECT_TRUE(builder_.AddTarget("out2", &err)); + ASSERT_EQ("", err); + EXPECT_TRUE(builder_.Build(&err)); + EXPECT_EQ("", err); + ASSERT_EQ(1u, commands_ran_.size()); } diff --git a/src/clean.cc b/src/clean.cc index 2565c28..3fe23ec 100644 --- a/src/clean.cc +++ b/src/clean.cc @@ -127,6 +127,8 @@ void Cleaner::DoCleanTarget(Node* target) { // Do not try to remove phony targets if (!e->is_phony()) { Remove(target->path()); + if (!target->in_edge()->rule().depfile().empty()) + Remove(target->in_edge()->EvaluateDepFile()); if (e->HasRspFile()) Remove(e->GetRspFile()); } @@ -189,6 +191,8 @@ void Cleaner::DoCleanRule(const Rule* rule) { for (vector<Node*>::iterator out_node = (*e)->outputs_.begin(); out_node != (*e)->outputs_.end(); ++out_node) { Remove((*out_node)->path()); + if (!(*e)->rule().depfile().empty()) + Remove((*e)->EvaluateDepFile()); if ((*e)->HasRspFile()) Remove((*e)->GetRspFile()); } diff --git a/src/clean_test.cc b/src/clean_test.cc index a93a3b4..5ed48da 100644 --- a/src/clean_test.cc +++ b/src/clean_test.cc @@ -249,6 +249,36 @@ TEST_F(CleanTest, CleanDepFile) { EXPECT_EQ(2u, fs_.files_removed_.size()); } +TEST_F(CleanTest, CleanDepFileOnCleanTarget) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cc\n" +" command = cc $in > $out\n" +" depfile = $out.d\n" +"build out1: cc in1\n")); + fs_.Create("out1", 1, ""); + fs_.Create("out1.d", 1, ""); + + Cleaner cleaner(&state_, config_, &fs_); + EXPECT_EQ(0, cleaner.CleanTarget("out1")); + EXPECT_EQ(2, cleaner.cleaned_files_count()); + EXPECT_EQ(2u, fs_.files_removed_.size()); +} + +TEST_F(CleanTest, CleanDepFileOnCleanRule) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cc\n" +" command = cc $in > $out\n" +" depfile = $out.d\n" +"build out1: cc in1\n")); + fs_.Create("out1", 1, ""); + fs_.Create("out1.d", 1, ""); + + Cleaner cleaner(&state_, config_, &fs_); + EXPECT_EQ(0, cleaner.CleanRule("cc")); + EXPECT_EQ(2, cleaner.cleaned_files_count()); + EXPECT_EQ(2u, fs_.files_removed_.size()); +} + TEST_F(CleanTest, CleanRspFile) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc index 8f8f9fe..261893f 100644 --- a/src/depfile_parser.cc +++ b/src/depfile_parser.cc @@ -32,8 +32,10 @@ bool DepfileParser::Parse(string* content, string* err) { // in: current parser input point. // end: end of input. + // parsing_targets: whether we are parsing targets or dependencies. char* in = &(*content)[0]; char* end = in + content->size(); + bool parsing_targets = true; while (in < end) { // out: current output point (typically same as in, but can fall behind // as we de-escape backslashes). @@ -62,7 +64,7 @@ bool DepfileParser::Parse(string* content, string* err) { 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 0, 0, 0, 0, 0, + 128, 128, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -82,7 +84,7 @@ bool DepfileParser::Parse(string* content, string* err) { }; yych = *in; - if (yych <= '[') { + if (yych <= '\\') { if (yych <= ':') { if (yych <= 0x00) goto yy6; if (yych <= '*') goto yy8; @@ -90,20 +92,18 @@ bool DepfileParser::Parse(string* content, string* err) { } else { if (yych <= '@') goto yy8; if (yych <= 'Z') goto yy4; - goto yy8; + if (yych <= '[') goto yy8; } } else { - if (yych <= '_') { - if (yych <= '\\') goto yy2; - if (yych <= '^') goto yy8; - goto yy4; + if (yych <= '`') { + if (yych == '_') goto yy4; + goto yy8; } else { - if (yych <= '`') goto yy8; if (yych <= 'z') goto yy4; + if (yych == '~') goto yy4; goto yy8; } } -yy2: ++in; if ((yych = *in) <= '$') { if (yych <= '\n') { @@ -180,16 +180,22 @@ yy13: } int len = out - filename; - if (len > 0 && filename[len - 1] == ':') + const bool is_target = parsing_targets; + if (len > 0 && filename[len - 1] == ':') { len--; // Strip off trailing colon, if any. + parsing_targets = false; + } if (len == 0) continue; - if (!out_.str_) { - out_ = StringPiece(filename, len); - } else { + if (!is_target) { ins_.push_back(StringPiece(filename, len)); + } else if (!out_.str_) { + out_ = StringPiece(filename, len); + } else if (out_ != StringPiece(filename, len)) { + *err = "depfile has multiple output paths."; + return false; } } return true; diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc index de1c38f..5e073df 100644 --- a/src/depfile_parser.in.cc +++ b/src/depfile_parser.in.cc @@ -31,8 +31,10 @@ bool DepfileParser::Parse(string* content, string* err) { // in: current parser input point. // end: end of input. + // parsing_targets: whether we are parsing targets or dependencies. char* in = &(*content)[0]; char* end = in + content->size(); + bool parsing_targets = true; while (in < end) { // out: current output point (typically same as in, but can fall behind // as we de-escape backslashes). @@ -66,7 +68,7 @@ bool DepfileParser::Parse(string* content, string* err) { *out++ = yych; continue; } - [a-zA-Z0-9+,/_:.-]+ { + [a-zA-Z0-9+,/_:.~-]+ { // Got a span of plain text. int len = in - start; // Need to shift it over if we're overwriting backslashes. @@ -87,16 +89,22 @@ bool DepfileParser::Parse(string* content, string* err) { } int len = out - filename; - if (len > 0 && filename[len - 1] == ':') + const bool is_target = parsing_targets; + if (len > 0 && filename[len - 1] == ':') { len--; // Strip off trailing colon, if any. + parsing_targets = false; + } if (len == 0) continue; - if (!out_.str_) { - out_ = StringPiece(filename, len); - } else { + if (!is_target) { ins_.push_back(StringPiece(filename, len)); + } else if (!out_.str_) { + out_ = StringPiece(filename, len); + } else if (out_ != StringPiece(filename, len)) { + *err = "depfile has multiple output paths."; + return false; } } return true; diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc index 43e677c..9094283 100644 --- a/src/depfile_parser_test.cc +++ b/src/depfile_parser_test.cc @@ -102,3 +102,33 @@ TEST_F(DepfileParserTest, Escapes) { parser_.out_.AsString()); ASSERT_EQ(0u, parser_.ins_.size()); } + +TEST_F(DepfileParserTest, UnifyMultupleOutputs) { + // check that multiple duplicate targets are properly unified + string err; + EXPECT_TRUE(Parse("foo foo: x y z", &err)); + ASSERT_EQ(parser_.out_.AsString(), "foo"); + ASSERT_EQ(parser_.ins_.size(), 3u); + EXPECT_EQ("x", parser_.ins_[0].AsString()); + EXPECT_EQ("y", parser_.ins_[1].AsString()); + EXPECT_EQ("z", parser_.ins_[2].AsString()); +} + +TEST_F(DepfileParserTest, RejectMultipleDifferentOutputs) { + // check that multiple different outputs are rejected by the parser + string err; + EXPECT_FALSE(Parse("foo bar: x y z", &err)); +} + +TEST_F(DepfileParserTest, Tilde) { + string err; + EXPECT_TRUE(Parse( +"foo~.o: foo~.c", + &err)); + ASSERT_EQ("", err); + EXPECT_EQ("foo~.o", + parser_.out_.AsString()); + ASSERT_EQ(1u, parser_.ins_.size()); + EXPECT_EQ("foo~.c", + parser_.ins_[0].AsString()); +} diff --git a/src/disk_interface.cc b/src/disk_interface.cc index 96a1e59..5a36685 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -94,7 +94,7 @@ TimeStamp RealDiskInterface::Stat(const string& path) { #else struct stat st; if (stat(path.c_str(), &st) < 0) { - if (errno == ENOENT) + if (errno == ENOENT || errno == ENOTDIR) return 0; Error("stat(%s): %s", path.c_str(), strerror(errno)); return -1; @@ -109,9 +109,10 @@ bool RealDiskInterface::WriteFile(const string & path, const string & contents) Error("WriteFile(%s): Unable to create file. %s", path.c_str(), strerror(errno)); return false; } - + if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) { Error("WriteFile(%s): Unable to write to the file. %s", path.c_str(), strerror(errno)); + fclose(fp); return false; } diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index 052a94b..9e460c5 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -36,6 +36,13 @@ class DiskInterfaceTest : public testing::Test { temp_dir_.Cleanup(); } + bool Touch(const char* path) { + FILE *f = fopen(path, "w"); + if (!f) + return false; + return fclose(f) == 0; + } + ScopedTempDir temp_dir_; RealDiskInterface disk_; }; @@ -46,21 +53,25 @@ TEST_F(DiskInterfaceTest, StatMissingFile) { // On Windows, the errno for a file in a nonexistent directory // is different. EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile")); + + // On POSIX systems, the errno is different if a component of the + // path prefix is not a directory. + ASSERT_TRUE(Touch("notadir")); + EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile")); } TEST_F(DiskInterfaceTest, StatBadPath) { - // To test the error code path, use an overlong file name. - // Both Windows and Linux appear to object to this. +#ifdef _WIN32 + string bad_path("cc:\\foo"); + EXPECT_EQ(-1, disk_.Stat(bad_path)); +#else string too_long_name(512, 'x'); EXPECT_EQ(-1, disk_.Stat(too_long_name)); +#endif } TEST_F(DiskInterfaceTest, StatExistingFile) { -#ifdef _WIN32 - ASSERT_EQ(0, system("cmd.exe /c echo hi > file")); -#else - ASSERT_EQ(0, system("touch file")); -#endif + ASSERT_TRUE(Touch("file")); EXPECT_GT(disk_.Stat("file"), 1); } @@ -86,13 +97,7 @@ TEST_F(DiskInterfaceTest, MakeDirs) { TEST_F(DiskInterfaceTest, RemoveFile) { const char* kFileName = "file-to-remove"; -#ifdef _WIN32 - string cmd = "cmd /c echo hi > "; -#else - string cmd = "touch "; -#endif - cmd += kFileName; - ASSERT_EQ(0, system(cmd.c_str())); + ASSERT_TRUE(Touch(kFileName)); EXPECT_EQ(0, disk_.RemoveFile(kFileName)); EXPECT_EQ(1, disk_.RemoveFile(kFileName)); EXPECT_EQ(1, disk_.RemoveFile("does not exist")); diff --git a/src/graph.cc b/src/graph.cc index e2f966c..9d45ce1 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -96,10 +96,10 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface, (*i)->MarkDirty(); } - // If we're dirty, our outputs are not ready. (It's possible to be - // clean but still have not be ready in the presence of order-only - // inputs.) - if (dirty) + // If we're dirty, our outputs are normally not ready. (It's possible to be + // clean but still not be ready in the presence of order-only inputs.) + // But phony edges with no inputs have nothing to do, so are always ready. + if (dirty && !(is_phony() && inputs_.empty())) outputs_ready_ = false; return true; diff --git a/src/ninja.cc b/src/ninja.cc index 7f07053..7d020db 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -31,6 +31,7 @@ #include <windows.h> #else #include <getopt.h> +#include <unistd.h> #endif #include "browse.h" @@ -195,9 +196,13 @@ bool CollectTargetsFromArgs(State* state, int argc, char* argv[], } else { *err = "unknown target '" + path + "'"; - Node* suggestion = state->SpellcheckNode(path); - if (suggestion) { - *err += ", did you mean '" + suggestion->path() + "'?"; + if (path == "clean") { + *err += ", did you mean 'ninja -t clean'?"; + } else { + Node* suggestion = state->SpellcheckNode(path); + if (suggestion) { + *err += ", did you mean '" + suggestion->path() + "'?"; + } } return false; } @@ -478,6 +483,31 @@ int ToolClean(Globals* globals, int argc, char* argv[]) { } } +void ToolUrtle() { + // RLE encoded. + const char* urtle = +" 13 ,3;2!2;\n8 ,;<11!;\n5 `'<10!(2`'2!\n11 ,6;, `\\. `\\9 .,c13$ec,.\n6 " +",2;11!>; `. ,;!2> .e8$2\".2 \"?7$e.\n <:<8!'` 2.3,.2` ,3!' ;,(?7\";2!2'<" +"; `?6$PF ,;,\n2 `'4!8;<!3'`2 3! ;,`'2`2'3!;4!`2.`!;2 3,2 .<!2'`).\n5 3`5" +"'2`9 `!2 `4!><3;5! J2$b,`!>;2!:2!`,d?b`!>\n26 `'-;,(<9!> $F3 )3.:!.2 d\"" +"2 ) !>\n30 7`2'<3!- \"=-='5 .2 `2-=\",!>\n25 .ze9$er2 .,cd16$bc.'\n22 .e" +"14$,26$.\n21 z45$c .\n20 J50$c\n20 14$P\"`?34$b\n20 14$ dbc `2\"?22$?7$c" +"\n20 ?18$c.6 4\"8?4\" c8$P\n9 .2,.8 \"20$c.3 ._14 J9$\n .2,2c9$bec,.2 `?" +"21$c.3`4%,3%,3 c8$P\"\n22$c2 2\"?21$bc2,.2` .2,c7$P2\",cb\n23$b bc,.2\"2" +"?14$2F2\"5?2\",J5$P\" ,zd3$\n24$ ?$3?%3 `2\"2?12$bcucd3$P3\"2 2=7$\n23$P" +"\" ,3;<5!>2;,. `4\"6?2\"2 ,9;, `\"?2$\n"; + int count = 0; + for (const char* p = urtle; *p; p++) { + if ('0' <= *p && *p <= '9') { + count = count*10 + *p - '0'; + } else { + for (int i = 0; i < std::max(count, 1); ++i) + printf("%c", *p); + count = 0; + } + } +} + int RunTool(const string& tool, Globals* globals, int argc, char** argv) { typedef int (*ToolFunc)(Globals*, int, char**); struct Tool { @@ -510,6 +540,9 @@ int RunTool(const string& tool, Globals* globals, int argc, char** argv) { printf("%10s %s\n", tools[i].name, tools[i].desc); } return 0; + } else if (tool == "urtle") { + ToolUrtle(); + return 0; } for (int i = 0; tools[i].name; ++i) { diff --git a/src/subprocess.cc b/src/subprocess.cc index d4a7d03..25b1bda 100644 --- a/src/subprocess.cc +++ b/src/subprocess.cc @@ -1,4 +1,4 @@ -// Copyright 2011 Google Inc. All Rights Reserved. +// Copyright 2012 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. @@ -42,10 +42,12 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { if (pipe(output_pipe) < 0) Fatal("pipe: %s", strerror(errno)); fd_ = output_pipe[0]; - // fd_ may be a member of the pselect set in SubprocessSet::DoWork. Check - // that it falls below the system limit. - if (fd_ >= FD_SETSIZE) +#if !defined(linux) + // On linux 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 SetCloseOnExec(fd_); pid_ = fork(); @@ -180,6 +182,54 @@ Subprocess *SubprocessSet::Add(const string &command) { return subprocess; } +#ifdef linux +bool SubprocessSet::DoWork() { + vector<pollfd> fds; + nfds_t nfds = 0; + + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ++i) { + int fd = (*i)->fd_; + if (fd < 0) + continue; + pollfd pfd = { fd, POLLIN | POLLPRI | POLLRDHUP, 0 }; + fds.push_back(pfd); + ++nfds; + } + + int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_); + if (ret == -1) { + if (errno != EINTR) { + perror("ninja: ppoll"); + return false; + } + bool interrupted = interrupted_; + interrupted_ = false; + return interrupted; + } + + nfds_t cur_nfd = 0; + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ) { + int fd = (*i)->fd_; + if (fd < 0) + continue; + assert(fd == fds[cur_nfd].fd); + if (fds[cur_nfd++].revents) { + (*i)->OnPipeReady(); + if ((*i)->Done()) { + finished_.push(*i); + i = running_.erase(i); + continue; + } + } + ++i; + } + + return false; +} + +#else // linux bool SubprocessSet::DoWork() { fd_set set; int nfds = 0; @@ -213,7 +263,7 @@ bool SubprocessSet::DoWork() { (*i)->OnPipeReady(); if ((*i)->Done()) { finished_.push(*i); - running_.erase(i); + i = running_.erase(i); continue; } } @@ -222,6 +272,7 @@ bool SubprocessSet::DoWork() { return false; } +#endif // linux Subprocess* SubprocessSet::NextFinished() { if (finished_.empty()) diff --git a/src/subprocess.h b/src/subprocess.h index 8e0d7f1..3294416 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -1,4 +1,4 @@ -// Copyright 2011 Google Inc. All Rights Reserved. +// Copyright 2012 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. @@ -68,7 +68,7 @@ struct Subprocess { friend struct SubprocessSet; }; -/// SubprocessSet runs a pselect() loop around a set of Subprocesses. +/// SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses. /// DoWork() waits for any state change in subprocesses; finished_ /// is a queue of subprocesses as they finish. struct SubprocessSet { diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 5b3e8a3..c155012 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -1,4 +1,4 @@ -// Copyright 2011 Google Inc. All Rights Reserved. +// Copyright 2012 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. @@ -142,3 +142,31 @@ TEST_F(SubprocessTest, SetWithMulti) { } } +#ifdef linux +TEST_F(SubprocessTest, SetWithLots) { + // Arbitrary big number; needs to be over 1024 to confirm we're no longer + // hostage to pselect. + const size_t kNumProcs = 1025; + + // Make sure [ulimit -n] isn't going to stop us from working. + rlimit rlim; + ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim)); + ASSERT_GT(rlim.rlim_cur, kNumProcs) + << "Raise [ulimit -n] well above " << kNumProcs + << " to make this test go"; + + vector<Subprocess*> procs; + for (size_t i = 0; i < kNumProcs; ++i) { + Subprocess* subproc = subprocs_.Add("/bin/echo"); + ASSERT_NE((Subprocess *) 0, subproc); + procs.push_back(subproc); + } + while (!subprocs_.running_.empty()) + subprocs_.DoWork(); + for (size_t i = 0; i < procs.size(); ++i) { + ASSERT_EQ(ExitSuccess, procs[i]->Finish()); + ASSERT_NE("", procs[i]->GetOutput()); + } + ASSERT_EQ(kNumProcs, subprocs_.finished_.size()); +} +#endif // linux diff --git a/src/util.cc b/src/util.cc index 02be994..c88dc4e 100644 --- a/src/util.cc +++ b/src/util.cc @@ -16,6 +16,7 @@ #ifdef _WIN32 #include <windows.h> +#include <io.h> #endif #include <errno.h> @@ -197,9 +198,10 @@ void SetCloseOnExec(int fd) { perror("fcntl(F_SETFD)"); } #else - // On Windows, handles must be explicitly marked to be passed to a - // spawned process, so there's nothing to do here. - NINJA_UNUSED_ARG(fd); + HANDLE hd = (HANDLE) _get_osfhandle(fd); + if (! SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0)) { + fprintf(stderr, "SetHandleInformation(): %s", GetLastErrorString().c_str()); + } #endif // ! _WIN32 } |