summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbootstrap.py20
-rwxr-xr-xconfigure.py14
-rw-r--r--doc/manual.asciidoc32
-rw-r--r--misc/ninja_syntax.py6
-rw-r--r--src/build.cc65
-rw-r--r--src/build.h3
-rw-r--r--src/build_log.cc2
-rw-r--r--src/build_log.h5
-rw-r--r--src/build_log_test.cc4
-rw-r--r--src/build_test.cc101
-rw-r--r--src/clean.cc4
-rw-r--r--src/clean_test.cc30
-rw-r--r--src/depfile_parser.cc32
-rw-r--r--src/depfile_parser.in.cc18
-rw-r--r--src/depfile_parser_test.cc30
-rw-r--r--src/disk_interface.cc5
-rw-r--r--src/disk_interface_test.cc33
-rw-r--r--src/graph.cc8
-rw-r--r--src/ninja.cc39
-rw-r--r--src/subprocess.cc61
-rw-r--r--src/subprocess.h4
-rw-r--r--src/subprocess_test.cc30
-rw-r--r--src/util.cc8
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
}