From a87ffee6bb53ebdd68e29580d67cc4093100e82e Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 5 Apr 2013 10:07:48 -0700 Subject: decided against using XXX in the manual for unknown versions --- RELEASING | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/RELEASING b/RELEASING index 0593e6d..3f83c7b 100644 --- a/RELEASING +++ b/RELEASING @@ -3,8 +3,7 @@ Notes to myself on all the steps to make for a Ninja release. 1. git checkout release; git merge master 2. fix version number in source (it will likely conflict in the above) 3. fix version in doc/manual.asciidoc -4. grep doc/manual.asciidoc for XXX, fix version references -5. rebuild manual, put in place on website -6. commit, tag, push -7. construct release notes from prior notes +4. rebuild manual, put in place on website +5. commit, tag, push +6. construct release notes from prior notes credits: git shortlog -s --no-merges REV.. -- cgit v0.12 From 75381bcf071ed45af79c3515eb37f406065f9b58 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 12:59:23 -0800 Subject: add noreturn attr on Fatal() --- src/util.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/util.h b/src/util.h index 3c2a297..4c1c466 100644 --- a/src/util.h +++ b/src/util.h @@ -25,8 +25,14 @@ #include using namespace std; +#ifdef _MSC_VER +#define NORETURN __declspec(noreturn) +#else +#define NORETURN __attribute__((noreturn)); +#endif + /// Log a fatal message and exit. -void Fatal(const char* msg, ...); +void Fatal(const char* msg, ...) NORETURN; /// Log a warning message. void Warning(const char* msg, ...); @@ -85,7 +91,7 @@ string ElideMiddle(const string& str, size_t width); string GetLastErrorString(); /// Calls Fatal() with a function name and GetLastErrorString. -void Win32Fatal(const char* function); +void Win32Fatal(const char* function) NORETURN; #endif #endif // NINJA_UTIL_H_ -- cgit v0.12 From 372a7a8850a6451dec4f4d2c556878064f2662fb Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 15:02:31 -0800 Subject: refactor some of the output mtime-handling code Reduces duplicated explain output. --- src/graph.cc | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/graph.cc b/src/graph.cc index b000c48..0f99698 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -161,24 +161,25 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge, // Dirty if the output is older than the input. if (most_recent_input && output->mtime() < most_recent_input->mtime()) { + TimeStamp output_mtime = output->mtime(); + // If this is a restat rule, we may have cleaned the output with a restat // rule in a previous run and stored the most recent input mtime in the // build log. Use that mtime instead, so that the file will only be // considered dirty if an input was modified since the previous run. - TimeStamp most_recent_stamp = most_recent_input->mtime(); + bool used_restat = false; if (edge->GetBindingBool("restat") && build_log() && (entry = build_log()->LookupByOutput(output->path()))) { - if (entry->restat_mtime < most_recent_stamp) { - EXPLAIN("restat of output %s older than most recent input %s " - "(%d vs %d)", - output->path().c_str(), most_recent_input->path().c_str(), - entry->restat_mtime, most_recent_stamp); - return true; - } - } else { - EXPLAIN("output %s older than most recent input %s (%d vs %d)", - output->path().c_str(), most_recent_input->path().c_str(), - output->mtime(), most_recent_stamp); + output_mtime = entry->restat_mtime; + used_restat = true; + } + + if (output_mtime < most_recent_input->mtime()) { + EXPLAIN("%soutput %s older than most recent input %s " + "(%d vs %d)", + used_restat ? "restat of " : "", output->path().c_str(), + most_recent_input->path().c_str(), + output_mtime, most_recent_input->mtime()); return true; } } -- cgit v0.12 From ca04327e24793348cb84b7a8494718fe5a4881c9 Mon Sep 17 00:00:00 2001 From: Guilherme Bufolo Date: Sat, 6 Apr 2013 13:47:18 +0200 Subject: Improved bash completion when using tools like '-t clean' --- misc/bash-completion | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/misc/bash-completion b/misc/bash-completion index b40136e..2d6975b 100644 --- a/misc/bash-completion +++ b/misc/bash-completion @@ -18,16 +18,23 @@ _ninja_target() { local cur targets dir line targets_command OPTIND cur="${COMP_WORDS[COMP_CWORD]}" - dir="." - line=$(echo ${COMP_LINE} | cut -d" " -f 2-) - while getopts C: opt "${line[@]}"; do - case $opt in - C) dir="$OPTARG" ;; - esac - done; - targets_command="ninja -C ${dir} -t targets all" - targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}') - COMPREPLY=($(compgen -W "$targets" -- "$cur")) - return 0 + + if [[ "$cur" == "--"* ]]; then + # there is currently only one argument that takes -- + COMPREPLY=($(compgen -P '--' -W 'version' -- "${cur:2}")) + else + dir="." + line=$(echo ${COMP_LINE} | cut -d" " -f 2-) + # filter out all non relevant arguments but keep C for dirs + while getopts C:f:j:l:k:nvd:t: opt "${line[@]}"; do + case $opt in + C) dir="$OPTARG" ;; + esac + done; + targets_command="ninja -C ${dir} -t targets all" + targets=$((${targets_command} 2>/dev/null) | awk -F: '{print $1}') + COMPREPLY=($(compgen -W "$targets" -- "$cur")) + fi + return } complete -F _ninja_target ninja -- cgit v0.12 From bb258a5b72a0237a0439d92ad360f5bfbcd8095e Mon Sep 17 00:00:00 2001 From: wang-bin Date: Sun, 7 Apr 2013 11:14:16 +0800 Subject: fix compile error about noreturn on windows --- src/util.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util.h b/src/util.h index 4c1c466..07381a1 100644 --- a/src/util.h +++ b/src/util.h @@ -28,11 +28,11 @@ using namespace std; #ifdef _MSC_VER #define NORETURN __declspec(noreturn) #else -#define NORETURN __attribute__((noreturn)); +#define NORETURN __attribute__((noreturn)) #endif /// Log a fatal message and exit. -void Fatal(const char* msg, ...) NORETURN; +NORETURN void Fatal(const char* msg, ...); /// Log a warning message. void Warning(const char* msg, ...); @@ -91,7 +91,7 @@ string ElideMiddle(const string& str, size_t width); string GetLastErrorString(); /// Calls Fatal() with a function name and GetLastErrorString. -void Win32Fatal(const char* function) NORETURN; +NORETURN void Win32Fatal(const char* function); #endif #endif // NINJA_UTIL_H_ -- cgit v0.12 From 29e76bc16dc5e1b50e6afc8fe1614259bb53cbd1 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 8 Apr 2013 14:05:27 -0700 Subject: move test virtual time "now_" into VirtualFileSystem It's the only piece that cares about the current time. --- src/build_test.cc | 157 +++++++++++++++++++++++++++--------------------------- src/clean_test.cc | 92 ++++++++++++++++---------------- src/graph_test.cc | 52 +++++++++--------- src/test.cc | 6 +-- src/test.h | 15 +++++- 5 files changed, 168 insertions(+), 154 deletions(-) diff --git a/src/build_test.cc b/src/build_test.cc index 40a82ed..7eecddf 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -380,15 +380,15 @@ struct BuildTest : public StateTestWithBuiltinRules, public CommandRunner { BuildTest() : config_(MakeConfig()), builder_(&state_, config_, NULL, &fs_), - now_(1), last_command_(NULL), status_(config_) { + last_command_(NULL), status_(config_) { builder_.command_runner_.reset(this); AssertParse(&state_, "build cat1: cat in1\n" "build cat2: cat in1 in2\n" "build cat12: cat cat1 cat2\n"); - fs_.Create("in1", now_, ""); - fs_.Create("in2", now_, ""); + fs_.Create("in1", ""); + fs_.Create("in2", ""); } ~BuildTest() { @@ -414,7 +414,6 @@ struct BuildTest : public StateTestWithBuiltinRules, BuildConfig config_; VirtualFileSystem fs_; Builder builder_; - int now_; vector commands_ran_; Edge* last_command_; @@ -446,7 +445,7 @@ bool BuildTest::StartCommand(Edge* edge) { edge->rule().name() == "touch-interrupt") { for (vector::iterator out = edge->outputs_.begin(); out != edge->outputs_.end(); ++out) { - fs_.Create((*out)->path(), now_, ""); + fs_.Create((*out)->path(), ""); } } else if (edge->rule().name() == "true" || edge->rule().name() == "fail" || @@ -540,11 +539,11 @@ TEST_F(BuildTest, TwoStep) { EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[2]); - now_++; + fs_.Tick(); // Modifying in2 requires rebuilding one intermediate file // and the final file. - fs_.Create("in2", now_, ""); + fs_.Create("in2", ""); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("cat12", &err)); ASSERT_EQ("", err); @@ -561,7 +560,7 @@ TEST_F(BuildTest, TwoOutputs) { " command = touch $out\n" "build out1 out2: touch in.txt\n")); - fs_.Create("in.txt", now_, ""); + fs_.Create("in.txt", ""); string err; EXPECT_TRUE(builder_.AddTarget("out1", &err)); @@ -581,8 +580,9 @@ TEST_F(BuildTest, MultiOutIn) { "build in1 otherfile: touch in\n" "build out: touch in | in1\n")); - fs_.Create("in", now_, ""); - fs_.Create("in1", ++now_, ""); + fs_.Create("in", ""); + fs_.Tick(); + fs_.Create("in1", ""); string err; EXPECT_TRUE(builder_.AddTarget("out", &err)); @@ -598,7 +598,7 @@ TEST_F(BuildTest, Chain) { "build c4: cat c3\n" "build c5: cat c4\n")); - fs_.Create("c1", now_, ""); + fs_.Create("c1", ""); string err; EXPECT_TRUE(builder_.AddTarget("c5", &err)); @@ -614,9 +614,9 @@ TEST_F(BuildTest, Chain) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.AlreadyUpToDate()); - now_++; + fs_.Tick(); - fs_.Create("c3", now_, ""); + fs_.Create("c3", ""); err.clear(); commands_ran_.clear(); state_.Reset(); @@ -657,7 +657,6 @@ TEST_F(BuildTest, MakeDirs) { #endif EXPECT_EQ("", err); - now_ = 0; // Make all stat()s return file not found. EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); ASSERT_EQ(2u, fs_.directories_made_.size()); @@ -674,7 +673,7 @@ TEST_F(BuildTest, DepFileMissing) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n command = cc $in\n depfile = $out.d\n" "build foo.o: cc foo.c\n")); - fs_.Create("foo.c", now_, ""); + fs_.Create("foo.c", ""); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); ASSERT_EQ("", err); @@ -690,9 +689,9 @@ TEST_F(BuildTest, DepFileOK) { "build foo.o: cc foo.c\n")); Edge* edge = state_.edges_.back(); - fs_.Create("foo.c", now_, ""); + fs_.Create("foo.c", ""); GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing. - fs_.Create("foo.o.d", now_, "foo.o: blah.h bar.h\n"); + fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n"); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); ASSERT_EQ("", err); ASSERT_EQ(1u, fs_.files_read_.size()); @@ -713,8 +712,8 @@ TEST_F(BuildTest, DepFileParseError) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n command = cc $in\n depfile = $out.d\n" "build foo.o: cc foo.c\n")); - fs_.Create("foo.c", now_, ""); - fs_.Create("foo.o.d", now_, "randomtext\n"); + fs_.Create("foo.c", ""); + fs_.Create("foo.o.d", "randomtext\n"); EXPECT_FALSE(builder_.AddTarget("foo.o", &err)); EXPECT_EQ("expected depfile 'foo.o.d' to mention 'foo.o', got 'randomtext'", err); @@ -727,9 +726,9 @@ TEST_F(BuildTest, OrderOnlyDeps) { "build foo.o: cc foo.c || otherfile\n")); Edge* edge = state_.edges_.back(); - fs_.Create("foo.c", now_, ""); - fs_.Create("otherfile", now_, ""); - fs_.Create("foo.o.d", now_, "foo.o: blah.h bar.h\n"); + fs_.Create("foo.c", ""); + fs_.Create("otherfile", ""); + fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n"); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); ASSERT_EQ("", err); @@ -752,11 +751,11 @@ TEST_F(BuildTest, OrderOnlyDeps) { ASSERT_EQ("", err); ASSERT_EQ(1u, commands_ran_.size()); - now_++; + fs_.Tick(); // implicit dep dirty, expect a rebuild. - fs_.Create("blah.h", now_, ""); - fs_.Create("bar.h", now_, ""); + fs_.Create("blah.h", ""); + fs_.Create("bar.h", ""); commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); @@ -764,10 +763,10 @@ TEST_F(BuildTest, OrderOnlyDeps) { ASSERT_EQ("", err); ASSERT_EQ(1u, commands_ran_.size()); - now_++; + fs_.Tick(); // order only dep dirty, no rebuild. - fs_.Create("otherfile", now_, ""); + fs_.Create("otherfile", ""); commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); @@ -792,8 +791,8 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { "build oo.h: cc oo.h.in\n" "build foo.o: cc foo.c || oo.h\n")); - fs_.Create("foo.c", now_, ""); - fs_.Create("oo.h.in", now_, ""); + fs_.Create("foo.c", ""); + fs_.Create("oo.h.in", ""); // foo.o and order-only dep dirty, build both. EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); @@ -818,10 +817,10 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { ASSERT_EQ(1u, commands_ran_.size()); ASSERT_EQ("cc oo.h.in", commands_ran_[0]); - now_++; + fs_.Tick(); // order-only dep dirty, build it only. - fs_.Create("oo.h.in", now_, ""); + fs_.Create("oo.h.in", ""); commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); @@ -836,7 +835,7 @@ TEST_F(BuildTest, Phony) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat bar.cc\n" "build all: phony out\n")); - fs_.Create("bar.cc", now_, ""); + fs_.Create("bar.cc", ""); EXPECT_TRUE(builder_.AddTarget("all", &err)); ASSERT_EQ("", err); @@ -853,8 +852,8 @@ TEST_F(BuildTest, PhonyNoWork) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat bar.cc\n" "build all: phony out\n")); - fs_.Create("bar.cc", now_, ""); - fs_.Create("out", now_, ""); + fs_.Create("bar.cc", ""); + fs_.Create("out", ""); EXPECT_TRUE(builder_.AddTarget("all", &err)); ASSERT_EQ("", err); @@ -934,8 +933,8 @@ TEST_F(BuildWithLogTest, NotInLogButOnDisk) { // Create input/output that would be considered up to date when // not considering the command line hash. - fs_.Create("in", now_, ""); - fs_.Create("out1", now_, ""); + fs_.Create("in", ""); + fs_.Create("out1", ""); string err; // Because it's not in the log, it should not be up-to-date until @@ -963,13 +962,13 @@ TEST_F(BuildWithLogTest, RestatTest) { "build out2: true out1\n" "build out3: cat out2\n")); - fs_.Create("out1", now_, ""); - fs_.Create("out2", now_, ""); - fs_.Create("out3", now_, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); + fs_.Create("out3", ""); - now_++; + fs_.Tick(); - fs_.Create("in", now_, ""); + fs_.Create("in", ""); // Do a pre-build so that there's commands in the log for the outputs, // otherwise, the lack of an entry in the build log will cause out3 to rebuild @@ -982,9 +981,9 @@ TEST_F(BuildWithLogTest, RestatTest) { commands_ran_.clear(); state_.Reset(); - now_++; + fs_.Tick(); - fs_.Create("in", now_, ""); + fs_.Create("in", ""); // "cc" touches out1, so we should build out2. But because "true" does not // touch out2, we should cancel the build of out3. EXPECT_TRUE(builder_.AddTarget("out3", &err)); @@ -1000,9 +999,9 @@ TEST_F(BuildWithLogTest, RestatTest) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.AlreadyUpToDate()); - now_++; + fs_.Tick(); - fs_.Create("in", now_, ""); + fs_.Create("in", ""); // The build log entry should not, however, prevent us from rebuilding out2 // if out1 changes. @@ -1028,8 +1027,8 @@ TEST_F(BuildWithLogTest, RestatMissingFile) { "build out1: true in\n" "build out2: cc out1\n")); - fs_.Create("in", now_, ""); - fs_.Create("out2", now_, ""); + fs_.Create("in", ""); + fs_.Create("out2", ""); // Do a pre-build so that there's commands in the log for the outputs, // otherwise, the lack of an entry in the build log will cause out2 to rebuild @@ -1042,9 +1041,9 @@ TEST_F(BuildWithLogTest, RestatMissingFile) { commands_ran_.clear(); state_.Reset(); - now_++; - fs_.Create("in", now_, ""); - fs_.Create("out2", now_, ""); + fs_.Tick(); + fs_.Create("in", ""); + fs_.Create("out2", ""); // Run a build, expect only the first command to run. // It doesn't touch its output (due to being the "true" command), so @@ -1069,14 +1068,14 @@ TEST_F(BuildWithLogTest, RestatMissingInput) { "build out2: cc out1\n")); // Create all necessary files - fs_.Create("in", now_, ""); + fs_.Create("in", ""); // The implicit dependencies and the depfile itself // are newer than the output - TimeStamp restat_mtime = ++now_; - fs_.Create("out1.d", now_, "out1: will.be.deleted restat.file\n"); - fs_.Create("will.be.deleted", now_, ""); - fs_.Create("restat.file", now_, ""); + TimeStamp restat_mtime = fs_.Tick(); + fs_.Create("out1.d", "out1: will.be.deleted restat.file\n"); + fs_.Create("will.be.deleted", ""); + fs_.Create("restat.file", ""); // Run the build, out1 and out2 get built string err; @@ -1127,13 +1126,13 @@ TEST_F(BuildDryRun, AllCommandsShown) { "build out2: true out1\n" "build out3: cat out2\n")); - fs_.Create("out1", now_, ""); - fs_.Create("out2", now_, ""); - fs_.Create("out3", now_, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); + fs_.Create("out3", ""); - now_++; + fs_.Tick(); - fs_.Create("in", now_, ""); + fs_.Create("in", ""); // "cc" touches out1, so we should build out2. But because "true" does not // touch out2, we should cancel the build of out3. @@ -1158,13 +1157,13 @@ TEST_F(BuildTest, RspFileSuccess) " rspfile = out2.rsp\n" " long_command = Some very long command\n")); - fs_.Create("out1", now_, ""); - fs_.Create("out2", now_, ""); - fs_.Create("out3", now_, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); + fs_.Create("out3", ""); - now_++; + fs_.Tick(); - fs_.Create("in", now_, ""); + fs_.Create("in", ""); string err; EXPECT_TRUE(builder_.AddTarget("out1", &err)); @@ -1198,9 +1197,9 @@ TEST_F(BuildTest, RspFileFailure) { " rspfile = out.rsp\n" " long_command = Another very long command\n")); - fs_.Create("out", now_, ""); - now_++; - fs_.Create("in", now_, ""); + fs_.Create("out", ""); + fs_.Tick(); + fs_.Create("in", ""); string err; EXPECT_TRUE(builder_.AddTarget("out", &err)); @@ -1237,9 +1236,9 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { " rspfile = out.rsp\n" " long_command = Original very long command\n")); - fs_.Create("out", now_, ""); - now_++; - fs_.Create("in", now_, ""); + fs_.Create("out", ""); + fs_.Tick(); + fs_.Create("in", ""); string err; EXPECT_TRUE(builder_.AddTarget("out", &err)); @@ -1282,11 +1281,11 @@ TEST_F(BuildTest, InterruptCleanup) { "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_, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); + fs_.Tick(); + fs_.Create("in1", ""); + fs_.Create("in2", ""); // An untouched output of an interrupted command should be retained. string err; @@ -1295,7 +1294,7 @@ TEST_F(BuildTest, InterruptCleanup) { EXPECT_FALSE(builder_.Build(&err)); EXPECT_EQ("interrupted by user", err); builder_.Cleanup(); - EXPECT_EQ(now_-1, fs_.Stat("out1")); + EXPECT_GT(fs_.Stat("out1"), 0); err = ""; // A touched output of an interrupted command should be deleted. @@ -1312,8 +1311,8 @@ TEST_F(BuildTest, PhonyWithNoInputs) { "build nonexistent: phony\n" "build out1: cat || nonexistent\n" "build out2: cat nonexistent\n")); - fs_.Create("out1", now_, ""); - fs_.Create("out2", now_, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); // out1 should be up to date even though its input is dirty, because its // order-only dependency has nothing to do. diff --git a/src/clean_test.cc b/src/clean_test.cc index 5ed48da..04cff73 100644 --- a/src/clean_test.cc +++ b/src/clean_test.cc @@ -31,10 +31,10 @@ TEST_F(CleanTest, CleanAll) { "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); @@ -61,10 +61,10 @@ TEST_F(CleanTest, CleanAllDryRun) { "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); @@ -92,10 +92,10 @@ TEST_F(CleanTest, CleanTarget) { "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); @@ -122,10 +122,10 @@ TEST_F(CleanTest, CleanTargetDryRun) { "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); @@ -155,10 +155,10 @@ TEST_F(CleanTest, CleanRule) { "build out1: cat in1\n" "build in2: cat_e src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); @@ -187,10 +187,10 @@ TEST_F(CleanTest, CleanRuleDryRun) { "build out1: cat in1\n" "build in2: cat_e src2\n" "build out2: cat in2\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); @@ -219,15 +219,15 @@ TEST_F(CleanTest, CleanRuleGenerator) { " generator = 1\n" "build out1: cat in1\n" "build out2: regen in2\n")); - fs_.Create("out1", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("out1", ""); + fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(1, cleaner.cleaned_files_count()); EXPECT_EQ(1u, fs_.files_removed_.size()); - fs_.Create("out1", 1, ""); + fs_.Create("out1", ""); EXPECT_EQ(0, cleaner.CleanAll(/*generator=*/true)); EXPECT_EQ(2, cleaner.cleaned_files_count()); @@ -240,8 +240,8 @@ TEST_F(CleanTest, CleanDepFile) { " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); - fs_.Create("out1", 1, ""); - fs_.Create("out1.d", 1, ""); + fs_.Create("out1", ""); + fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); @@ -255,8 +255,8 @@ TEST_F(CleanTest, CleanDepFileOnCleanTarget) { " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); - fs_.Create("out1", 1, ""); - fs_.Create("out1.d", 1, ""); + fs_.Create("out1", ""); + fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanTarget("out1")); @@ -270,8 +270,8 @@ TEST_F(CleanTest, CleanDepFileOnCleanRule) { " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); - fs_.Create("out1", 1, ""); - fs_.Create("out1.d", 1, ""); + fs_.Create("out1", ""); + fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanRule("cc")); @@ -288,8 +288,8 @@ TEST_F(CleanTest, CleanRspFile) { "build out1: cc in1\n" " rspfile = cc1.rsp\n" " rspfile_content=$in\n")); - fs_.Create("out1", 1, ""); - fs_.Create("cc1.rsp", 1, ""); + fs_.Create("out1", ""); + fs_.Create("cc1.rsp", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); @@ -311,12 +311,12 @@ TEST_F(CleanTest, CleanRsp) { "build out2: cat_rsp in2\n" " rspfile=out2.rsp\n" " rspfile_content=$in\n")); - fs_.Create("in1", 1, ""); - fs_.Create("out1", 1, ""); - fs_.Create("in2.rsp", 1, ""); - fs_.Create("out2.rsp", 1, ""); - fs_.Create("in2", 1, ""); - fs_.Create("out2", 1, ""); + fs_.Create("in1", ""); + fs_.Create("out1", ""); + fs_.Create("in2.rsp", ""); + fs_.Create("out2.rsp", ""); + fs_.Create("in2", ""); + fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); @@ -354,9 +354,9 @@ TEST_F(CleanTest, CleanPhony) { "build t1: cat\n" "build t2: cat\n")); - fs_.Create("phony", 1, ""); - fs_.Create("t1", 1, ""); - fs_.Create("t2", 1, ""); + fs_.Create("phony", ""); + fs_.Create("t1", ""); + fs_.Create("t2", ""); // Check that CleanAll does not remove "phony". Cleaner cleaner(&state_, config_, &fs_); @@ -364,8 +364,8 @@ TEST_F(CleanTest, CleanPhony) { EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_NE(0, fs_.Stat("phony")); - fs_.Create("t1", 1, ""); - fs_.Create("t2", 1, ""); + fs_.Create("t1", ""); + fs_.Create("t2", ""); // Check that CleanTarget does not remove "phony". EXPECT_EQ(0, cleaner.CleanTarget("phony")); diff --git a/src/graph_test.cc b/src/graph_test.cc index 396def4..c105684 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -26,8 +26,8 @@ struct GraphTest : public StateTestWithBuiltinRules { TEST_F(GraphTest, MissingImplicit) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat in | implicit\n")); - fs_.Create("in", 1, ""); - fs_.Create("out", 1, ""); + fs_.Create("in", ""); + fs_.Create("out", ""); Edge* edge = GetNode("out")->in_edge(); string err; @@ -43,9 +43,10 @@ TEST_F(GraphTest, MissingImplicit) { TEST_F(GraphTest, ModifiedImplicit) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat in | implicit\n")); - fs_.Create("in", 1, ""); - fs_.Create("out", 1, ""); - fs_.Create("implicit", 2, ""); + fs_.Create("in", ""); + fs_.Create("out", ""); + fs_.Tick(); + fs_.Create("implicit", ""); Edge* edge = GetNode("out")->in_edge(); string err; @@ -62,10 +63,11 @@ TEST_F(GraphTest, FunkyMakefilePath) { " depfile = $out.d\n" " command = cat $in > $out\n" "build out.o: catdep foo.cc\n")); - fs_.Create("implicit.h", 2, ""); - fs_.Create("foo.cc", 1, ""); - fs_.Create("out.o.d", 1, "out.o: ./foo/../implicit.h\n"); - fs_.Create("out.o", 1, ""); + fs_.Create("foo.cc", ""); + fs_.Create("out.o.d", "out.o: ./foo/../implicit.h\n"); + fs_.Create("out.o", ""); + fs_.Tick(); + fs_.Create("implicit.h", ""); Edge* edge = GetNode("out.o")->in_edge(); string err; @@ -84,11 +86,12 @@ TEST_F(GraphTest, ExplicitImplicit) { " command = cat $in > $out\n" "build implicit.h: cat data\n" "build out.o: catdep foo.cc || implicit.h\n")); - fs_.Create("data", 2, ""); - fs_.Create("implicit.h", 1, ""); - fs_.Create("foo.cc", 1, ""); - fs_.Create("out.o.d", 1, "out.o: implicit.h\n"); - fs_.Create("out.o", 1, ""); + fs_.Create("implicit.h", ""); + fs_.Create("foo.cc", ""); + fs_.Create("out.o.d", "out.o: implicit.h\n"); + fs_.Create("out.o", ""); + fs_.Tick(); + fs_.Create("data", ""); Edge* edge = GetNode("out.o")->in_edge(); string err; @@ -107,9 +110,9 @@ TEST_F(GraphTest, PathWithCurrentDirectory) { " depfile = $out.d\n" " command = cat $in > $out\n" "build ./out.o: catdep ./foo.cc\n")); - fs_.Create("foo.cc", 1, ""); - fs_.Create("out.o.d", 1, "out.o: foo.cc\n"); - fs_.Create("out.o", 1, ""); + fs_.Create("foo.cc", ""); + fs_.Create("out.o.d", "out.o: foo.cc\n"); + fs_.Create("out.o", ""); Edge* edge = GetNode("out.o")->in_edge(); string err; @@ -151,9 +154,9 @@ TEST_F(GraphTest, DepfileWithCanonicalizablePath) { " depfile = $out.d\n" " command = cat $in > $out\n" "build ./out.o: catdep ./foo.cc\n")); - fs_.Create("foo.cc", 1, ""); - fs_.Create("out.o.d", 1, "out.o: bar/../foo.cc\n"); - fs_.Create("out.o", 1, ""); + fs_.Create("foo.cc", ""); + fs_.Create("out.o.d", "out.o: bar/../foo.cc\n"); + fs_.Create("out.o", ""); Edge* edge = GetNode("out.o")->in_edge(); string err; @@ -170,10 +173,11 @@ TEST_F(GraphTest, DepfileRemoved) { " depfile = $out.d\n" " command = cat $in > $out\n" "build ./out.o: catdep ./foo.cc\n")); - fs_.Create("foo.h", 1, ""); - fs_.Create("foo.cc", 1, ""); - fs_.Create("out.o.d", 2, "out.o: foo.h\n"); - fs_.Create("out.o", 2, ""); + fs_.Create("foo.h", ""); + fs_.Create("foo.cc", ""); + fs_.Tick(); + fs_.Create("out.o.d", "out.o: foo.h\n"); + fs_.Create("out.o", ""); Edge* edge = GetNode("out.o")->in_edge(); string err; diff --git a/src/test.cc b/src/test.cc index 0138b3a..c48ca82 100644 --- a/src/test.cc +++ b/src/test.cc @@ -94,9 +94,9 @@ void AssertHash(const char* expected, uint64_t actual) { ASSERT_EQ(BuildLog::LogEntry::HashCommand(expected), actual); } -void VirtualFileSystem::Create(const string& path, int time, +void VirtualFileSystem::Create(const string& path, const string& contents) { - files_[path].mtime = time; + files_[path].mtime = now_; files_[path].contents = contents; files_created_.insert(path); } @@ -109,7 +109,7 @@ TimeStamp VirtualFileSystem::Stat(const string& path) { } bool VirtualFileSystem::WriteFile(const string& path, const string& contents) { - Create(path, 0, contents); + Create(path, contents); return true; } diff --git a/src/test.h b/src/test.h index 37ca1f9..bff0e5a 100644 --- a/src/test.h +++ b/src/test.h @@ -41,8 +41,16 @@ void AssertHash(const char* expected, uint64_t actual); /// of disk state. It also logs file accesses and directory creations /// so it can be used by tests to verify disk access patterns. struct VirtualFileSystem : public DiskInterface { - /// "Create" a file with a given mtime and contents. - void Create(const string& path, int time, const string& contents); + VirtualFileSystem() : now_(1) {} + + /// "Create" a file with contents. + void Create(const string& path, const string& contents); + + /// Tick "time" forwards; subsequent file operations will be newer than + /// previous ones. + int Tick() { + return ++now_; + } // DiskInterface virtual TimeStamp Stat(const string& path); @@ -63,6 +71,9 @@ struct VirtualFileSystem : public DiskInterface { FileMap files_; set files_removed_; set files_created_; + + /// A simple fake timestamp for file operations. + int now_; }; struct ScopedTempDir { -- cgit v0.12 From 76faa257c79dd1404825442fe6b2e83194a0b045 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 8 Apr 2013 14:15:36 -0700 Subject: split FakeCommandRunner out of BuildTest Separating concerns to make a subsequent refactoring easier. --- src/build_test.cc | 182 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 95 insertions(+), 87 deletions(-) diff --git a/src/build_test.cc b/src/build_test.cc index 7eecddf..d4468a3 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -375,13 +375,28 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { ASSERT_FALSE(plan_.more_to_do()); } +/// Fake implementation of CommandRunner, useful for tests. +struct FakeCommandRunner : public CommandRunner { + explicit FakeCommandRunner(VirtualFileSystem* fs) : + last_command_(NULL), fs_(fs) {} -struct BuildTest : public StateTestWithBuiltinRules, - public CommandRunner { - BuildTest() : config_(MakeConfig()), + // CommandRunner impl + virtual bool CanRunMore(); + virtual bool StartCommand(Edge* edge); + virtual Edge* WaitForCommand(ExitStatus* status, string* output); + virtual vector GetActiveEdges(); + virtual void Abort(); + + vector commands_ran_; + Edge* last_command_; + VirtualFileSystem* fs_; +}; + +struct BuildTest : public StateTestWithBuiltinRules { + BuildTest() : config_(MakeConfig()), command_runner_(&fs_), builder_(&state_, config_, NULL, &fs_), - last_command_(NULL), status_(config_) { - builder_.command_runner_.reset(this); + status_(config_) { + builder_.command_runner_.reset(&command_runner_); AssertParse(&state_, "build cat1: cat in1\n" "build cat2: cat in1 in2\n" @@ -398,13 +413,6 @@ struct BuildTest : public StateTestWithBuiltinRules, // Mark a path dirty. void Dirty(const string& path); - // CommandRunner impl - virtual bool CanRunMore(); - virtual bool StartCommand(Edge* edge); - virtual Edge* WaitForCommand(ExitStatus* status, string* output); - virtual vector GetActiveEdges(); - virtual void Abort(); - BuildConfig MakeConfig() { BuildConfig config; config.verbosity = BuildConfig::QUIET; @@ -412,30 +420,19 @@ struct BuildTest : public StateTestWithBuiltinRules, } BuildConfig config_; + FakeCommandRunner command_runner_; VirtualFileSystem fs_; Builder builder_; - vector commands_ran_; - Edge* last_command_; BuildStatus status_; }; -void BuildTest::Dirty(const string& path) { - Node* node = GetNode(path); - node->MarkDirty(); - - // If it's an input file, mark that we've already stat()ed it and - // it's missing. - if (!node->in_edge()) - node->MarkMissing(); -} - -bool BuildTest::CanRunMore() { +bool FakeCommandRunner::CanRunMore() { // Only run one at a time. return last_command_ == NULL; } -bool BuildTest::StartCommand(Edge* edge) { +bool FakeCommandRunner::StartCommand(Edge* edge) { assert(!last_command_); commands_ran_.push_back(edge->EvaluateCommand()); if (edge->rule().name() == "cat" || @@ -445,7 +442,7 @@ bool BuildTest::StartCommand(Edge* edge) { edge->rule().name() == "touch-interrupt") { for (vector::iterator out = edge->outputs_.begin(); out != edge->outputs_.end(); ++out) { - fs_.Create((*out)->path(), ""); + fs_->Create((*out)->path(), ""); } } else if (edge->rule().name() == "true" || edge->rule().name() == "fail" || @@ -460,7 +457,8 @@ bool BuildTest::StartCommand(Edge* edge) { return true; } -Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { +Edge* FakeCommandRunner::WaitForCommand(ExitStatus* status, + string* /* output */) { if (Edge* edge = last_command_) { if (edge->rule().name() == "interrupt" || edge->rule().name() == "touch-interrupt") { @@ -479,17 +477,27 @@ Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) { return NULL; } -vector BuildTest::GetActiveEdges() { +vector FakeCommandRunner::GetActiveEdges() { vector edges; if (last_command_) edges.push_back(last_command_); return edges; } -void BuildTest::Abort() { +void FakeCommandRunner::Abort() { last_command_ = NULL; } +void BuildTest::Dirty(const string& path) { + Node* node = GetNode(path); + node->MarkDirty(); + + // If it's an input file, mark that we've already stat()ed it and + // it's missing. + if (!node->in_edge()) + node->MarkMissing(); +} + TEST_F(BuildTest, NoWork) { string err; EXPECT_TRUE(builder_.AlreadyUpToDate()); @@ -505,8 +513,8 @@ TEST_F(BuildTest, OneStep) { EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); - EXPECT_EQ("cat in1 > cat1", commands_ran_[0]); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]); } TEST_F(BuildTest, OneStep2) { @@ -519,8 +527,8 @@ TEST_F(BuildTest, OneStep2) { EXPECT_TRUE(builder_.Build(&err)); EXPECT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); - EXPECT_EQ("cat in1 > cat1", commands_ran_[0]); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]); } TEST_F(BuildTest, TwoStep) { @@ -529,15 +537,15 @@ TEST_F(BuildTest, TwoStep) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); EXPECT_EQ("", err); - ASSERT_EQ(3u, commands_ran_.size()); + ASSERT_EQ(3u, command_runner_.commands_ran_.size()); // Depending on how the pointers work out, we could've ran // the first two commands in either order. - EXPECT_TRUE((commands_ran_[0] == "cat in1 > cat1" && - commands_ran_[1] == "cat in1 in2 > cat2") || - (commands_ran_[1] == "cat in1 > cat1" && - commands_ran_[0] == "cat in1 in2 > cat2")); + EXPECT_TRUE((command_runner_.commands_ran_[0] == "cat in1 > cat1" && + command_runner_.commands_ran_[1] == "cat in1 in2 > cat2") || + (command_runner_.commands_ran_[1] == "cat in1 > cat1" && + command_runner_.commands_ran_[0] == "cat in1 in2 > cat2")); - EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[2]); + EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[2]); fs_.Tick(); @@ -549,9 +557,9 @@ TEST_F(BuildTest, TwoStep) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(5u, commands_ran_.size()); - EXPECT_EQ("cat in1 in2 > cat2", commands_ran_[3]); - EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[4]); + ASSERT_EQ(5u, command_runner_.commands_ran_.size()); + EXPECT_EQ("cat in1 in2 > cat2", command_runner_.commands_ran_[3]); + EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[4]); } TEST_F(BuildTest, TwoOutputs) { @@ -567,8 +575,8 @@ TEST_F(BuildTest, TwoOutputs) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); EXPECT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); - EXPECT_EQ("touch out1 out2", commands_ran_[0]); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + EXPECT_EQ("touch out1 out2", command_runner_.commands_ran_[0]); } // Test case from @@ -605,10 +613,10 @@ TEST_F(BuildTest, Chain) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); EXPECT_EQ("", err); - ASSERT_EQ(4u, commands_ran_.size()); + ASSERT_EQ(4u, command_runner_.commands_ran_.size()); err.clear(); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("c5", &err)); ASSERT_EQ("", err); @@ -618,13 +626,13 @@ TEST_F(BuildTest, Chain) { fs_.Create("c3", ""); err.clear(); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("c5", &err)); ASSERT_EQ("", err); EXPECT_FALSE(builder_.AlreadyUpToDate()); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2u, commands_ran_.size()); // 3->4, 4->5 + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // 3->4, 4->5 } TEST_F(BuildTest, MissingInput) { @@ -749,25 +757,25 @@ TEST_F(BuildTest, OrderOnlyDeps) { // explicit dep dirty, expect a rebuild. EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); fs_.Tick(); // implicit dep dirty, expect a rebuild. fs_.Create("blah.h", ""); fs_.Create("bar.h", ""); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); fs_.Tick(); // order only dep dirty, no rebuild. fs_.Create("otherfile", ""); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_EQ("", err); @@ -775,12 +783,12 @@ TEST_F(BuildTest, OrderOnlyDeps) { // implicit dep missing, expect rebuild. fs_.RemoveFile("bar.h"); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } TEST_F(BuildTest, RebuildOrderOnlyDeps) { @@ -798,10 +806,10 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(2u, commands_ran_.size()); + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // all clean, no rebuild. - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_EQ("", err); @@ -809,25 +817,25 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { // order-only dep missing, build it only. fs_.RemoveFile("oo.h"); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); - ASSERT_EQ("cc oo.h.in", commands_ran_[0]); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]); fs_.Tick(); // order-only dep dirty, build it only. fs_.Create("oo.h.in", ""); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("foo.o", &err)); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); - ASSERT_EQ("cc oo.h.in", commands_ran_[0]); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]); } TEST_F(BuildTest, Phony) { @@ -844,7 +852,7 @@ TEST_F(BuildTest, Phony) { EXPECT_FALSE(builder_.AlreadyUpToDate()); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } TEST_F(BuildTest, PhonyNoWork) { @@ -871,7 +879,7 @@ TEST_F(BuildTest, Fail) { ASSERT_EQ("", err); EXPECT_FALSE(builder_.Build(&err)); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); ASSERT_EQ("subcommand failed", err); } @@ -892,7 +900,7 @@ TEST_F(BuildTest, SwallowFailures) { ASSERT_EQ("", err); EXPECT_FALSE(builder_.Build(&err)); - ASSERT_EQ(3u, commands_ran_.size()); + ASSERT_EQ(3u, command_runner_.commands_ran_.size()); ASSERT_EQ("subcommands failed", err); } @@ -913,7 +921,7 @@ TEST_F(BuildTest, SwallowFailuresLimit) { ASSERT_EQ("", err); EXPECT_FALSE(builder_.Build(&err)); - ASSERT_EQ(3u, commands_ran_.size()); + ASSERT_EQ(3u, command_runner_.commands_ran_.size()); ASSERT_EQ("cannot make progress due to previous errors", err); } @@ -942,7 +950,7 @@ TEST_F(BuildWithLogTest, NotInLogButOnDisk) { EXPECT_TRUE(builder_.AddTarget("out1", &err)); EXPECT_FALSE(builder_.AlreadyUpToDate()); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out1", &err)); @@ -978,7 +986,7 @@ TEST_F(BuildWithLogTest, RestatTest) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); fs_.Tick(); @@ -989,11 +997,11 @@ TEST_F(BuildWithLogTest, RestatTest) { EXPECT_TRUE(builder_.AddTarget("out3", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2u, commands_ran_.size()); + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // If we run again, it should be a no-op, because the build log has recorded // that we've already built out2 with an input timestamp of 2 (from out1). - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out3", &err)); ASSERT_EQ("", err); @@ -1005,12 +1013,12 @@ TEST_F(BuildWithLogTest, RestatTest) { // The build log entry should not, however, prevent us from rebuilding out2 // if out1 changes. - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out3", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2u, commands_ran_.size()); + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); } TEST_F(BuildWithLogTest, RestatMissingFile) { @@ -1038,7 +1046,7 @@ TEST_F(BuildWithLogTest, RestatMissingFile) { ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); fs_.Tick(); @@ -1051,7 +1059,7 @@ TEST_F(BuildWithLogTest, RestatMissingFile) { EXPECT_TRUE(builder_.AddTarget("out2", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } // Test scenario, in which an input file is removed, but output isn't changed @@ -1082,7 +1090,7 @@ TEST_F(BuildWithLogTest, RestatMissingInput) { EXPECT_TRUE(builder_.AddTarget("out2", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2u, commands_ran_.size()); + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // See that an entry in the logfile is created, capturing // the right mtime @@ -1095,12 +1103,12 @@ TEST_F(BuildWithLogTest, RestatMissingInput) { fs_.RemoveFile("will.be.deleted"); // Trigger the build again - only out1 gets built - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out2", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); // Check that the logfile entry remains correctly set log_entry = build_log_.LookupByOutput("out1"); @@ -1140,7 +1148,7 @@ TEST_F(BuildDryRun, AllCommandsShown) { EXPECT_TRUE(builder_.AddTarget("out3", &err)); ASSERT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(3u, commands_ran_.size()); + ASSERT_EQ(3u, command_runner_.commands_ran_.size()); } // Test that RSP files are created when & where appropriate and deleted after @@ -1175,7 +1183,7 @@ TEST_F(BuildTest, RspFileSuccess) size_t files_removed = fs_.files_removed_.size(); EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(2u, commands_ran_.size()); // cat + cat_rsp + ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // cat + cat_rsp // The RSP file was created ASSERT_EQ(files_created + 1, fs_.files_created_.size()); @@ -1210,7 +1218,7 @@ TEST_F(BuildTest, RspFileFailure) { EXPECT_FALSE(builder_.Build(&err)); ASSERT_EQ("subcommand failed", err); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); // The RSP file was created ASSERT_EQ(files_created + 1, fs_.files_created_.size()); @@ -1246,10 +1254,10 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { // 1. Build for the 1st time (-> populate log) EXPECT_TRUE(builder_.Build(&err)); - ASSERT_EQ(1u, commands_ran_.size()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); // 2. Build again (no change) - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out", &err)); EXPECT_EQ("", err); @@ -1264,12 +1272,12 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) { log_entry->command_hash)); log_entry->command_hash++; // Change the command hash to something else. // Now expect the target to be rebuilt - commands_ran_.clear(); + command_runner_.commands_ran_.clear(); state_.Reset(); EXPECT_TRUE(builder_.AddTarget("out", &err)); EXPECT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); - EXPECT_EQ(1u, commands_ran_.size()); + EXPECT_EQ(1u, command_runner_.commands_ran_.size()); } TEST_F(BuildTest, InterruptCleanup) { @@ -1323,13 +1331,13 @@ TEST_F(BuildTest, PhonyWithNoInputs) { // out2 should still be out of date though, because its input is dirty. err.clear(); - commands_ran_.clear(); + command_runner_.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()); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } TEST_F(BuildTest, StatusFormatReplacePlaceholder) { -- cgit v0.12 From 9f1852fa3c97197e1876f1d47ca45e66b5e6cd28 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 8 Apr 2013 14:44:30 -0700 Subject: sync version number in master with release Update RELEASING with notes on how to not screw this up again. --- RELEASING | 13 +++++++------ src/version.cc | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/RELEASING b/RELEASING index 3f83c7b..4d2e46a 100644 --- a/RELEASING +++ b/RELEASING @@ -1,9 +1,10 @@ Notes to myself on all the steps to make for a Ninja release. -1. git checkout release; git merge master -2. fix version number in source (it will likely conflict in the above) -3. fix version in doc/manual.asciidoc -4. rebuild manual, put in place on website -5. commit, tag, push -6. construct release notes from prior notes +1. update src/version.cc with new version (with ".git") +2. git checkout release; git merge master +3. fix version number in src/version.cc (it will likely conflict in the above) +4. fix version in doc/manual.asciidoc +5. rebuild manual, put in place on website +6. commit, tag, push +7. construct release notes from prior notes credits: git shortlog -s --no-merges REV.. diff --git a/src/version.cc b/src/version.cc index b3a93d1..8eb2e07 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.1.0.git"; +const char* kNinjaVersion = "1.2.0.git"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.'); -- cgit v0.12 From b6a9a1c8adbb444c2489d884f06e5bd39627c3e9 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 17 Dec 2012 09:08:15 -0800 Subject: add DepsLog, a new data structure for dependency information DepsLog is a compact serialization of dependency information. It can be used to replace depfiles for faster loading. --- configure.py | 2 + src/deps_log.cc | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/deps_log.h | 91 +++++++++++++++++++++++++++++++ src/deps_log_test.cc | 63 ++++++++++++++++++++++ src/graph.h | 9 +++- src/state.cc | 5 +- 6 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 src/deps_log.cc create mode 100644 src/deps_log.h create mode 100644 src/deps_log_test.cc diff --git a/configure.py b/configure.py index 10c6994..8f5a497 100755 --- a/configure.py +++ b/configure.py @@ -269,6 +269,7 @@ for name in ['build', 'build_log', 'clean', 'depfile_parser', + 'deps_log', 'disk_interface', 'edit_distance', 'eval_env', @@ -348,6 +349,7 @@ for name in ['build_log_test', 'build_test', 'clean_test', 'depfile_parser_test', + 'deps_log_test', 'disk_interface_test', 'edit_distance_test', 'graph_test', diff --git a/src/deps_log.cc b/src/deps_log.cc new file mode 100644 index 0000000..ca7fd4b --- /dev/null +++ b/src/deps_log.cc @@ -0,0 +1,149 @@ +// 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "deps_log.h" + +#include +#include +#include +#include + +#include "graph.h" +#include "state.h" +#include "util.h" + +bool DepsLog::OpenForWrite(const string& path, string* err) { + file_ = fopen(path.c_str(), "ab"); + if (!file_) { + *err = strerror(errno); + return false; + } + SetCloseOnExec(fileno(file_)); + + // Opening a file in append mode doesn't set the file pointer to the file's + // end on Windows. Do that explicitly. + fseek(file_, 0, SEEK_END); + + /* XXX + if (ftell(log_file_) == 0) { + if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) { + *err = strerror(errno); + return false; + } + } + */ + + return true; +} + +bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, + const vector& nodes) { + // Assign ids to all nodes that are missing one. + if (node->id() < 0) + RecordId(node); + for (vector::const_iterator i = nodes.begin(); + i != nodes.end(); ++i) { + if ((*i)->id() < 0) + RecordId(*i); + } + + uint16_t size = 4 * (1 + 1 + nodes.size()); + size |= 0x8000; // Deps record: set high bit. + fwrite(&size, 2, 1, file_); + int id = node->id(); + fwrite(&id, 4, 1, file_); + int timestamp = node->mtime(); + fwrite(×tamp, 4, 1, file_); + for (vector::const_iterator i = nodes.begin(); + i != nodes.end(); ++i) { + id = node->id(); + fwrite(&id, 4, 1, file_); + } + + return true; +} + +void DepsLog::Close() { + fclose(file_); + file_ = NULL; +} + +bool DepsLog::Load(const string& path, State* state, string* err) { + char buf[32 << 10]; + FILE* f = fopen(path.c_str(), "rb"); + if (!f) { + *err = strerror(errno); + return false; + } + + int id = 0; + for (;;) { + uint16_t size; + if (fread(&size, 2, 1, f) < 1) + break; + bool is_deps = (size >> 15) != 0; + size = size & 0x7FFF; + + if (fread(buf, size, 1, f) < 1) + break; + + if (is_deps) { + assert(size % 4 == 0); + int* deps_data = reinterpret_cast(buf); + int out_id = deps_data[0]; + int mtime = deps_data[1]; + deps_data += 2; + int deps_count = (size / 4) - 2; + + Deps* deps = new Deps; + deps->mtime = mtime; + deps->node_count = deps_count; + deps->nodes = new Node*[deps_count]; + for (int i = 0; i < deps_count; ++i) { + assert(deps_data[i] < (int)nodes_.size()); + assert(nodes_[deps_data[i]]); + deps->nodes[i] = nodes_[deps_data[i]]; + } + + if (out_id >= (int)deps_.size()) + deps_.resize(out_id + 1); + if (deps_[out_id]) + delete deps_[out_id]; + deps_[out_id] = deps; + } else { + StringPiece path(buf, size); + Node* node = state->GetNode(path); + assert(node->id() < 0); + node->set_id(id); + ++id; + } + } + if (ferror(f)) { + *err = strerror(ferror(f)); + return false; + } + fclose(f); + return true; +} + +bool DepsLog::RecordId(Node* node) { + uint16_t size = node->path().size(); + fwrite(&size, 2, 1, file_); + fwrite(node->path().data(), node->path().size(), 1, file_); + + node->set_id(nodes_.size()); + nodes_.push_back(node); + + return true; +} diff --git a/src/deps_log.h b/src/deps_log.h new file mode 100644 index 0000000..45d2cea --- /dev/null +++ b/src/deps_log.h @@ -0,0 +1,91 @@ +// 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NINJA_DEPS_LOG_H_ +#define NINJA_DEPS_LOG_H_ + +#include +#include +using namespace std; + +#include + +#include "timestamp.h" + +struct Node; +struct State; + +/// As build commands run they can output extra dependency information +/// (e.g. header dependencies for C source) via a pipe. DepsLog collects +/// that information at build time and reloads it at startup. +/// +/// The on-disk format is based on two primary constraints: +/// - it must be written to as a stream (during the build, which may be +/// interrupted); +/// - it can be read all at once on startup. (Alternative designs, where +/// it contains indexing information, were considered and discarded as +/// too complicated to implement; if the file is small than reading it +/// fully on startup is acceptable.) +/// Here are some stats from the Windows Chrome dependency files, to +/// help guide the design space. The total text in the files sums to +/// 90mb so some compression is warranted to keep load-time fast. +/// There's about 10k files worth of dependencies that reference about +/// 40k total paths totalling 2mb of unique strings. +/// +/// Based on these above, the file is structured as a sequence of records. +/// Each record is either a path string or a dependency list. +/// Numbering the path strings in file order gives them dense integer ids. +/// A dependency list maps an output id to a list of input ids. +/// +/// Concretely, a record is: +/// two bytes record length, high bit indicates record type +/// (implies max record length 32k) +/// path records contain just the string name of the path +/// dependency records are an array of 4-byte integers +/// [output path id, output path mtime, input path id, input path id...] +/// (The mtime is compared against the on-disk output path mtime +/// to verify the stored data is up-to-date.) +/// If two records reference the same output the latter one in the file +/// wins, allowing updates to just be appended to the file. A separate +/// repacking step can run occasionally to remove dead records. +struct DepsLog { + + // Writing (build-time) interface. + bool OpenForWrite(const string& path, string* err); + bool RecordDeps(Node* node, TimeStamp mtime, const vector& nodes); + void Close(); + + // Reading (startup-time) interface. + bool Load(const string& path, State* state, string* err); + + private: + // Write a node name record, assigning it an id. + bool RecordId(Node* node); + + struct Deps { + Deps() : mtime(-1), node_count(0), nodes(NULL) {} + ~Deps() { delete [] nodes; } + int mtime; + int node_count; + Node** nodes; + }; + + FILE* file_; + vector nodes_; + vector deps_; + + friend struct DepsLogTest; +}; + +#endif // NINJA_DEPS_LOG_H_ diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc new file mode 100644 index 0000000..540865b --- /dev/null +++ b/src/deps_log_test.cc @@ -0,0 +1,63 @@ +// 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "deps_log.h" + +#include "graph.h" +#include "util.h" +#include "test.h" + +namespace { + +const char kTestFilename[] = "DepsLogTest-tempfile"; + +struct DepsLogTest : public testing::Test { + virtual void SetUp() { + // In case a crashing test left a stale file behind. + unlink(kTestFilename); + } + virtual void TearDown() { + //unlink(kTestFilename); + } +}; + +TEST_F(DepsLogTest, WriteRead) { + State state1; + DepsLog log1; + string err; + EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state1.GetNode("foo.h")); + deps.push_back(state1.GetNode("bar.h")); + log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + + deps.clear(); + deps.push_back(state1.GetNode("foo.h")); + deps.push_back(state1.GetNode("bar2.h")); + log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + + log1.Close(); + + State state2; + DepsLog log2; + EXPECT_TRUE(log1.Load(kTestFilename, &state2, &err)); + ASSERT_EQ("", err); + state2.Dump(); + + state2.GetNode("out2.o")->Dump(); +} + +} // anonymous namespace diff --git a/src/graph.h b/src/graph.h index 8b93e29..4ef05ec 100644 --- a/src/graph.h +++ b/src/graph.h @@ -32,7 +32,8 @@ struct Node { : path_(path), mtime_(-1), dirty_(false), - in_edge_(NULL) {} + in_edge_(NULL), + id_(-1) {} /// Return true if the file exists (mtime_ got a value). bool Stat(DiskInterface* disk_interface); @@ -74,6 +75,9 @@ struct Node { Edge* in_edge() const { return in_edge_; } void set_in_edge(Edge* edge) { in_edge_ = edge; } + int id() const { return id_; } + void set_id(int id) { id_ = id; } + const vector& out_edges() const { return out_edges_; } void AddOutEdge(Edge* edge) { out_edges_.push_back(edge); } @@ -98,6 +102,9 @@ private: /// All Edges that use this Node as an input. vector out_edges_; + + /// A dense integer id for the node, assigned and used by DepsLog. + int id_; }; /// An invokable build command and associated metadata (description, etc.). diff --git a/src/state.cc b/src/state.cc index 9f46fee..d2d5ebe 100644 --- a/src/state.cc +++ b/src/state.cc @@ -202,10 +202,11 @@ void State::Reset() { void State::Dump() { for (Paths::iterator i = paths_.begin(); i != paths_.end(); ++i) { Node* node = i->second; - printf("%s %s\n", + printf("%s %s [id:%d]\n", node->path().c_str(), node->status_known() ? (node->dirty() ? "dirty" : "clean") - : "unknown"); + : "unknown", + node->id()); } if (!pools_.empty()) { printf("resource_pools:\n"); -- cgit v0.12 From 695a8e5704d390c84737292b06a2ec97aab52093 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 29 Dec 2012 10:53:59 -0800 Subject: pass command results via a struct WaitForCommand now passes all command output via a struct. This will allow adding more output in a future change. --- src/build.cc | 74 ++++++++++++++++++++++--------------------------------- src/build.h | 13 ++++++++-- src/build_test.cc | 35 +++++++++++++------------- 3 files changed, 58 insertions(+), 64 deletions(-) diff --git a/src/build.cc b/src/build.cc index ae47a50..451d7ca 100644 --- a/src/build.cc +++ b/src/build.cc @@ -47,7 +47,7 @@ struct DryRunCommandRunner : public CommandRunner { // Overridden from CommandRunner: virtual bool CanRunMore(); virtual bool StartCommand(Edge* edge); - virtual Edge* WaitForCommand(ExitStatus* status, string* /* output */); + virtual bool WaitForCommand(Result* result); private: queue finished_; @@ -62,16 +62,14 @@ bool DryRunCommandRunner::StartCommand(Edge* edge) { return true; } -Edge* DryRunCommandRunner::WaitForCommand(ExitStatus* status, - string* /*output*/) { - if (finished_.empty()) { - *status = ExitFailure; - return NULL; - } - *status = ExitSuccess; - Edge* edge = finished_.front(); +bool DryRunCommandRunner::WaitForCommand(Result* result) { + if (finished_.empty()) + return false; + + result->status = ExitSuccess; + result->edge = finished_.front(); finished_.pop(); - return edge; + return true; } } // namespace @@ -549,7 +547,7 @@ struct RealCommandRunner : public CommandRunner { virtual ~RealCommandRunner() {} virtual bool CanRunMore(); virtual bool StartCommand(Edge* edge); - virtual Edge* WaitForCommand(ExitStatus* status, string* output); + virtual bool WaitForCommand(Result* result); virtual vector GetActiveEdges(); virtual void Abort(); @@ -586,25 +584,23 @@ bool RealCommandRunner::StartCommand(Edge* edge) { return true; } -Edge* RealCommandRunner::WaitForCommand(ExitStatus* status, string* output) { +bool RealCommandRunner::WaitForCommand(Result* result) { Subprocess* subproc; while ((subproc = subprocs_.NextFinished()) == NULL) { bool interrupted = subprocs_.DoWork(); - if (interrupted) { - *status = ExitInterrupted; - return 0; - } + if (interrupted) + return false; } - *status = subproc->Finish(); - *output = subproc->GetOutput(); + result->status = subproc->Finish(); + result->output = subproc->GetOutput(); map::iterator i = subproc_to_edge_.find(subproc); - Edge* edge = i->second; + result->edge = i->second; subproc_to_edge_.erase(i); delete subproc; - return edge; + return true; } Builder::Builder(State* state, const BuildConfig& config, @@ -696,8 +692,6 @@ bool Builder::Build(string* err) { // First, we attempt to start as many commands as allowed by the // command runner. // Second, we attempt to wait for / reap the next finished command. - // If we can do neither of those, the build is stuck, and we report - // an error. while (plan_.more_to_do()) { // See if we can start any more commands. if (failures_allowed && command_runner_->CanRunMore()) { @@ -719,34 +713,24 @@ bool Builder::Build(string* err) { // See if we can reap any finished commands. if (pending_commands) { - ExitStatus status; - string output; - Edge* edge = command_runner_->WaitForCommand(&status, &output); - if (edge && status != ExitInterrupted) { - bool success = (status == ExitSuccess); - --pending_commands; - FinishEdge(edge, success, output); - if (!success) { - if (failures_allowed) - failures_allowed--; - } - - // We made some progress; start the main loop over. - continue; - } - - if (status == ExitInterrupted) { + CommandRunner::Result result; + if (!command_runner_->WaitForCommand(&result) || + result.status == ExitInterrupted) { status_->BuildFinished(); *err = "interrupted by user"; return false; } - } - // If we get here, we can neither enqueue new commands nor are any running. - if (pending_commands) { - status_->BuildFinished(); - *err = "stuck: pending commands but none to wait for? [this is a bug]"; - return false; + bool success = (result.status == ExitSuccess); + --pending_commands; + FinishEdge(result.edge, success, result.output); + if (!success) { + if (failures_allowed) + failures_allowed--; + } + + // We made some progress; start the main loop over. + continue; } // If we get here, we cannot make any more progress. diff --git a/src/build.h b/src/build.h index 5747170..fa73620 100644 --- a/src/build.h +++ b/src/build.h @@ -103,8 +103,17 @@ struct CommandRunner { virtual ~CommandRunner() {} virtual bool CanRunMore() = 0; virtual bool StartCommand(Edge* edge) = 0; - /// Wait for a command to complete. - virtual Edge* WaitForCommand(ExitStatus* status, string* output) = 0; + + /// The result of waiting for a command. + struct Result { + Result() : edge(NULL) {} + Edge* edge; + ExitStatus status; + string output; + }; + /// Wait for a command to complete, or return false if interrupted. + virtual bool WaitForCommand(Result* result) = 0; + virtual vector GetActiveEdges() { return vector(); } virtual void Abort() {} }; diff --git a/src/build_test.cc b/src/build_test.cc index d4468a3..a74beb9 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -383,7 +383,7 @@ struct FakeCommandRunner : public CommandRunner { // CommandRunner impl virtual bool CanRunMore(); virtual bool StartCommand(Edge* edge); - virtual Edge* WaitForCommand(ExitStatus* status, string* output); + virtual bool WaitForCommand(Result* result); virtual vector GetActiveEdges(); virtual void Abort(); @@ -457,24 +457,25 @@ bool FakeCommandRunner::StartCommand(Edge* edge) { return true; } -Edge* FakeCommandRunner::WaitForCommand(ExitStatus* status, - string* /* output */) { - if (Edge* edge = last_command_) { - if (edge->rule().name() == "interrupt" || - edge->rule().name() == "touch-interrupt") { - *status = ExitInterrupted; - return NULL; - } +bool FakeCommandRunner::WaitForCommand(Result* result) { + if (!last_command_) + return false; - if (edge->rule().name() == "fail") - *status = ExitFailure; - else - *status = ExitSuccess; - last_command_ = NULL; - return edge; + Edge* edge = last_command_; + result->edge = edge; + + if (edge->rule().name() == "interrupt" || + edge->rule().name() == "touch-interrupt") { + result->status = ExitInterrupted; + return true; } - *status = ExitFailure; - return NULL; + + if (edge->rule().name() == "fail") + result->status = ExitFailure; + else + result->status = ExitSuccess; + last_command_ = NULL; + return true; } vector FakeCommandRunner::GetActiveEdges() { -- cgit v0.12 From 00ffada47e1f9649ba76f12ff514f9434a182ef8 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 29 Dec 2012 11:24:03 -0800 Subject: factor out implicit dep loading --- src/graph.cc | 5 +++-- src/graph.h | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/graph.cc b/src/graph.cc index 0f99698..cbd93b6 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -61,7 +61,7 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { string depfile = edge->GetBinding("depfile"); if (!depfile.empty()) { - if (!LoadDepFile(edge, depfile, err)) { + if (!dep_loader_.LoadDepFile(edge, depfile, err)) { if (!err->empty()) return false; EXPLAIN("Edge targets are dirty because depfile '%s' is missing", @@ -282,7 +282,8 @@ bool Edge::GetBindingBool(const string& key) { return !GetBinding(key).empty(); } -bool DependencyScan::LoadDepFile(Edge* edge, const string& path, string* err) { +bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, + string* err) { METRIC_RECORD("depfile load"); string content = disk_interface_->ReadFile(path, err); if (!err->empty()) { diff --git a/src/graph.h b/src/graph.h index 4ef05ec..dc1ef89 100644 --- a/src/graph.h +++ b/src/graph.h @@ -185,13 +185,26 @@ struct Edge { }; +/// ImplicitDepLoader loads implicit dependencies, as referenced via the +/// "depfile" attribute in build files. +struct ImplicitDepLoader { + explicit ImplicitDepLoader(State* state, DiskInterface* disk_interface) + : state_(state), disk_interface_(disk_interface) {} + + bool LoadDepFile(Edge* edge, const string& path, string* err); + + State* state_; + DiskInterface* disk_interface_; +}; + /// DependencyScan manages the process of scanning the files in a graph /// and updating the dirty/outputs_ready state of all the nodes and edges. struct DependencyScan { DependencyScan(State* state, BuildLog* build_log, DiskInterface* disk_interface) - : state_(state), build_log_(build_log), - disk_interface_(disk_interface) {} + : build_log_(build_log), + disk_interface_(disk_interface), + dep_loader_(state, disk_interface) {} /// Examine inputs, outputs, and command lines to judge whether an edge /// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_| @@ -204,8 +217,6 @@ struct DependencyScan { bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input, const string& command, Node* output); - bool LoadDepFile(Edge* edge, const string& path, string* err); - BuildLog* build_log() const { return build_log_; } @@ -214,9 +225,9 @@ struct DependencyScan { } private: - State* state_; BuildLog* build_log_; DiskInterface* disk_interface_; + ImplicitDepLoader dep_loader_; }; #endif // NINJA_GRAPH_H_ -- cgit v0.12 From 5504c0cb054ecfa1c8cd04fa141f5831560d13f4 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 29 Dec 2012 11:55:52 -0800 Subject: use DepsLog in loading dependencies WIP --- src/deps_log.cc | 6 +++ src/deps_log.h | 12 ++--- src/graph.cc | 135 +++++++++++++++++++++++++++++++++----------------------- src/graph.h | 23 +++++++--- 4 files changed, 109 insertions(+), 67 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index ca7fd4b..744b031 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -137,6 +137,12 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return true; } +DepsLog::Deps* DepsLog::GetDeps(Node* node) { + if (node->id() < 0) + return NULL; + return deps_[node->id()]; +} + bool DepsLog::RecordId(Node* node) { uint16_t size = node->path().size(); fwrite(&size, 2, 1, file_); diff --git a/src/deps_log.h b/src/deps_log.h index 45d2cea..e11cca3 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -67,12 +67,6 @@ struct DepsLog { void Close(); // Reading (startup-time) interface. - bool Load(const string& path, State* state, string* err); - - private: - // Write a node name record, assigning it an id. - bool RecordId(Node* node); - struct Deps { Deps() : mtime(-1), node_count(0), nodes(NULL) {} ~Deps() { delete [] nodes; } @@ -80,6 +74,12 @@ struct DepsLog { int node_count; Node** nodes; }; + bool Load(const string& path, State* state, string* err); + Deps* GetDeps(Node* node); + + private: + // Write a node name record, assigning it an id. + bool RecordId(Node* node); FILE* file_; vector nodes_; diff --git a/src/graph.cc b/src/graph.cc index cbd93b6..43f4304 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -19,6 +19,7 @@ #include "build_log.h" #include "depfile_parser.h" +#include "deps_log.h" #include "disk_interface.h" #include "explain.h" #include "manifest_parser.h" @@ -282,6 +283,48 @@ bool Edge::GetBindingBool(const string& key) { return !GetBinding(key).empty(); } +void Edge::Dump(const char* prefix) const { + printf("%s[ ", prefix); + for (vector::const_iterator i = inputs_.begin(); + i != inputs_.end() && *i != NULL; ++i) { + printf("%s ", (*i)->path().c_str()); + } + printf("--%s-> ", rule_->name().c_str()); + for (vector::const_iterator i = outputs_.begin(); + i != outputs_.end() && *i != NULL; ++i) { + printf("%s ", (*i)->path().c_str()); + } + if (pool_) { + if (!pool_->name().empty()) { + printf("(in pool '%s')", pool_->name().c_str()); + } + } else { + printf("(null pool?)"); + } + printf("] 0x%p\n", this); +} + +bool Edge::is_phony() const { + return rule_ == &State::kPhonyRule; +} + +void Node::Dump(const char* prefix) const { + printf("%s <%s 0x%p> mtime: %d%s, (:%s), ", + prefix, path().c_str(), this, + mtime(), mtime() ? "" : " (:missing)", + dirty() ? " dirty" : " clean"); + if (in_edge()) { + in_edge()->Dump("in-edge: "); + } else { + printf("no in-edge\n"); + } + printf(" out edges:\n"); + for (vector::const_iterator e = out_edges().begin(); + e != out_edges().end() && *e != NULL; ++e) { + (*e)->Dump(" +- "); + } +} + bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, string* err) { METRIC_RECORD("depfile load"); @@ -311,11 +354,8 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, } // Preallocate space in edge->inputs_ to be filled in below. - edge->inputs_.insert(edge->inputs_.end() - edge->order_only_deps_, - depfile.ins_.size(), 0); - edge->implicit_deps_ += depfile.ins_.size(); vector::iterator implicit_dep = - edge->inputs_.end() - edge->order_only_deps_ - depfile.ins_.size(); + PreallocateSpace(edge, depfile.ins_.size()); // Add all its in-edges. for (vector::iterator i = depfile.ins_.begin(); @@ -326,66 +366,49 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, Node* node = state_->GetNode(*i); *implicit_dep = node; node->AddOutEdge(edge); - - // If we don't have a edge that generates this input already, - // create one; this makes us not abort if the input is missing, - // but instead will rebuild in that circumstance. - if (!node->in_edge()) { - Edge* phony_edge = state_->AddEdge(&State::kPhonyRule); - node->set_in_edge(phony_edge); - phony_edge->outputs_.push_back(node); - - // RecomputeDirty might not be called for phony_edge if a previous call - // to RecomputeDirty had caused the file to be stat'ed. Because previous - // invocations of RecomputeDirty would have seen this node without an - // input edge (and therefore ready), we have to set outputs_ready_ to true - // to avoid a potential stuck build. If we do call RecomputeDirty for - // this node, it will simply set outputs_ready_ to the correct value. - phony_edge->outputs_ready_ = true; - } + CreatePhonyInEdge(node); } return true; } -void Edge::Dump(const char* prefix) const { - printf("%s[ ", prefix); - for (vector::const_iterator i = inputs_.begin(); - i != inputs_.end() && *i != NULL; ++i) { - printf("%s ", (*i)->path().c_str()); - } - printf("--%s-> ", rule_->name().c_str()); - for (vector::const_iterator i = outputs_.begin(); - i != outputs_.end() && *i != NULL; ++i) { - printf("%s ", (*i)->path().c_str()); - } - if (pool_) { - if (!pool_->name().empty()) { - printf("(in pool '%s')", pool_->name().c_str()); - } - } else { - printf("(null pool?)"); +bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) { + DepsLog::Deps* deps = deps_log_->GetDeps(edge->outputs_[0]); + if (!deps) + return false; + + // XXX mtime + + vector::iterator implicit_dep = + PreallocateSpace(edge, deps->node_count); + for (int i = 0; i < deps->node_count; ++i, ++implicit_dep) { + *implicit_dep = deps->nodes[i]; + CreatePhonyInEdge(*implicit_dep); } - printf("] 0x%p\n", this); + return true; } -bool Edge::is_phony() const { - return rule_ == &State::kPhonyRule; +vector::iterator ImplicitDepLoader::PreallocateSpace(Edge* edge, + int count) { + edge->inputs_.insert(edge->inputs_.end() - edge->order_only_deps_, + (size_t)count, 0); + edge->implicit_deps_ += count; + return edge->inputs_.end() - edge->order_only_deps_ - count; } -void Node::Dump(const char* prefix) const { - printf("%s <%s 0x%p> mtime: %d%s, (:%s), ", - prefix, path().c_str(), this, - mtime(), mtime() ? "" : " (:missing)", - dirty() ? " dirty" : " clean"); - if (in_edge()) { - in_edge()->Dump("in-edge: "); - } else { - printf("no in-edge\n"); - } - printf(" out edges:\n"); - for (vector::const_iterator e = out_edges().begin(); - e != out_edges().end() && *e != NULL; ++e) { - (*e)->Dump(" +- "); - } +void ImplicitDepLoader::CreatePhonyInEdge(Node* node) { + if (node->in_edge()) + return; + + Edge* phony_edge = state_->AddEdge(&State::kPhonyRule); + node->set_in_edge(phony_edge); + phony_edge->outputs_.push_back(node); + + // RecomputeDirty might not be called for phony_edge if a previous call + // to RecomputeDirty had caused the file to be stat'ed. Because previous + // invocations of RecomputeDirty would have seen this node without an + // input edge (and therefore ready), we have to set outputs_ready_ to true + // to avoid a potential stuck build. If we do call RecomputeDirty for + // this node, it will simply set outputs_ready_ to the correct value. + phony_edge->outputs_ready_ = true; } diff --git a/src/graph.h b/src/graph.h index dc1ef89..b27111b 100644 --- a/src/graph.h +++ b/src/graph.h @@ -22,8 +22,13 @@ using namespace std; #include "eval_env.h" #include "timestamp.h" +struct BuildLog; struct DiskInterface; +struct DepsLog; struct Edge; +struct Node; +struct Pool; +struct State; /// Information about a node in the dependency graph: the file, whether /// it's dirty, mtime, etc. @@ -128,11 +133,6 @@ struct Rule { map bindings_; }; -struct BuildLog; -struct Node; -struct State; -struct Pool; - /// An edge in the dependency graph; links between Nodes using Rules. struct Edge { Edge() : rule_(NULL), env_(NULL), outputs_ready_(false), implicit_deps_(0), @@ -192,11 +192,24 @@ struct ImplicitDepLoader { : state_(state), disk_interface_(disk_interface) {} bool LoadDepFile(Edge* edge, const string& path, string* err); + bool LoadDepsFromLog(Edge* edge, string* err); + + private: + /// Preallocate \a count spaces in the input array on \a edge, returning + /// an iterator pointing at the first new space. + vector::iterator PreallocateSpace(Edge* edge, int count); + + /// If we don't have a edge that generates this input already, + /// create one; this makes us not abort if the input is missing, + /// but instead will rebuild in that circumstance. + void CreatePhonyInEdge(Node* node); State* state_; DiskInterface* disk_interface_; + DepsLog* deps_log_; }; + /// DependencyScan manages the process of scanning the files in a graph /// and updating the dirty/outputs_ready state of all the nodes and edges. struct DependencyScan { -- cgit v0.12 From 78ed77667671cde08b5ef045226aae3f848b1b40 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 09:53:57 -0800 Subject: factor out creation of build directory --- src/ninja.cc | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/ninja.cc b/src/ninja.cc index 69646e1..da99020 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -641,20 +641,13 @@ bool DebugEnable(const string& name, Globals* globals) { } } -bool OpenLog(BuildLog* build_log, Globals* globals, - DiskInterface* disk_interface) { - const string build_dir = - globals->state->bindings_.LookupVariable("builddir"); - const char* kLogPath = ".ninja_log"; - string log_path = kLogPath; - if (!build_dir.empty()) { - log_path = build_dir + "/" + kLogPath; - if (!disk_interface->MakeDirs(log_path) && errno != EEXIST) { - Error("creating build directory %s: %s", - build_dir.c_str(), strerror(errno)); - return false; - } - } +/// Open the build log. +/// @return false on error. +bool OpenBuildLog(BuildLog* build_log, const string& build_dir, + Globals* globals, DiskInterface* disk_interface) { + string log_path = ".ninja_log"; + if (!build_dir.empty()) + log_path = build_dir + "/" + log_path; string err; if (!build_log->Load(log_path, &err)) { @@ -872,9 +865,21 @@ reload: if (tool && tool->when == Tool::RUN_AFTER_LOAD) return tool->func(&globals, argc, argv); - BuildLog build_log; RealDiskInterface disk_interface; - if (!OpenLog(&build_log, &globals, &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)); + return 1; + } + } + + BuildLog build_log; + if (!OpenBuildLog(&build_log, build_dir, &globals, &disk_interface)) return 1; if (!rebuilt_manifest) { // Don't get caught in an infinite loop by a rebuild -- cgit v0.12 From 7c357182a4f672b9bdb901e5710887613eef20e8 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 10:10:03 -0800 Subject: load deps log at startup --- src/ninja.cc | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/ninja.cc b/src/ninja.cc index da99020..9f4d67b 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -32,6 +32,7 @@ #include "browse.h" #include "build.h" #include "build_log.h" +#include "deps_log.h" #include "clean.h" #include "disk_interface.h" #include "edit_distance.h" @@ -670,6 +671,36 @@ bool OpenBuildLog(BuildLog* build_log, const string& build_dir, return true; } +/// 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) { + string path = ".ninja_deps"; + if (!build_dir.empty()) + path = build_dir + "/" + path; + + string err; + if (!deps_log->Load(path, globals->state, &err)) { + Error("loading deps log %s: %s", path.c_str(), err.c_str()); + return false; + } + if (!err.empty()) { + // Hack: Load() can return a warning via err by returning true. + Warning("%s", err.c_str()); + err.clear(); + } + + if (!globals->config->dry_run) { + if (!deps_log->OpenForWrite(path, &err)) { + Error("opening deps log: %s", err.c_str()); + return false; + } + } + + return true; +} + + /// Dump the output requested by '-d stats'. void DumpMetrics(Globals* globals) { g_metrics->Report(); @@ -882,6 +913,10 @@ reload: if (!OpenBuildLog(&build_log, build_dir, &globals, &disk_interface)) return 1; + DepsLog deps_log; + if (!OpenDepsLog(&deps_log, build_dir, &globals, &disk_interface)) + 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, -- cgit v0.12 From 6e21a236edd859b885f8c1bea8090bbdd76bf15d Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 10:11:13 -0800 Subject: no error if deps log doesn't exist --- src/deps_log.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/deps_log.cc b/src/deps_log.cc index 744b031..23c9820 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -83,6 +83,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { char buf[32 << 10]; FILE* f = fopen(path.c_str(), "rb"); if (!f) { + if (errno == ENOENT) + return true; *err = strerror(errno); return false; } -- cgit v0.12 From 70fd0f590c3a46a4a9ff9a160f455c5c30f5b90a Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 10:15:49 -0800 Subject: plumb DepsLog load through Builder --- src/build.cc | 5 +++-- src/build.h | 3 ++- src/build_test.cc | 2 +- src/disk_interface_test.cc | 2 +- src/graph.h | 9 +++++---- src/graph_test.cc | 2 +- src/ninja.cc | 5 +++-- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/build.cc b/src/build.cc index 451d7ca..f44cd52 100644 --- a/src/build.cc +++ b/src/build.cc @@ -604,9 +604,10 @@ bool RealCommandRunner::WaitForCommand(Result* result) { } Builder::Builder(State* state, const BuildConfig& config, - BuildLog* log, DiskInterface* disk_interface) + BuildLog* build_log, DepsLog* deps_log, + DiskInterface* disk_interface) : state_(state), config_(config), disk_interface_(disk_interface), - scan_(state, log, disk_interface) { + scan_(state, build_log, deps_log, disk_interface) { status_ = new BuildStatus(config); } diff --git a/src/build.h b/src/build.h index fa73620..36bca57 100644 --- a/src/build.h +++ b/src/build.h @@ -140,7 +140,8 @@ struct BuildConfig { /// Builder wraps the build process: starting commands, updating status. struct Builder { Builder(State* state, const BuildConfig& config, - BuildLog* log, DiskInterface* disk_interface); + BuildLog* build_log, DepsLog* deps_log, + DiskInterface* disk_interface); ~Builder(); /// Clean up after interrupted commands by deleting output files. diff --git a/src/build_test.cc b/src/build_test.cc index a74beb9..3617439 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -394,7 +394,7 @@ struct FakeCommandRunner : public CommandRunner { struct BuildTest : public StateTestWithBuiltinRules { BuildTest() : config_(MakeConfig()), command_runner_(&fs_), - builder_(&state_, config_, NULL, &fs_), + builder_(&state_, config_, NULL, NULL, &fs_), status_(config_) { builder_.command_runner_.reset(&command_runner_); AssertParse(&state_, diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index c2315c7..9b43d0f 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -104,7 +104,7 @@ TEST_F(DiskInterfaceTest, RemoveFile) { struct StatTest : public StateTestWithBuiltinRules, public DiskInterface { - StatTest() : scan_(&state_, NULL, this) {} + StatTest() : scan_(&state_, NULL, NULL, this) {} // DiskInterface implementation. virtual TimeStamp Stat(const string& path); diff --git a/src/graph.h b/src/graph.h index b27111b..d943702 100644 --- a/src/graph.h +++ b/src/graph.h @@ -188,8 +188,9 @@ struct Edge { /// ImplicitDepLoader loads implicit dependencies, as referenced via the /// "depfile" attribute in build files. struct ImplicitDepLoader { - explicit ImplicitDepLoader(State* state, DiskInterface* disk_interface) - : state_(state), disk_interface_(disk_interface) {} + ImplicitDepLoader(State* state, DepsLog* deps_log, + DiskInterface* disk_interface) + : state_(state), disk_interface_(disk_interface), deps_log_(deps_log) {} bool LoadDepFile(Edge* edge, const string& path, string* err); bool LoadDepsFromLog(Edge* edge, string* err); @@ -213,11 +214,11 @@ struct ImplicitDepLoader { /// DependencyScan manages the process of scanning the files in a graph /// and updating the dirty/outputs_ready state of all the nodes and edges. struct DependencyScan { - DependencyScan(State* state, BuildLog* build_log, + DependencyScan(State* state, BuildLog* build_log, DepsLog* deps_log, DiskInterface* disk_interface) : build_log_(build_log), disk_interface_(disk_interface), - dep_loader_(state, disk_interface) {} + dep_loader_(state, deps_log, disk_interface) {} /// Examine inputs, outputs, and command lines to judge whether an edge /// needs to be re-run, and update outputs_ready_ and each outputs' |dirty_| diff --git a/src/graph_test.cc b/src/graph_test.cc index c105684..63d5757 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -17,7 +17,7 @@ #include "test.h" struct GraphTest : public StateTestWithBuiltinRules { - GraphTest() : scan_(&state_, NULL, &fs_) {} + GraphTest() : scan_(&state_, NULL, NULL, &fs_) {} VirtualFileSystem fs_; DependencyScan scan_; diff --git a/src/ninja.cc b/src/ninja.cc index 9f4d67b..8ba1aa6 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -919,7 +919,7 @@ reload: 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, + Builder manifest_builder(globals.state, config, &build_log, &deps_log, &disk_interface); if (RebuildManifest(&manifest_builder, input_file, &err)) { rebuilt_manifest = true; @@ -931,7 +931,8 @@ reload: } } - Builder builder(globals.state, config, &build_log, &disk_interface); + Builder builder(globals.state, config, &build_log, &deps_log, + &disk_interface); int result = RunBuild(&builder, argc, argv); if (g_metrics) DumpMetrics(&globals); -- cgit v0.12 From 2f2bacc39ee47b648d6b0aea4c60cb64c41393df Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 12:48:44 -0800 Subject: expand DepsLog test, fix two bugs it revealed --- src/deps_log.cc | 9 ++++----- src/deps_log.h | 5 +++++ src/deps_log_test.cc | 38 +++++++++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 23c9820..5732b6d 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -63,11 +63,11 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, fwrite(&size, 2, 1, file_); int id = node->id(); fwrite(&id, 4, 1, file_); - int timestamp = node->mtime(); + int timestamp = mtime; fwrite(×tamp, 4, 1, file_); for (vector::const_iterator i = nodes.begin(); i != nodes.end(); ++i) { - id = node->id(); + id = (*i)->id(); fwrite(&id, 4, 1, file_); } @@ -89,7 +89,6 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return false; } - int id = 0; for (;;) { uint16_t size; if (fread(&size, 2, 1, f) < 1) @@ -127,8 +126,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { StringPiece path(buf, size); Node* node = state->GetNode(path); assert(node->id() < 0); - node->set_id(id); - ++id; + node->set_id(nodes_.size()); + nodes_.push_back(node); } } if (ferror(f)) { diff --git a/src/deps_log.h b/src/deps_log.h index e11cca3..5ddc7bd 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -77,12 +77,17 @@ struct DepsLog { bool Load(const string& path, State* state, string* err); Deps* GetDeps(Node* node); + /// Used for tests. + const vector& nodes() const { return nodes_; } + private: // Write a node name record, assigning it an id. bool RecordId(Node* node); FILE* file_; + /// Maps id -> Node. vector nodes_; + /// Maps id -> deps of that id. vector deps_; friend struct DepsLogTest; diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 540865b..3f47fef 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -39,25 +39,41 @@ TEST_F(DepsLogTest, WriteRead) { EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err)); ASSERT_EQ("", err); - vector deps; - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar.h")); - log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + { + vector deps; + deps.push_back(state1.GetNode("foo.h")); + deps.push_back(state1.GetNode("bar.h")); + log1.RecordDeps(state1.GetNode("out.o"), 1, deps); - deps.clear(); - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar2.h")); - log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + deps.clear(); + deps.push_back(state1.GetNode("foo.h")); + deps.push_back(state1.GetNode("bar2.h")); + log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + } log1.Close(); State state2; DepsLog log2; - EXPECT_TRUE(log1.Load(kTestFilename, &state2, &err)); + EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err)); ASSERT_EQ("", err); - state2.Dump(); - state2.GetNode("out2.o")->Dump(); + ASSERT_EQ(log1.nodes().size(), log2.nodes().size()); + for (int i = 0; i < (int)log1.nodes().size(); ++i) { + Node* node1 = log1.nodes()[i]; + Node* node2 = log2.nodes()[i]; + ASSERT_EQ(i, node1->id()); + ASSERT_EQ(node1->id(), node2->id()); + } + + // log1 has no deps entries, as it was only used for writing. + // Manually check the entries in log2. + DepsLog::Deps* deps = log2.GetDeps(state2.GetNode("out.o")); + ASSERT_TRUE(deps); + ASSERT_EQ(1, deps->mtime); + ASSERT_EQ(2, deps->node_count); + ASSERT_EQ("foo.h", deps->nodes[0]->path()); + ASSERT_EQ("bar.h", deps->nodes[1]->path()); } } // anonymous namespace -- cgit v0.12 From c683ca7d0d9281ef8fc7cb2c4ec022ebc861b745 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 30 Dec 2012 12:50:43 -0800 Subject: track deps log load time in metrics --- src/deps_log.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/deps_log.cc b/src/deps_log.cc index 5732b6d..de25f7b 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -20,6 +20,7 @@ #include #include "graph.h" +#include "metrics.h" #include "state.h" #include "util.h" @@ -80,6 +81,7 @@ void DepsLog::Close() { } bool DepsLog::Load(const string& path, State* state, string* err) { + METRIC_RECORD(".ninja_deps load"); char buf[32 << 10]; FILE* f = fopen(path.c_str(), "rb"); if (!f) { -- cgit v0.12 From 85a4db7822d1b433eff8cf9f94f8f1dab3f0b23d Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 4 Jan 2013 09:39:01 -0800 Subject: add "special=gcc" attribute, use to load depslog --- misc/ninja_syntax.py | 4 +++- src/graph.cc | 44 ++++++++++++++++++++++++++++++++++---------- src/graph.h | 11 ++++++++++- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index ece7eb5..519330e 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -38,7 +38,7 @@ class Writer(object): def rule(self, name, command, description=None, depfile=None, generator=False, pool=None, restat=False, rspfile=None, - rspfile_content=None): + rspfile_content=None, special=None): self._line('rule %s' % name) self.variable('command', command, indent=1) if description: @@ -55,6 +55,8 @@ class Writer(object): self.variable('rspfile', rspfile, indent=1) if rspfile_content: self.variable('rspfile_content', rspfile_content, indent=1) + if special: + self.variable('special', special, indent=1) def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): diff --git a/src/graph.cc b/src/graph.cc index 43f4304..2f86785 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -53,22 +53,19 @@ bool Rule::IsReservedBinding(const string& var) { var == "pool" || var == "restat" || var == "rspfile" || - var == "rspfile_content"; + var == "rspfile_content" || + var == "special"; } bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { bool dirty = false; edge->outputs_ready_ = true; - string depfile = edge->GetBinding("depfile"); - if (!depfile.empty()) { - if (!dep_loader_.LoadDepFile(edge, depfile, err)) { - if (!err->empty()) - return false; - EXPLAIN("Edge targets are dirty because depfile '%s' is missing", - depfile.c_str()); - dirty = true; - } + if (!dep_loader_.LoadDeps(edge, err)) { + if (!err->empty()) + return false; + // Failed to load dependency info: rebuild to regenerate it. + dirty = true; } // Visit all inputs; we're dirty if any of the inputs are dirty. @@ -325,6 +322,33 @@ void Node::Dump(const char* prefix) const { } } +bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { + string special = edge->GetBinding("special"); + if (!special.empty() && special == "gcc") { + if (!LoadDepsFromLog(edge, err)) { + if (!err->empty()) + return false; + EXPLAIN("deps for %s are missing", edge->outputs_[0]->path().c_str()); + return false; + } + return true; + } + + string depfile = edge->GetBinding("depfile"); + if (!depfile.empty()) { + if (!LoadDepFile(edge, depfile, err)) { + if (!err->empty()) + return false; + EXPLAIN("depfile '%s' is missing", depfile.c_str()); + return false; + } + return true; + } + + // No deps to load. + return true; +} + bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, string* err) { METRIC_RECORD("depfile load"); diff --git a/src/graph.h b/src/graph.h index d943702..a7b60db 100644 --- a/src/graph.h +++ b/src/graph.h @@ -192,10 +192,19 @@ struct ImplicitDepLoader { DiskInterface* disk_interface) : state_(state), disk_interface_(disk_interface), deps_log_(deps_log) {} + /// Load implicit dependencies for \a edge. + /// @return false on error (without filling \a err if info is just missing). + bool LoadDeps(Edge* edge, string* err); + + private: + /// Load implicit dependencies for \a edge from a depfile attribute. + /// @return false on error (without filling \a err if info is just missing). bool LoadDepFile(Edge* edge, const string& path, string* err); + + /// Load implicit dependencies for \a edge from the DepsLog. + /// @return false on error (without filling \a err if info is just missing). bool LoadDepsFromLog(Edge* edge, string* err); - private: /// Preallocate \a count spaces in the input array on \a edge, returning /// an iterator pointing at the first new space. vector::iterator PreallocateSpace(Edge* edge, int count); -- cgit v0.12 From d9c24381c50506436212ac447a0805f5ff6892ac Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 5 Jan 2013 10:49:48 -0800 Subject: windows: add uint16 casts in depslog --- src/deps_log.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index de25f7b..da6cd93 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -59,7 +59,7 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, RecordId(*i); } - uint16_t size = 4 * (1 + 1 + nodes.size()); + uint16_t size = 4 * (1 + 1 + (uint16_t)nodes.size()); size |= 0x8000; // Deps record: set high bit. fwrite(&size, 2, 1, file_); int id = node->id(); @@ -147,7 +147,7 @@ DepsLog::Deps* DepsLog::GetDeps(Node* node) { } bool DepsLog::RecordId(Node* node) { - uint16_t size = node->path().size(); + uint16_t size = (uint16_t)node->path().size(); fwrite(&size, 2, 1, file_); fwrite(node->path().data(), node->path().size(), 1, file_); -- cgit v0.12 From 70d43e48e8b8a71c1f1cc55718b34480f68e1341 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 6 Jan 2013 11:36:17 -0800 Subject: factor MSVC parsing out of CLWrapper into CLParser --- src/msvc_helper-win32.cc | 94 +++++++++++++++----------------- src/msvc_helper.h | 42 ++++++++------- src/msvc_helper_main-win32.cc | 26 ++++----- src/msvc_helper_test.cc | 123 ++++++++++++++++++++---------------------- 4 files changed, 139 insertions(+), 146 deletions(-) diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc index fd9b671..be2a5e0 100644 --- a/src/msvc_helper-win32.cc +++ b/src/msvc_helper-win32.cc @@ -39,15 +39,15 @@ string Replace(const string& input, const string& find, const string& replace) { return result; } +} // anonymous namespace + string EscapeForDepfile(const string& path) { // Depfiles don't escape single \. return Replace(path, " ", "\\ "); } -} // anonymous namespace - // static -string CLWrapper::FilterShowIncludes(const string& line) { +string CLParser::FilterShowIncludes(const string& line) { static const char kMagicPrefix[] = "Note: including file: "; const char* in = line.c_str(); const char* end = in + line.size(); @@ -63,14 +63,14 @@ string CLWrapper::FilterShowIncludes(const string& line) { } // static -bool CLWrapper::IsSystemInclude(const string& path) { +bool CLParser::IsSystemInclude(const string& path) { // 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 CLWrapper::FilterInputFilename(const string& line) { +bool CLParser::FilterInputFilename(const string& line) { // TODO: other extensions, like .asm? return EndsWith(line, ".c") || EndsWith(line, ".cc") || @@ -78,7 +78,42 @@ bool CLWrapper::FilterInputFilename(const string& line) { EndsWith(line, ".cpp"); } -int CLWrapper::Run(const string& command, string* extra_output) { +string CLParser::Parse(const string& output) { + string filtered_output; + + // Loop over all lines in the output to process them. + size_t start = 0; + while (start < output.size()) { + size_t end = output.find_first_of("\r\n", start); + if (end == string::npos) + end = output.size(); + string line = output.substr(start, end - start); + + string include = FilterShowIncludes(line); + if (!include.empty()) { + include = IncludesNormalize::Normalize(include, NULL); + if (!IsSystemInclude(include)) + includes_.insert(include); + } else if (FilterInputFilename(line)) { + // Drop it. + // TODO: if we support compiling multiple output files in a single + // cl.exe invocation, we should stash the filename. + } else { + filtered_output.append(line); + filtered_output.append("\n"); + } + + if (end < output.size() && output[end] == '\r') + ++end; + if (end < output.size() && output[end] == '\n') + ++end; + start = end; + } + + return filtered_output; +} + +int CLWrapper::Run(const string& command, string* output) { SECURITY_ATTRIBUTES security_attributes = {}; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = TRUE; @@ -118,8 +153,7 @@ int CLWrapper::Run(const string& command, string* extra_output) { Win32Fatal("CloseHandle"); } - // Read output of the subprocess and parse it. - string output; + // Read all output of the subprocess. DWORD read_len = 1; while (read_len) { char buf[64 << 10]; @@ -128,44 +162,12 @@ int CLWrapper::Run(const string& command, string* extra_output) { GetLastError() != ERROR_BROKEN_PIPE) { Win32Fatal("ReadFile"); } - output.append(buf, read_len); - - // Loop over all lines in the output and process them. - for (;;) { - size_t ofs = output.find_first_of("\r\n"); - if (ofs == string::npos) - break; - string line = output.substr(0, ofs); - - string include = FilterShowIncludes(line); - if (!include.empty()) { - include = IncludesNormalize::Normalize(include, NULL); - if (!IsSystemInclude(include)) - includes_.insert(include); - } else if (FilterInputFilename(line)) { - // Drop it. - // TODO: if we support compiling multiple output files in a single - // cl.exe invocation, we should stash the filename. - } else { - if (extra_output) { - extra_output->append(line); - extra_output->append("\n"); - } else { - printf("%s\n", line.c_str()); - } - } - - if (ofs < output.size() && output[ofs] == '\r') - ++ofs; - if (ofs < output.size() && output[ofs] == '\n') - ++ofs; - output = output.substr(ofs); - } + output->append(buf, read_len); } + // Wait for it to exit and grab its exit code. if (WaitForSingleObject(process_info.hProcess, INFINITE) == WAIT_FAILED) Win32Fatal("WaitForSingleObject"); - DWORD exit_code = 0; if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) Win32Fatal("GetExitCodeProcess"); @@ -178,11 +180,3 @@ int CLWrapper::Run(const string& command, string* extra_output) { return exit_code; } - -vector CLWrapper::GetEscapedResult() { - vector result; - for (set::iterator i = includes_.begin(); i != includes_.end(); ++i) { - result.push_back(EscapeForDepfile(*i)); - } - return result; -} diff --git a/src/msvc_helper.h b/src/msvc_helper.h index 102201b..32ab606 100644 --- a/src/msvc_helper.h +++ b/src/msvc_helper.h @@ -17,23 +17,13 @@ #include using namespace std; +string EscapeForDepfile(const string& path); + /// Visual Studio's cl.exe requires some massaging to work with Ninja; /// for example, it emits include information on stderr in a funny -/// format when building with /showIncludes. This class wraps a CL -/// process and parses that output to extract the file list. -struct CLWrapper { - CLWrapper() : env_block_(NULL) {} - - /// Set the environment block (as suitable for CreateProcess) to be used - /// by Run(). - void SetEnvBlock(void* env_block) { env_block_ = env_block; } - - /// Start a process and parse its output. Returns its exit code. - /// Any non-parsed output is buffered into \a extra_output if provided, - /// otherwise it is printed to stdout while the process runs. - /// Crashes (calls Fatal()) on error. - int Run(const string& command, string* extra_output=NULL); - +/// format when building with /showIncludes. This class parses this +/// output. +struct CLParser { /// Parse a line of cl.exe output and extract /showIncludes info. /// If a dependency is extracted, returns a nonempty string. /// Exposed for testing. @@ -50,10 +40,24 @@ struct CLWrapper { /// Exposed for testing. static bool FilterInputFilename(const string& line); - /// Fill a vector with the unique'd headers, escaped for output as a .d - /// file. - vector GetEscapedResult(); + /// Parse the full output of cl, returning the output (if any) that + /// should printed. + string Parse(const string& output); - void* env_block_; set includes_; }; + +/// Wraps a synchronous execution of a CL subprocess. +struct CLWrapper { + CLWrapper() : env_block_(NULL) {} + + /// Set the environment block (as suitable for CreateProcess) to be used + /// by Run(). + void SetEnvBlock(void* env_block) { env_block_ = env_block; } + + /// Start a process and gather its raw output. Returns its exit code. + /// Crashes (calls Fatal()) on error. + int Run(const string& command, string* output); + + void* env_block_; +}; diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc index 0bbe98b..3192821 100644 --- a/src/msvc_helper_main-win32.cc +++ b/src/msvc_helper_main-win32.cc @@ -44,12 +44,13 @@ void PushPathIntoEnvironment(const string& env_block) { } } -void WriteDepFileOrDie(const char* object_path, CLWrapper* cl) { +void WriteDepFileOrDie(const char* object_path, const CLParser& parse) { string depfile_path = string(object_path) + ".d"; FILE* depfile = fopen(depfile_path.c_str(), "w"); if (!depfile) { unlink(object_path); - Fatal("opening %s: %s", depfile_path.c_str(), GetLastErrorString().c_str()); + Fatal("opening %s: %s", depfile_path.c_str(), + GetLastErrorString().c_str()); } if (fprintf(depfile, "%s: ", object_path) < 0) { unlink(object_path); @@ -57,9 +58,9 @@ void WriteDepFileOrDie(const char* object_path, CLWrapper* cl) { unlink(depfile_path.c_str()); Fatal("writing %s", depfile_path.c_str()); } - vector headers = cl->GetEscapedResult(); - for (vector::iterator i = headers.begin(); i != headers.end(); ++i) { - if (fprintf(depfile, "%s\n", i->c_str()) < 0) { + const set& headers = parse.includes_; + for (set::iterator i = headers.begin(); i != headers.end(); ++i) { + if (fprintf(depfile, "%s\n", EscapeForDepfile(*i).c_str()) < 0) { unlink(object_path); fclose(depfile); unlink(depfile_path.c_str()); @@ -95,11 +96,6 @@ int MSVCHelperMain(int argc, char** argv) { } } - if (!output_filename) { - Usage(); - Fatal("-o required"); - } - string env; if (envfile) { string err; @@ -118,9 +114,15 @@ int MSVCHelperMain(int argc, char** argv) { CLWrapper cl; if (!env.empty()) cl.SetEnvBlock((void*)env.data()); - int exit_code = cl.Run(command); + string output; + int exit_code = cl.Run(command, &output); - WriteDepFileOrDie(output_filename, &cl); + if (output_filename) { + CLParser parser; + output = parser.Parse(output); + WriteDepFileOrDie(output_filename, parser); + } + printf("%s\n", output.c_str()); return exit_code; } diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc index 7730425..1e1cbde 100644 --- a/src/msvc_helper_test.cc +++ b/src/msvc_helper_test.cc @@ -19,100 +19,93 @@ #include "test.h" #include "util.h" -TEST(MSVCHelperTest, ShowIncludes) { - ASSERT_EQ("", CLWrapper::FilterShowIncludes("")); +TEST(CLParserTest, ShowIncludes) { + ASSERT_EQ("", CLParser::FilterShowIncludes("")); - ASSERT_EQ("", CLWrapper::FilterShowIncludes("Sample compiler output")); + ASSERT_EQ("", CLParser::FilterShowIncludes("Sample compiler output")); ASSERT_EQ("c:\\Some Files\\foobar.h", - CLWrapper::FilterShowIncludes("Note: including file: " - "c:\\Some Files\\foobar.h")); + CLParser::FilterShowIncludes("Note: including file: " + "c:\\Some Files\\foobar.h")); ASSERT_EQ("c:\\initspaces.h", - CLWrapper::FilterShowIncludes("Note: including file: " - "c:\\initspaces.h")); + CLParser::FilterShowIncludes("Note: including file: " + "c:\\initspaces.h")); } -TEST(MSVCHelperTest, FilterInputFilename) { - ASSERT_TRUE(CLWrapper::FilterInputFilename("foobar.cc")); - ASSERT_TRUE(CLWrapper::FilterInputFilename("foo bar.cc")); - ASSERT_TRUE(CLWrapper::FilterInputFilename("baz.c")); +TEST(CLParserTest, FilterInputFilename) { + ASSERT_TRUE(CLParser::FilterInputFilename("foobar.cc")); + ASSERT_TRUE(CLParser::FilterInputFilename("foo bar.cc")); + ASSERT_TRUE(CLParser::FilterInputFilename("baz.c")); - ASSERT_FALSE(CLWrapper::FilterInputFilename( + ASSERT_FALSE(CLParser::FilterInputFilename( "src\\cl_helper.cc(166) : fatal error C1075: end " "of file found ...")); } -TEST(MSVCHelperTest, Run) { - CLWrapper cl; - string output; - cl.Run("cmd /c \"echo foo&& echo Note: including file: foo.h&&echo bar\"", - &output); +TEST(CLParserTest, ParseSimple) { + CLParser parser; + string output = parser.Parse( + "foo\r\n" + "Note: including file: foo.h\r\n" + "bar\r\n"); + ASSERT_EQ("foo\nbar\n", output); - ASSERT_EQ(1u, cl.includes_.size()); - ASSERT_EQ("foo.h", *cl.includes_.begin()); + ASSERT_EQ(1u, parser.includes_.size()); + ASSERT_EQ("foo.h", *parser.includes_.begin()); } -TEST(MSVCHelperTest, RunFilenameFilter) { - CLWrapper cl; - string output; - cl.Run("cmd /c \"echo foo.cc&& echo cl: warning\"", - &output); +TEST(CLParserTest, ParseFilenameFilter) { + CLParser parser; + string output = parser.Parse( + "foo.cc\r\n" + "cl: warning\r\n"); ASSERT_EQ("cl: warning\n", output); } -TEST(MSVCHelperTest, RunSystemInclude) { - CLWrapper cl; - string output; - cl.Run("cmd /c \"echo Note: including file: c:\\Program Files\\foo.h&&" - "echo Note: including file: d:\\Microsoft Visual Studio\\bar.h&&" - "echo Note: including file: path.h\"", - &output); +TEST(CLParserTest, ParseSystemInclude) { + CLParser parser; + string output = parser.Parse( + "Note: including file: c:\\Program Files\\foo.h\r\n" + "Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n" + "Note: including file: path.h\r\n"); // We should have dropped the first two includes because they look like // system headers. ASSERT_EQ("", output); - ASSERT_EQ(1u, cl.includes_.size()); - ASSERT_EQ("path.h", *cl.includes_.begin()); -} - -TEST(MSVCHelperTest, EnvBlock) { - char env_block[] = "foo=bar\0"; - CLWrapper cl; - cl.SetEnvBlock(env_block); - string output; - cl.Run("cmd /c \"echo foo is %foo%", &output); - ASSERT_EQ("foo is bar\n", output); + ASSERT_EQ(1u, parser.includes_.size()); + ASSERT_EQ("path.h", *parser.includes_.begin()); } -TEST(MSVCHelperTest, DuplicatedHeader) { - CLWrapper cl; - string output; - cl.Run("cmd /c \"echo Note: including file: foo.h&&" - "echo Note: including file: bar.h&&" - "echo Note: including file: foo.h\"", - &output); +TEST(CLParserTest, DuplicatedHeader) { + CLParser parser; + string output = parser.Parse( + "Note: including file: foo.h\r\n" + "Note: including file: bar.h\r\n" + "Note: including file: foo.h\r\n"); // We should have dropped one copy of foo.h. ASSERT_EQ("", output); - ASSERT_EQ(2u, cl.includes_.size()); + ASSERT_EQ(2u, parser.includes_.size()); } -TEST(MSVCHelperTest, DuplicatedHeaderPathConverted) { - CLWrapper cl; - string output; - cl.Run("cmd /c \"echo Note: including file: sub/foo.h&&" - "echo Note: including file: bar.h&&" - "echo Note: including file: sub\\foo.h\"", - &output); +TEST(CLParserTest, DuplicatedHeaderPathConverted) { + CLParser parser; + string output = parser.Parse( + "Note: including file: sub/foo.h\r\n" + "Note: including file: bar.h\r\n" + "Note: including file: sub\\foo.h\r\n"); // We should have dropped one copy of foo.h. ASSERT_EQ("", output); - ASSERT_EQ(2u, cl.includes_.size()); + ASSERT_EQ(2u, parser.includes_.size()); } -TEST(MSVCHelperTest, SpacesInFilename) { +TEST(CLParserTest, SpacesInFilename) { + ASSERT_EQ("sub\\some\\ sdk\\foo.h", + EscapeForDepfile("sub\\some sdk\\foo.h")); +} + +TEST(MSVCHelperTest, EnvBlock) { + char env_block[] = "foo=bar\0"; CLWrapper cl; + cl.SetEnvBlock(env_block); string output; - cl.Run("cmd /c \"echo Note: including file: sub\\some sdk\\foo.h", - &output); - ASSERT_EQ("", output); - vector headers = cl.GetEscapedResult(); - ASSERT_EQ(1u, headers.size()); - ASSERT_EQ("sub\\some\\ sdk\\foo.h", headers[0]); + cl.Run("cmd /c \"echo foo is %foo%", &output); + ASSERT_EQ("foo is bar\r\n", output); } -- cgit v0.12 From befa46192f97e98947e6d8772888e91e8a3bc629 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 6 Jan 2013 11:42:40 -0800 Subject: refactor build-time deps-extraction --- src/build.cc | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/build.h | 3 +++ 2 files changed, 53 insertions(+) diff --git a/src/build.cc b/src/build.cc index f44cd52..eea2d70 100644 --- a/src/build.cc +++ b/src/build.cc @@ -15,6 +15,7 @@ #include "build.h" #include +#include #include #include #include @@ -32,6 +33,7 @@ #endif #include "build_log.h" +#include "depfile_parser.h" #include "disk_interface.h" #include "graph.h" #include "state.h" @@ -723,6 +725,18 @@ bool Builder::Build(string* err) { } bool success = (result.status == ExitSuccess); + + if (success) { + vector deps_nodes; + string extract_err; + if (!ExtractDeps(&result, &deps_nodes, &extract_err)) { + if (!result.output.empty()) + result.output.append("\n"); + result.output.append(extract_err); + success = false; + } + } + --pending_commands; FinishEdge(result.edge, success, result.output); if (!success) { @@ -846,3 +860,39 @@ void Builder::FinishEdge(Edge* edge, bool success, const string& output) { scan_.build_log()->RecordCommand(edge, start_time, end_time, restat_mtime); } +bool Builder::ExtractDeps(CommandRunner::Result* result, + vector* deps_nodes, + string* err) { +#ifdef _WIN32 +#else + string depfile = result->edge->GetBinding("depfile"); + if (depfile.empty()) + return true; // No dependencies to load. + + string content = disk_interface_->ReadFile(depfile, err); + if (!err->empty()) + return false; + + DepfileParser deps; + if (!deps.Parse(&content, err)) + return false; + + // XXX check depfile matches expected output. + deps_nodes->reserve(deps.ins_.size()); + for (vector::iterator i = deps.ins_.begin(); + i != deps.ins_.end(); ++i) { + if (!CanonicalizePath(const_cast(i->str_), &i->len_, err)) + return false; + deps_nodes->push_back(state_->GetNode(*i)); + } + + /* TODO: unlink the file via diskinterface. + if (unlink(depfile.c_str()) < 0) { + *err = string("unlink: ")) + strerror(errno); + return false; + } + */ +#endif + + return deps_nodes; +} diff --git a/src/build.h b/src/build.h index 36bca57..e71549d 100644 --- a/src/build.h +++ b/src/build.h @@ -175,6 +175,9 @@ struct Builder { BuildStatus* status_; private: + bool ExtractDeps(CommandRunner::Result* result, vector* deps_nodes, + string* err); + DiskInterface* disk_interface_; DependencyScan scan_; -- cgit v0.12 From 1bda3330103dbc3b11c2043318da5fc66991a893 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 6 Jan 2013 15:21:07 -0800 Subject: windows: use CLParser to extract deps during build --- src/build.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/build.cc b/src/build.cc index eea2d70..f648584 100644 --- a/src/build.cc +++ b/src/build.cc @@ -36,6 +36,7 @@ #include "depfile_parser.h" #include "disk_interface.h" #include "graph.h" +#include "msvc_helper.h" #include "state.h" #include "subprocess.h" #include "util.h" @@ -864,6 +865,12 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, vector* deps_nodes, string* err) { #ifdef _WIN32 + CLParser parser; + result->output = parser.Parse(result->output); + for (set::iterator i = parser.includes_.begin(); + i != parser.includes_.end(); ++i) { + deps_nodes->push_back(state_->GetNode(*i)); + } #else string depfile = result->edge->GetBinding("depfile"); if (depfile.empty()) @@ -894,5 +901,5 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, */ #endif - return deps_nodes; + return true; } -- cgit v0.12 From ce65cb987adc7366f6da8f9d4e34c8cdab2d49bc Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 6 Jan 2013 15:26:20 -0800 Subject: use special=anything to trigger loading from depslog --- src/graph.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graph.cc b/src/graph.cc index 2f86785..bb99bcb 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -324,7 +324,7 @@ void Node::Dump(const char* prefix) const { bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { string special = edge->GetBinding("special"); - if (!special.empty() && special == "gcc") { + if (!special.empty()) { if (!LoadDepsFromLog(edge, err)) { if (!err->empty()) return false; -- cgit v0.12 From 58c7139b9f404e18097d4f3ef6adcd49a01e3d73 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 6 Jan 2013 15:33:46 -0800 Subject: windows: drop use of msvc helper in build --- bootstrap.py | 4 +--- configure.py | 12 +++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index fcf1a20..4d9bc84 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -143,9 +143,7 @@ Done! Note: to work around Windows file locking, where you can't rebuild an in-use binary, to run ninja after making any changes to build ninja itself -you should run ninja.bootstrap instead. Your build is also configured to -use ninja.bootstrap.exe as the MSVC helper; see the --with-ninja flag of -the --help output of configure.py.""") +you should run ninja.bootstrap instead.""") else: print('Building ninja using itself...') run([sys.executable, 'configure.py'] + conf_args) diff --git a/configure.py b/configure.py index 8f5a497..eddf248 100755 --- a/configure.py +++ b/configure.py @@ -47,9 +47,6 @@ 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('--with-ninja', metavar='NAME', - help="name for ninja binary for -t msvc (MSVC only)", - default="ninja") (options, args) = parser.parse_args() if args: print('ERROR: extra unparsed command-line arguments:', args) @@ -190,14 +187,11 @@ n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags)) n.newline() if platform == 'windows': - compiler = '$cxx' - if options.with_ninja: - compiler = ('%s -t msvc -o $out -- $cxx /showIncludes' % - options.with_ninja) n.rule('cxx', - command='%s $cflags -c $in /Fo$out' % compiler, + command='$cxx /showIncludes $cflags -c $in /Fo$out', depfile='$out.d', - description='CXX $out') + description='CXX $out', + special='msvc') else: n.rule('cxx', command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', -- cgit v0.12 From ab218230c4c6c3f0bb2a26215d1ac09e397e6065 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 7 Jan 2013 10:59:27 -0800 Subject: don't write out deps entries if nothing changed Shortcuts a common case. --- src/deps_log.cc | 32 ++++++++++++++++++++++++++++++-- src/deps_log_test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index da6cd93..0f10b5c 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -50,15 +50,43 @@ bool DepsLog::OpenForWrite(const string& path, string* err) { bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, const vector& nodes) { + // Track whether there's any new data to be recorded. + bool made_change = false; + // Assign ids to all nodes that are missing one. - if (node->id() < 0) + if (node->id() < 0) { RecordId(node); + made_change = true; + } for (vector::const_iterator i = nodes.begin(); i != nodes.end(); ++i) { - if ((*i)->id() < 0) + if ((*i)->id() < 0) { RecordId(*i); + made_change = true; + } + } + + // See if the new data is different than the existing data, if any. + if (!made_change) { + Deps* deps = GetDeps(node); + if (!deps || + deps->mtime != mtime || + deps->node_count != (int)nodes.size()) { + made_change = true; + } else { + for (int i = 0; i < (int)nodes.size(); ++i) { + if (deps->nodes[i] != nodes[i]) { + made_change = true; + break; + } + } + } } + // Don't write anything if there's no new info. + if (!made_change) + return true; + uint16_t size = 4 * (1 + 1 + (uint16_t)nodes.size()); size |= 0x8000; // Deps record: set high bit. fwrite(&size, 2, 1, file_); diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 3f47fef..e411e12 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -76,4 +76,50 @@ TEST_F(DepsLogTest, WriteRead) { ASSERT_EQ("bar.h", deps->nodes[1]->path()); } +// Verify that adding the same deps twice doesn't grow the file. +TEST_F(DepsLogTest, DoubleEntry) { + // Write some deps to the file and grab its size. + int file_size; + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + log.Close(); + + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + file_size = (int)st.st_size; + ASSERT_GT(file_size, 0); + } + + // Now reload the file, and readd the same deps. + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); + + EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + log.Close(); + + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + int file_size_2 = (int)st.st_size; + ASSERT_EQ(file_size, file_size_2); + } +} + } // anonymous namespace -- cgit v0.12 From afd206d99004d551afcfef55ec69ab65c4eb81d4 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 7 Jan 2013 11:04:24 -0800 Subject: record and check depslog file version Future-proofing against some change we may need to make later. --- src/deps_log.cc | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 0f10b5c..5706be4 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -24,6 +24,12 @@ #include "state.h" #include "util.h" +namespace { +const char kFileSignature[] = "# ninja deps v%d\n"; +const int kCurrentVersion = 1; +} // anonymous namespace + + bool DepsLog::OpenForWrite(const string& path, string* err) { file_ = fopen(path.c_str(), "ab"); if (!file_) { @@ -36,14 +42,12 @@ bool DepsLog::OpenForWrite(const string& path, string* err) { // end on Windows. Do that explicitly. fseek(file_, 0, SEEK_END); - /* XXX - if (ftell(log_file_) == 0) { - if (fprintf(log_file_, kFileSignature, kCurrentVersion) < 0) { + if (ftell(file_) == 0) { + if (fprintf(file_, kFileSignature, kCurrentVersion) < 0) { *err = strerror(errno); return false; } } - */ return true; } @@ -119,6 +123,22 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return false; } + if (!fgets(buf, sizeof(buf), f)) { + *err = strerror(errno); + return false; + } + int version = 0; + if (sscanf(buf, kFileSignature, &version) != 1) { + *err = "unable to read file signature"; + return false; + } + if (version != kCurrentVersion) { + *err = "bad deps log version; starting over"; + // Don't report this as a failure. An empty deps log will cause + // us to rebuild the outputs anyway. + return true; + } + for (;;) { uint16_t size; if (fread(&size, 2, 1, f) < 1) -- cgit v0.12 From e801e67a4cfae117098f252d365d67704eba4bfd Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 8 Jan 2013 08:17:12 -0800 Subject: make old deps format migration actually work --- src/deps_log.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 5706be4..081fcf0 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -128,12 +128,11 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return false; } int version = 0; - if (sscanf(buf, kFileSignature, &version) != 1) { - *err = "unable to read file signature"; - return false; - } + sscanf(buf, kFileSignature, &version); if (version != kCurrentVersion) { - *err = "bad deps log version; starting over"; + *err = "bad deps log signature or version; starting over"; + fclose(f); + unlink(path.c_str()); // Don't report this as a failure. An empty deps log will cause // us to rebuild the outputs anyway. return true; -- cgit v0.12 From e280115c03a296078b26da4b10a643cb1a6bda8a Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 8 Jan 2013 08:20:56 -0800 Subject: clarify depslog overview --- src/deps_log.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/deps_log.h b/src/deps_log.h index 5ddc7bd..56f9590 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -27,10 +27,10 @@ struct Node; struct State; /// As build commands run they can output extra dependency information -/// (e.g. header dependencies for C source) via a pipe. DepsLog collects -/// that information at build time and reloads it at startup. +/// (e.g. header dependencies for C source) dynamically. DepsLog collects +/// that information at build time and uses it for subsequent builds. /// -/// The on-disk format is based on two primary constraints: +/// The on-disk format is based on two primary design constraints: /// - it must be written to as a stream (during the build, which may be /// interrupted); /// - it can be read all at once on startup. (Alternative designs, where @@ -43,7 +43,8 @@ struct State; /// There's about 10k files worth of dependencies that reference about /// 40k total paths totalling 2mb of unique strings. /// -/// Based on these above, the file is structured as a sequence of records. +/// Based on these stats, here's the current design. +/// The file is structured as version header followed by a sequence of records. /// Each record is either a path string or a dependency list. /// Numbering the path strings in file order gives them dense integer ids. /// A dependency list maps an output id to a list of input ids. -- cgit v0.12 From ec92fe3da6e792f9e14a490675aebc132ec37ef6 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 8 Jan 2013 08:43:39 -0800 Subject: add recompaction to depslog Not done automatically yet, just an implementation and a test. --- src/deps_log.cc | 63 ++++++++++++++++++++++++++++++++++++++-------- src/deps_log.h | 4 +++ src/deps_log_test.cc | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 081fcf0..5031515 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -54,6 +54,11 @@ bool DepsLog::OpenForWrite(const string& path, string* err) { bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, const vector& nodes) { + return RecordDeps(node, mtime, nodes.size(), (Node**)&nodes.front()); +} + +bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, + int node_count, Node** nodes) { // Track whether there's any new data to be recorded. bool made_change = false; @@ -62,10 +67,9 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, RecordId(node); made_change = true; } - for (vector::const_iterator i = nodes.begin(); - i != nodes.end(); ++i) { - if ((*i)->id() < 0) { - RecordId(*i); + for (int i = 0; i < node_count; ++i) { + if (nodes[i]->id() < 0) { + RecordId(nodes[i]); made_change = true; } } @@ -75,10 +79,10 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, Deps* deps = GetDeps(node); if (!deps || deps->mtime != mtime || - deps->node_count != (int)nodes.size()) { + deps->node_count != node_count) { made_change = true; } else { - for (int i = 0; i < (int)nodes.size(); ++i) { + for (int i = 0; i < node_count; ++i) { if (deps->nodes[i] != nodes[i]) { made_change = true; break; @@ -91,16 +95,15 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, if (!made_change) return true; - uint16_t size = 4 * (1 + 1 + (uint16_t)nodes.size()); + uint16_t size = 4 * (1 + 1 + (uint16_t)node_count); size |= 0x8000; // Deps record: set high bit. fwrite(&size, 2, 1, file_); int id = node->id(); fwrite(&id, 4, 1, file_); int timestamp = mtime; fwrite(×tamp, 4, 1, file_); - for (vector::const_iterator i = nodes.begin(); - i != nodes.end(); ++i) { - id = (*i)->id(); + for (int i = 0; i < node_count; ++i) { + id = nodes[i]->id(); fwrite(&id, 4, 1, file_); } @@ -193,6 +196,46 @@ DepsLog::Deps* DepsLog::GetDeps(Node* node) { return deps_[node->id()]; } +bool DepsLog::Recompact(const string& path, string* err) { + METRIC_RECORD(".ninja_deps recompact"); + printf("Recompacting deps...\n"); + + string temp_path = path + ".recompact"; + DepsLog new_log; + if (!new_log.OpenForWrite(temp_path, err)) + return false; + + // Clear all known ids so that new ones can be reassigned. + for (vector::iterator i = nodes_.begin(); + i != nodes_.end(); ++i) { + (*i)->set_id(-1); + } + + // Write out all deps again. + for (int old_id = 0; old_id < (int)deps_.size(); ++old_id) { + Deps* deps = deps_[old_id]; + if (!new_log.RecordDeps(nodes_[old_id], deps->mtime, + deps->node_count, deps->nodes)) { + new_log.Close(); + return false; + } + } + + new_log.Close(); + + if (unlink(path.c_str()) < 0) { + *err = strerror(errno); + return false; + } + + if (rename(temp_path.c_str(), path.c_str()) < 0) { + *err = strerror(errno); + return false; + } + + return true; +} + bool DepsLog::RecordId(Node* node) { uint16_t size = (uint16_t)node->path().size(); fwrite(&size, 2, 1, file_); diff --git a/src/deps_log.h b/src/deps_log.h index 56f9590..d763a1d 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -65,6 +65,7 @@ struct DepsLog { // Writing (build-time) interface. bool OpenForWrite(const string& path, string* err); bool RecordDeps(Node* node, TimeStamp mtime, const vector& nodes); + bool RecordDeps(Node* node, TimeStamp mtime, int node_count, Node** nodes); void Close(); // Reading (startup-time) interface. @@ -78,6 +79,9 @@ struct DepsLog { bool Load(const string& path, State* state, string* err); Deps* GetDeps(Node* node); + /// Rewrite the known log entries, throwing away old data. + bool Recompact(const string& path, string* err); + /// Used for tests. const vector& nodes() const { return nodes_; } diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index e411e12..2d91c0e 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -122,4 +122,74 @@ TEST_F(DepsLogTest, DoubleEntry) { } } +// Verify that adding the new deps works and can be compacted away. +TEST_F(DepsLogTest, Recompact) { + // Write some deps to the file and grab its size. + int file_size; + { + State state; + DepsLog log; + string err; + ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + log.Close(); + + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + file_size = (int)st.st_size; + ASSERT_GT(file_size, 0); + } + + // Now reload the file, and add slighly different deps. + int file_size_2; + { + State state; + DepsLog log; + string err; + ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); + + ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + log.Close(); + + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + file_size_2 = (int)st.st_size; + // The file should grow to record the new deps. + ASSERT_GT(file_size_2, file_size); + } + + // Now reload the file, verify the new deps have replaced the old, then + // recompact. + { + State state; + DepsLog log; + string err; + ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); + + DepsLog::Deps* deps = log.GetDeps(state.GetNode("out.o")); + ASSERT_TRUE(deps); + ASSERT_EQ(1, deps->mtime); + ASSERT_EQ(1, deps->node_count); + ASSERT_EQ("foo.h", deps->nodes[0]->path()); + + ASSERT_TRUE(log.Recompact(kTestFilename, &err)); + + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + int file_size_3 = (int)st.st_size; + // The file should have shrunk a bit for the smaller deps. + ASSERT_LT(file_size_3, file_size_2); + } +} + } // anonymous namespace -- cgit v0.12 From e223be80b6d308d64674ec884df461fe5b8e42e6 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 8 Jan 2013 17:52:44 -0800 Subject: depslog: track dead record count --- src/deps_log.cc | 4 +++- src/deps_log.h | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 5031515..b1ec009 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -171,8 +171,10 @@ bool DepsLog::Load(const string& path, State* state, string* err) { if (out_id >= (int)deps_.size()) deps_.resize(out_id + 1); - if (deps_[out_id]) + if (deps_[out_id]) { + ++dead_record_count_; delete deps_[out_id]; + } deps_[out_id] = deps; } else { StringPiece path(buf, size); diff --git a/src/deps_log.h b/src/deps_log.h index d763a1d..e32a6fe 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -61,6 +61,7 @@ struct State; /// wins, allowing updates to just be appended to the file. A separate /// repacking step can run occasionally to remove dead records. struct DepsLog { + DepsLog() : dead_record_count_(0) {} // Writing (build-time) interface. bool OpenForWrite(const string& path, string* err); @@ -89,6 +90,10 @@ struct DepsLog { // Write a node name record, assigning it an id. bool RecordId(Node* node); + /// Number of deps record read while loading the file that ended up + /// being unused (due to a latter entry superceding it). + int dead_record_count_; + FILE* file_; /// Maps id -> Node. vector nodes_; -- cgit v0.12 From c3c1b3fda8b415e917b2762526c0c86e099b4300 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 14 Jan 2013 18:00:52 -0800 Subject: remove depfiles files as they're parsed --- src/build.cc | 8 ++++---- src/build_test.cc | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/build.cc b/src/build.cc index f648584..0d819b2 100644 --- a/src/build.cc +++ b/src/build.cc @@ -879,6 +879,8 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, string content = disk_interface_->ReadFile(depfile, err); if (!err->empty()) return false; + if (content.empty()) + return true; DepfileParser deps; if (!deps.Parse(&content, err)) @@ -893,12 +895,10 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, deps_nodes->push_back(state_->GetNode(*i)); } - /* TODO: unlink the file via diskinterface. - if (unlink(depfile.c_str()) < 0) { - *err = string("unlink: ")) + strerror(errno); + if (disk_interface_->RemoveFile(depfile) < 0) { + *err = string("deleting depfile: ") + strerror(errno); return false; } - */ #endif return true; diff --git a/src/build_test.cc b/src/build_test.cc index 3617439..bcd4d2e 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -762,6 +762,9 @@ TEST_F(BuildTest, OrderOnlyDeps) { fs_.Tick(); + // Recreate the depfile, as it should have been deleted by the build. + fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n"); + // implicit dep dirty, expect a rebuild. fs_.Create("blah.h", ""); fs_.Create("bar.h", ""); @@ -774,6 +777,9 @@ TEST_F(BuildTest, OrderOnlyDeps) { fs_.Tick(); + // Recreate the depfile, as it should have been deleted by the build. + fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n"); + // order only dep dirty, no rebuild. fs_.Create("otherfile", ""); command_runner_.commands_ran_.clear(); -- cgit v0.12 From 317ee87f86119556f4a3e4f0150d40c594f3581a Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 16 Feb 2013 14:34:49 -0800 Subject: missing header --- src/deps_log.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/deps_log.cc b/src/deps_log.cc index b1ec009..78eb23f 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include "graph.h" #include "metrics.h" -- cgit v0.12 From a8d7d8163a0e022c838a830e6c093ba428c10f24 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 12:09:54 -0800 Subject: rename "special" to "deps" --- configure.py | 2 +- misc/ninja_syntax.py | 6 +++--- src/graph.cc | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/configure.py b/configure.py index eddf248..eaf67c7 100755 --- a/configure.py +++ b/configure.py @@ -191,7 +191,7 @@ if platform == 'windows': command='$cxx /showIncludes $cflags -c $in /Fo$out', depfile='$out.d', description='CXX $out', - special='msvc') + deps='msvc') else: n.rule('cxx', command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index 519330e..d69e3e4 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -38,7 +38,7 @@ class Writer(object): def rule(self, name, command, description=None, depfile=None, generator=False, pool=None, restat=False, rspfile=None, - rspfile_content=None, special=None): + rspfile_content=None, deps=None): self._line('rule %s' % name) self.variable('command', command, indent=1) if description: @@ -55,8 +55,8 @@ class Writer(object): self.variable('rspfile', rspfile, indent=1) if rspfile_content: self.variable('rspfile_content', rspfile_content, indent=1) - if special: - self.variable('special', special, indent=1) + if deps: + self.variable('deps', deps, indent=1) def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, variables=None): diff --git a/src/graph.cc b/src/graph.cc index bb99bcb..b11973f 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -49,12 +49,12 @@ bool Rule::IsReservedBinding(const string& var) { return var == "command" || var == "depfile" || var == "description" || + var == "deps" || var == "generator" || var == "pool" || var == "restat" || var == "rspfile" || - var == "rspfile_content" || - var == "special"; + var == "rspfile_content"; } bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { @@ -323,8 +323,8 @@ void Node::Dump(const char* prefix) const { } bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { - string special = edge->GetBinding("special"); - if (!special.empty()) { + string deps_type = edge->GetBinding("deps"); + if (!deps_type.empty()) { if (!LoadDepsFromLog(edge, err)) { if (!err->empty()) return false; -- cgit v0.12 From 98d937903a4fe43e874a10b7e78c76cec8da48d8 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 12:53:40 -0800 Subject: hook up depslog writing into build process --- src/build.cc | 216 +++++++++++++++++++++++++++++++++-------------------------- src/build.h | 7 +- src/graph.h | 8 +++ 3 files changed, 134 insertions(+), 97 deletions(-) diff --git a/src/build.cc b/src/build.cc index 0d819b2..df208af 100644 --- a/src/build.cc +++ b/src/build.cc @@ -34,6 +34,7 @@ #include "build_log.h" #include "depfile_parser.h" +#include "deps_log.h" #include "disk_interface.h" #include "graph.h" #include "msvc_helper.h" @@ -705,10 +706,11 @@ bool Builder::Build(string* err) { return false; } - if (edge->is_phony()) - FinishEdge(edge, true, ""); - else + if (edge->is_phony()) { + plan_.EdgeFinished(edge); + } else { ++pending_commands; + } // We made some progress; go back to the main loop. continue; @@ -725,22 +727,10 @@ bool Builder::Build(string* err) { return false; } - bool success = (result.status == ExitSuccess); - - if (success) { - vector deps_nodes; - string extract_err; - if (!ExtractDeps(&result, &deps_nodes, &extract_err)) { - if (!result.output.empty()) - result.output.append("\n"); - result.output.append(extract_err); - success = false; - } - } - --pending_commands; - FinishEdge(result.edge, success, result.output); - if (!success) { + FinishCommand(&result); + + if (!result.success()) { if (failures_allowed) failures_allowed--; } @@ -801,105 +791,143 @@ bool Builder::StartEdge(Edge* edge, string* err) { return true; } -void Builder::FinishEdge(Edge* edge, bool success, const string& output) { - METRIC_RECORD("FinishEdge"); - TimeStamp restat_mtime = 0; +void Builder::FinishCommand(CommandRunner::Result* result) { + METRIC_RECORD("FinishCommand"); - if (success) { - if (edge->GetBindingBool("restat") && !config_.dry_run) { - bool node_cleaned = false; - - for (vector::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - TimeStamp new_mtime = disk_interface_->Stat((*i)->path()); - if ((*i)->mtime() == new_mtime) { - // The rule command did not change the output. Propagate the clean - // state through the build graph. - // Note that this also applies to nonexistent outputs (mtime == 0). - plan_.CleanNode(&scan_, *i); - node_cleaned = true; - } + Edge* edge = result->edge; + + // First try to extract dependencies from the result, if any. + // This must happen first as it filters the command output and + // can fail, which makes the command fail from a build perspective. + + vector deps_nodes; + if (result->success()) { + string deps_type = edge->GetBinding("deps"); + if (!deps_type.empty()) { + string extract_err; + if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { + if (!result->output.empty()) + result->output.append("\n"); + result->output.append(extract_err); + result->status = ExitFailure; } + } + } - if (node_cleaned) { - // If any output was cleaned, find the most recent mtime of any - // (existing) non-order-only input or the depfile. - for (vector::iterator i = edge->inputs_.begin(); - i != edge->inputs_.end() - edge->order_only_deps_; ++i) { - TimeStamp input_mtime = disk_interface_->Stat((*i)->path()); - if (input_mtime > restat_mtime) - restat_mtime = input_mtime; - } + int start_time, end_time; + status_->BuildEdgeFinished(edge, result->success(), result->output, + &start_time, &end_time); - string depfile = edge->GetBinding("depfile"); - if (restat_mtime != 0 && !depfile.empty()) { - TimeStamp depfile_mtime = disk_interface_->Stat(depfile); - if (depfile_mtime > restat_mtime) - restat_mtime = depfile_mtime; - } + // The rest of this function only applies to successful commands. + if (!result->success()) + return; - // The total number of edges in the plan may have changed as a result - // of a restat. - status_->PlanHasTotalEdges(plan_.command_edge_count()); + // Restat the edge outputs, if necessary. + TimeStamp restat_mtime = 0; + if (edge->GetBindingBool("restat") && !config_.dry_run) { + bool node_cleaned = false; + + for (vector::iterator i = edge->outputs_.begin(); + i != edge->outputs_.end(); ++i) { + TimeStamp new_mtime = disk_interface_->Stat((*i)->path()); + if ((*i)->mtime() == new_mtime) { + // The rule command did not change the output. Propagate the clean + // state through the build graph. + // Note that this also applies to nonexistent outputs (mtime == 0). + plan_.CleanNode(&scan_, *i); + node_cleaned = true; } } - // Delete the response file on success (if exists) - string rspfile = edge->GetBinding("rspfile"); - if (!rspfile.empty()) - disk_interface_->RemoveFile(rspfile); + if (node_cleaned) { + // If any output was cleaned, find the most recent mtime of any + // (existing) non-order-only input or the depfile. + for (vector::iterator i = edge->inputs_.begin(); + i != edge->inputs_.end() - edge->order_only_deps_; ++i) { + TimeStamp input_mtime = disk_interface_->Stat((*i)->path()); + if (input_mtime > restat_mtime) + restat_mtime = input_mtime; + } + + string depfile = edge->GetBinding("depfile"); + if (restat_mtime != 0 && !depfile.empty()) { + TimeStamp depfile_mtime = disk_interface_->Stat(depfile); + if (depfile_mtime > restat_mtime) + restat_mtime = depfile_mtime; + } - plan_.EdgeFinished(edge); + // The total number of edges in the plan may have changed as a result + // of a restat. + status_->PlanHasTotalEdges(plan_.command_edge_count()); + } } - if (edge->is_phony()) - return; + plan_.EdgeFinished(edge); + + // Delete any left over response file. + string rspfile = edge->GetBinding("rspfile"); + if (!rspfile.empty()) + disk_interface_->RemoveFile(rspfile); + + if (scan_.build_log()) { + scan_.build_log()->RecordCommand(edge, start_time, end_time, + restat_mtime); + } + + if (scan_.deps_log()) { + // XXX figure out multiple outputs. + Node* out = edge->outputs_[0]; + // XXX need to restat for restat_mtime. + scan_.deps_log()->RecordDeps(out, restat_mtime, deps_nodes); + } - int start_time, end_time; - status_->BuildEdgeFinished(edge, success, output, &start_time, &end_time); - if (success && scan_.build_log()) - scan_.build_log()->RecordCommand(edge, start_time, end_time, restat_mtime); } bool Builder::ExtractDeps(CommandRunner::Result* result, + const string& deps_type, vector* deps_nodes, string* err) { #ifdef _WIN32 - CLParser parser; - result->output = parser.Parse(result->output); - for (set::iterator i = parser.includes_.begin(); - i != parser.includes_.end(); ++i) { - deps_nodes->push_back(state_->GetNode(*i)); - } -#else - string depfile = result->edge->GetBinding("depfile"); - if (depfile.empty()) - return true; // No dependencies to load. - - string content = disk_interface_->ReadFile(depfile, err); - if (!err->empty()) - return false; - if (content.empty()) - return true; + if (deps_type == "msvc") { + CLParser parser; + result->output = parser.Parse(result->output); + for (set::iterator i = parser.includes_.begin(); + i != parser.includes_.end(); ++i) { + deps_nodes->push_back(state_->GetNode(*i)); + } + } else +#endif + if (deps_type == "gcc") { + string depfile = result->edge->GetBinding("depfile"); + if (depfile.empty()) + return true; // No dependencies to load. - DepfileParser deps; - if (!deps.Parse(&content, err)) - return false; + string content = disk_interface_->ReadFile(depfile, err); + if (!err->empty()) + return false; + if (content.empty()) + return true; - // XXX check depfile matches expected output. - deps_nodes->reserve(deps.ins_.size()); - for (vector::iterator i = deps.ins_.begin(); - i != deps.ins_.end(); ++i) { - if (!CanonicalizePath(const_cast(i->str_), &i->len_, err)) + DepfileParser deps; + if (!deps.Parse(&content, err)) return false; - deps_nodes->push_back(state_->GetNode(*i)); - } - if (disk_interface_->RemoveFile(depfile) < 0) { - *err = string("deleting depfile: ") + strerror(errno); - return false; + // XXX check depfile matches expected output. + deps_nodes->reserve(deps.ins_.size()); + for (vector::iterator i = deps.ins_.begin(); + i != deps.ins_.end(); ++i) { + if (!CanonicalizePath(const_cast(i->str_), &i->len_, err)) + return false; + deps_nodes->push_back(state_->GetNode(*i)); + } + + if (disk_interface_->RemoveFile(depfile) < 0) { + *err = string("deleting depfile: ") + strerror(errno); + return false; + } + } else { + Fatal("unknown deps type '%s'", deps_type.c_str()); } -#endif return true; } diff --git a/src/build.h b/src/build.h index e71549d..fb5fd10 100644 --- a/src/build.h +++ b/src/build.h @@ -110,6 +110,7 @@ struct CommandRunner { Edge* edge; ExitStatus status; string output; + bool success() const { return status == ExitSuccess; } }; /// Wait for a command to complete, or return false if interrupted. virtual bool WaitForCommand(Result* result) = 0; @@ -161,7 +162,7 @@ struct Builder { bool Build(string* err); bool StartEdge(Edge* edge, string* err); - void FinishEdge(Edge* edge, bool success, const string& output); + void FinishCommand(CommandRunner::Result* result); /// Used for tests. void SetBuildLog(BuildLog* log) { @@ -175,8 +176,8 @@ struct Builder { BuildStatus* status_; private: - bool ExtractDeps(CommandRunner::Result* result, vector* deps_nodes, - string* err); + bool ExtractDeps(CommandRunner::Result* result, const string& deps_type, + vector* deps_nodes, string* err); DiskInterface* disk_interface_; DependencyScan scan_; diff --git a/src/graph.h b/src/graph.h index a7b60db..650a83e 100644 --- a/src/graph.h +++ b/src/graph.h @@ -196,6 +196,10 @@ struct ImplicitDepLoader { /// @return false on error (without filling \a err if info is just missing). bool LoadDeps(Edge* edge, string* err); + DepsLog* deps_log() const { + return deps_log_; + } + private: /// Load implicit dependencies for \a edge from a depfile attribute. /// @return false on error (without filling \a err if info is just missing). @@ -247,6 +251,10 @@ struct DependencyScan { build_log_ = log; } + DepsLog* deps_log() const { + return dep_loader_.deps_log(); + } + private: BuildLog* build_log_; DiskInterface* disk_interface_; -- cgit v0.12 From 5031940a8fa5c0c7a76a4bb7fffdb4526713ce7c Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 13:19:53 -0800 Subject: don't call .front() on an empty vector Fixes a gcc debug-mode assertion. --- src/deps_log.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 78eb23f..454d2e5 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -55,7 +55,8 @@ bool DepsLog::OpenForWrite(const string& path, string* err) { bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, const vector& nodes) { - return RecordDeps(node, mtime, nodes.size(), (Node**)&nodes.front()); + return RecordDeps(node, mtime, nodes.size(), + nodes.empty() ? NULL : (Node**)&nodes.front()); } bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, -- cgit v0.12 From 149e8d8d5d2b043fa53f9f8b74fff893ea1819df Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sun, 17 Feb 2013 15:13:35 -0800 Subject: use logged deps mtime in dirty calculation The idea here is that it's possible for a build to complete (writing its output) but then for Ninja to get interrupted before writing out the updated dependency information. In that case the mtime stored in the deps log (if any) will match the previous output, and we'll know we need to rebuild the output just to get the deps updated. --- src/build.cc | 2 +- src/graph.cc | 20 +++++++++++++++----- src/graph.h | 8 +++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/build.cc b/src/build.cc index df208af..ecde4df 100644 --- a/src/build.cc +++ b/src/build.cc @@ -516,7 +516,7 @@ void Plan::CleanNode(DependencyScan* scan, Node* node) { if (!(*ni)->dirty()) continue; - if (scan->RecomputeOutputDirty(*ei, most_recent_input, + if (scan->RecomputeOutputDirty(*ei, most_recent_input, 0, command, *ni)) { (*ni)->MarkDirty(); all_outputs_clean = false; diff --git a/src/graph.cc b/src/graph.cc index b11973f..5cc491b 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -61,7 +61,8 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { bool dirty = false; edge->outputs_ready_ = true; - if (!dep_loader_.LoadDeps(edge, err)) { + TimeStamp deps_mtime = 0; + if (!dep_loader_.LoadDeps(edge, &deps_mtime, err)) { if (!err->empty()) return false; // Failed to load dependency info: rebuild to regenerate it. @@ -112,7 +113,8 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { for (vector::iterator i = edge->outputs_.begin(); i != edge->outputs_.end(); ++i) { (*i)->StatIfNecessary(disk_interface_); - if (RecomputeOutputDirty(edge, most_recent_input, command, *i)) { + if (RecomputeOutputDirty(edge, most_recent_input, deps_mtime, + command, *i)) { dirty = true; break; } @@ -141,6 +143,7 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { bool DependencyScan::RecomputeOutputDirty(Edge* edge, Node* most_recent_input, + TimeStamp deps_mtime, const string& command, Node* output) { if (edge->is_phony()) { @@ -182,6 +185,12 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge, } } + // Dirty if the output is newer than the deps. + if (deps_mtime && output->mtime() > deps_mtime) { + EXPLAIN("stored deps info out of date for for %s", output->path().c_str()); + return true; + } + // May also be dirty due to the command changing since the last build. // But if this is a generator rule, the command changing does not make us // dirty. @@ -322,10 +331,10 @@ void Node::Dump(const char* prefix) const { } } -bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { +bool ImplicitDepLoader::LoadDeps(Edge* edge, TimeStamp* mtime, string* err) { string deps_type = edge->GetBinding("deps"); if (!deps_type.empty()) { - if (!LoadDepsFromLog(edge, err)) { + if (!LoadDepsFromLog(edge, mtime, err)) { if (!err->empty()) return false; EXPLAIN("deps for %s are missing", edge->outputs_[0]->path().c_str()); @@ -396,7 +405,8 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, return true; } -bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) { +bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, TimeStamp* deps_mtime, + string* err) { DepsLog::Deps* deps = deps_log_->GetDeps(edge->outputs_[0]); if (!deps) return false; diff --git a/src/graph.h b/src/graph.h index 650a83e..428ba01 100644 --- a/src/graph.h +++ b/src/graph.h @@ -192,9 +192,10 @@ struct ImplicitDepLoader { DiskInterface* disk_interface) : state_(state), disk_interface_(disk_interface), deps_log_(deps_log) {} - /// Load implicit dependencies for \a edge. + /// Load implicit dependencies for \a edge. May fill in \a mtime with + /// the timestamp of the loaded information. /// @return false on error (without filling \a err if info is just missing). - bool LoadDeps(Edge* edge, string* err); + bool LoadDeps(Edge* edge, TimeStamp* mtime, string* err); DepsLog* deps_log() const { return deps_log_; @@ -207,7 +208,7 @@ struct ImplicitDepLoader { /// Load implicit dependencies for \a edge from the DepsLog. /// @return false on error (without filling \a err if info is just missing). - bool LoadDepsFromLog(Edge* edge, string* err); + bool LoadDepsFromLog(Edge* edge, TimeStamp* mtime, string* err); /// Preallocate \a count spaces in the input array on \a edge, returning /// an iterator pointing at the first new space. @@ -242,6 +243,7 @@ struct DependencyScan { /// Recompute whether a given single output should be marked dirty. /// Returns true if so. bool RecomputeOutputDirty(Edge* edge, Node* most_recent_input, + TimeStamp deps_mtime, const string& command, Node* output); BuildLog* build_log() const { -- cgit v0.12 From c47f2cedbb8f44182a72343d0c71f5af5196d274 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 19 Feb 2013 10:00:05 -0800 Subject: only write deps log if edge is in deps log mode --- src/build.cc | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/build.cc b/src/build.cc index ecde4df..56f2d9b 100644 --- a/src/build.cc +++ b/src/build.cc @@ -801,16 +801,14 @@ void Builder::FinishCommand(CommandRunner::Result* result) { // can fail, which makes the command fail from a build perspective. vector deps_nodes; - if (result->success()) { - string deps_type = edge->GetBinding("deps"); - if (!deps_type.empty()) { - string extract_err; - if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { - if (!result->output.empty()) - result->output.append("\n"); - result->output.append(extract_err); - result->status = ExitFailure; - } + string deps_type = edge->GetBinding("deps"); + if (result->success() && !deps_type.empty()) { + string extract_err; + if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { + if (!result->output.empty()) + result->output.append("\n"); + result->output.append(extract_err); + result->status = ExitFailure; } } @@ -874,7 +872,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { restat_mtime); } - if (scan_.deps_log()) { + if (!deps_type.empty() && scan_.deps_log()) { // XXX figure out multiple outputs. Node* out = edge->outputs_[0]; // XXX need to restat for restat_mtime. -- cgit v0.12 From adc4ee81443dbfae8584456c04ea1ba383da3d01 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 5 Apr 2013 10:27:22 -0700 Subject: make it an error for now to have multiple outputs with depslog --- src/build.cc | 2 +- src/manifest_parser.cc | 8 ++++++++ src/manifest_parser_test.cc | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/build.cc b/src/build.cc index 56f2d9b..0caf6e7 100644 --- a/src/build.cc +++ b/src/build.cc @@ -873,7 +873,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { } if (!deps_type.empty() && scan_.deps_log()) { - // XXX figure out multiple outputs. + assert(edge->outputs_.size() == 1); Node* out = edge->outputs_[0]; // XXX need to restat for restat_mtime. scan_.deps_log()->RecordDeps(out, restat_mtime, deps_nodes); diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index 14fca73..a581114 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -329,6 +329,14 @@ bool ManifestParser::ParseEdge(string* err) { edge->implicit_deps_ = implicit; edge->order_only_deps_ = order_only; + // Multiple outputs aren't (yet?) supported with depslog. + string deps_type = edge->GetBinding("deps"); + if (!deps_type.empty() && edge->outputs_.size() > 1) { + return lexer_.Error("multiple outputs aren't (yet?) supported by depslog; " + "bring this up on the mailing list if it affects you", + err); + } + return true; } diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 4ac093f..76d235d 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -71,6 +71,7 @@ TEST_F(ParserTest, RuleAttributes) { "rule cat\n" " command = a\n" " depfile = a\n" +" deps = a\n" " description = a\n" " generator = a\n" " restat = a\n" @@ -599,6 +600,17 @@ TEST_F(ParserTest, MultipleOutputs) { EXPECT_EQ("", err); } +TEST_F(ParserTest, MultipleOutputsWithDeps) { + State state; + ManifestParser parser(&state, NULL); + string err; + EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n deps = gcc\n" + "build a.o b.o: cc c.cc\n", + &err)); + EXPECT_EQ("input:5: multiple outputs aren't (yet?) supported by depslog; " + "bring this up on the mailing list if it affects you\n", err); +} + TEST_F(ParserTest, SubNinja) { files_["test.ninja"] = "var = inner\n" -- cgit v0.12 From 8ec425abe38f468bc4bbb4c95d78fab3b93d2141 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 5 Apr 2013 14:08:41 -0700 Subject: add a test verifying build failure on bad deps --- src/build_test.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/build_test.cc b/src/build_test.cc index bcd4d2e..1907197 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1352,3 +1352,20 @@ TEST_F(BuildTest, StatusFormatReplacePlaceholder) { status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]")); } +TEST_F(BuildTest, FailedDepsParse) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build bad_deps.o: cat in1\n" +" deps = gcc\n" +" depfile = in1.d\n")); + + string err; + EXPECT_TRUE(builder_.AddTarget("bad_deps.o", &err)); + ASSERT_EQ("", err); + + // These deps will fail to parse, as they should only have one + // path to the left of the colon. + fs_.Create("in1.d", "XXX YYY"); + + EXPECT_FALSE(builder_.Build(&err)); + EXPECT_EQ("subcommand failed", err); +} -- cgit v0.12 From 94f999b681ea4ced1cc27b29e0db77d72554ecf9 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 18:02:05 -0700 Subject: Make gtest output more silent, ninja issue #528. This is just a proof-of-concept. The terminal printing logic should be extracted from src/build.cc and then reused here. --- configure.py | 4 +--- src/ninja_test.cc | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/ninja_test.cc diff --git a/configure.py b/configure.py index 10c6994..3ea91f0 100755 --- a/configure.py +++ b/configure.py @@ -331,9 +331,6 @@ if options.with_gtest: 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' + objext), 'cxx', - os.path.join(path, 'src', 'gtest_main.cc'), - variables=[('cflags', gtest_cflags)]) test_cflags.append('-I%s' % os.path.join(path, 'include')) else: @@ -353,6 +350,7 @@ for name in ['build_log_test', 'graph_test', 'lexer_test', 'manifest_parser_test', + 'ninja_test', 'state_test', 'subprocess_test', 'test', diff --git a/src/ninja_test.cc b/src/ninja_test.cc new file mode 100644 index 0000000..0e0f882 --- /dev/null +++ b/src/ninja_test.cc @@ -0,0 +1,54 @@ +#include "gtest/gtest.h" + +/// A test result printer that's less wordy than gtest's default. +class LaconicPrinter : public testing::EmptyTestEventListener { + public: + LaconicPrinter() : have_blank_line_(false), smart_terminal_(true) {} + + virtual void OnTestStart(const testing::TestInfo& test_info) { + printf("\r%s.%s starting.", test_info.test_case_name(), test_info.name()); + printf("\x1B[K"); // Clear to end of line. + fflush(stdout); + have_blank_line_ = false; + } + + virtual void OnTestPartResult( + const testing::TestPartResult& test_part_result) { + if (!test_part_result.failed()) + return; + if (!have_blank_line_ && smart_terminal_) + printf("\n"); + printf("*** Failure in %s:%d\n%s\n", + test_part_result.file_name(), + test_part_result.line_number(), + test_part_result.summary()); + have_blank_line_ = true; + } + + virtual void OnTestEnd(const testing::TestInfo& test_info) { + printf("\r%s.%s ending.", test_info.test_case_name(), test_info.name()); + printf("\x1B[K"); // Clear to end of line. + fflush(stdout); + have_blank_line_ = false; + } + + virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { + if (!have_blank_line_ && smart_terminal_) + printf("\n"); + } + + private: + bool have_blank_line_; + bool smart_terminal_; +}; + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + delete listeners.Release(listeners.default_result_printer()); + listeners.Append(new LaconicPrinter); + + return RUN_ALL_TESTS(); +} -- cgit v0.12 From 0397155218f3d311200ec4e25786028f14c53c6a Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 8 Apr 2013 10:20:58 -0700 Subject: add a test for the "deps out of date" case It touched the various remaining XXXes in the code, hooray. --- src/build.cc | 8 +++-- src/build_test.cc | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/deps_log.cc | 6 +++- src/deps_log.h | 3 +- src/graph.cc | 2 +- src/test.cc | 6 +++- src/test.h | 6 ++++ 7 files changed, 120 insertions(+), 7 deletions(-) diff --git a/src/build.cc b/src/build.cc index 0caf6e7..2d3d9b4 100644 --- a/src/build.cc +++ b/src/build.cc @@ -872,11 +872,13 @@ void Builder::FinishCommand(CommandRunner::Result* result) { restat_mtime); } - if (!deps_type.empty() && scan_.deps_log()) { + if (!deps_type.empty()) { assert(edge->outputs_.size() == 1); Node* out = edge->outputs_[0]; - // XXX need to restat for restat_mtime. - scan_.deps_log()->RecordDeps(out, restat_mtime, deps_nodes); + TimeStamp mtime = disk_interface_->Stat(out->path()); + // XXX we could reuse the restat logic to avoid a second stat, + // but in practice we only care about the single output. + scan_.deps_log()->RecordDeps(out, mtime, deps_nodes); } } diff --git a/src/build_test.cc b/src/build_test.cc index 1907197..a227854 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -15,6 +15,7 @@ #include "build.h" #include "build_log.h" +#include "deps_log.h" #include "graph.h" #include "test.h" @@ -396,6 +397,11 @@ struct BuildTest : public StateTestWithBuiltinRules { BuildTest() : config_(MakeConfig()), command_runner_(&fs_), builder_(&state_, config_, NULL, NULL, &fs_), status_(config_) { + } + + virtual void SetUp() { + StateTestWithBuiltinRules::SetUp(); + builder_.command_runner_.reset(&command_runner_); AssertParse(&state_, "build cat1: cat in1\n" @@ -1369,3 +1375,93 @@ TEST_F(BuildTest, FailedDepsParse) { EXPECT_FALSE(builder_.Build(&err)); EXPECT_EQ("subcommand failed", err); } + +/// Tests of builds involving deps logs necessarily must span +/// multiple builds. We reuse methods on BuildTest but not the +/// builder_ it sets up, because we want pristine objects for +/// each build. +struct BuildWithDepsLogTest : public BuildTest { + BuildWithDepsLogTest() {} + + virtual void SetUp() { + BuildTest::SetUp(); + + temp_dir_.CreateAndEnter("BuildWithDepsLogTest"); + } + + virtual void TearDown() { + temp_dir_.Cleanup(); + } + + ScopedTempDir temp_dir_; + + /// Shadow parent class builder_ so we don't accidentally use it. + void* builder_; +}; + +TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { + // Don't make use of the class's built-in Builder etc. so that we + // can construct objects with the DepsLog in place. + + string err; + // Note: in1 was created by the superclass SetUp(). + const char* manifest = + "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: in2"); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + // The deps file should have been removed. + EXPECT_EQ(0, fs_.Stat("in1.d")); + deps_log.Close(); + builder.command_runner_.release(); + } + + { + State state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // Pretend that the build aborted before the deps were + // removed, leaving behind an obsolete .d file, but after + // the output was written. + fs_.Create("in1.d", "XXX"); + fs_.Tick(); + fs_.Create("out", ""); + + // 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); + + // We should have rebuilt the output due to the deps being + // out of date. + EXPECT_EQ(1u, command_runner_.commands_ran_.size()); + + builder.command_runner_.release(); + } +} diff --git a/src/deps_log.cc b/src/deps_log.cc index 454d2e5..5590a32 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -30,6 +30,9 @@ const char kFileSignature[] = "# ninja deps v%d\n"; const int kCurrentVersion = 1; } // anonymous namespace +DepsLog::~DepsLog() { + Close(); +} bool DepsLog::OpenForWrite(const string& path, string* err) { file_ = fopen(path.c_str(), "ab"); @@ -113,7 +116,8 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, } void DepsLog::Close() { - fclose(file_); + if (file_) + fclose(file_); file_ = NULL; } diff --git a/src/deps_log.h b/src/deps_log.h index e32a6fe..99b006e 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -61,7 +61,8 @@ struct State; /// wins, allowing updates to just be appended to the file. A separate /// repacking step can run occasionally to remove dead records. struct DepsLog { - DepsLog() : dead_record_count_(0) {} + DepsLog() : dead_record_count_(0), file_(NULL) {} + ~DepsLog(); // Writing (build-time) interface. bool OpenForWrite(const string& path, string* err); diff --git a/src/graph.cc b/src/graph.cc index 5cc491b..2614882 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -411,7 +411,7 @@ bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, TimeStamp* deps_mtime, if (!deps) return false; - // XXX mtime + *deps_mtime = deps->mtime; vector::iterator implicit_dep = PreallocateSpace(edge, deps->node_count); diff --git a/src/test.cc b/src/test.cc index c48ca82..45a9226 100644 --- a/src/test.cc +++ b/src/test.cc @@ -74,7 +74,11 @@ string GetSystemTempDir() { } // anonymous namespace StateTestWithBuiltinRules::StateTestWithBuiltinRules() { - AssertParse(&state_, + AddCatRule(&state_); +} + +void StateTestWithBuiltinRules::AddCatRule(State* state) { + AssertParse(state, "rule cat\n" " command = cat $in > $out\n"); } diff --git a/src/test.h b/src/test.h index bff0e5a..9f29e07 100644 --- a/src/test.h +++ b/src/test.h @@ -29,6 +29,12 @@ struct Node; /// builtin "cat" rule. struct StateTestWithBuiltinRules : public testing::Test { StateTestWithBuiltinRules(); + + /// Add a "cat" rule to \a state. Used by some tests; it's + /// otherwise done by the ctor to state_. + void AddCatRule(State* state); + + /// Short way to get a Node by its path from state_. Node* GetNode(const string& path); State state_; -- cgit v0.12 From ba1642091f67f3e71da1fe2768dd54146beb9c63 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:08:19 -0700 Subject: Make deps=gcc without depfile an error. When I first played with depslog, I accidentally removed the depfile attribute on my cc edges, which had the effect of ninja silently ignoring all depfiles. Instead, let ninja complain about edges that have deps set to gcc and depfile set to nothing. This is done at edge build time, instead of at mainfest load time, because adding this check to ParseEdge() regressed empty build time for target 'chrome' by 30ms. The check is only useful for generator authors, regular users should never see this. --- src/build.cc | 10 ++++++---- src/build_test.cc | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/build.cc b/src/build.cc index 2d3d9b4..5e874df 100644 --- a/src/build.cc +++ b/src/build.cc @@ -873,7 +873,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { } if (!deps_type.empty()) { - assert(edge->outputs_.size() == 1); + assert(edge->outputs_.size() == 1 && "should have been rejected by parser"); Node* out = edge->outputs_[0]; TimeStamp mtime = disk_interface_->Stat(out->path()); // XXX we could reuse the restat logic to avoid a second stat, @@ -899,8 +899,10 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, #endif if (deps_type == "gcc") { string depfile = result->edge->GetBinding("depfile"); - if (depfile.empty()) - return true; // No dependencies to load. + if (depfile.empty()) { + *err = string("edge with deps=gcc but no depfile makes no sense\n"); + return false; + } string content = disk_interface_->ReadFile(depfile, err); if (!err->empty()) @@ -922,7 +924,7 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, } if (disk_interface_->RemoveFile(depfile) < 0) { - *err = string("deleting depfile: ") + strerror(errno); + *err = string("deleting depfile: ") + strerror(errno) + string("\n"); return false; } } else { diff --git a/src/build_test.cc b/src/build_test.cc index a227854..7df742f 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1353,6 +1353,24 @@ TEST_F(BuildTest, PhonyWithNoInputs) { ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } +TEST_F(BuildTest, DepsGccWithEmptyDeps) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cc\n" +" command = cc\n" +" deps = gcc\n" +"build out: cc\n")); + Dirty("out"); + + string err; + EXPECT_TRUE(builder_.AddTarget("out", &err)); + ASSERT_EQ("", err); + EXPECT_FALSE(builder_.AlreadyUpToDate()); + + EXPECT_FALSE(builder_.Build(&err)); + ASSERT_EQ("subcommand failed", err); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); +} + TEST_F(BuildTest, StatusFormatReplacePlaceholder) { EXPECT_EQ("[%/s0/t0/r0/u0/f0]", status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]")); -- cgit v0.12 From 10d1c939ae788bfb410eb37c7e71da2be07797f9 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 20:54:28 -0700 Subject: move single-line printing to new class --- configure.py | 1 + src/build.cc | 97 +++++----------------------------------------------- src/build.h | 7 ++-- src/line_printer.cc | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/line_printer.h | 37 ++++++++++++++++++++ src/ninja_test.cc | 14 ++++++++ 6 files changed, 162 insertions(+), 92 deletions(-) create mode 100644 src/line_printer.cc create mode 100644 src/line_printer.h diff --git a/configure.py b/configure.py index 3ea91f0..6db181c 100755 --- a/configure.py +++ b/configure.py @@ -276,6 +276,7 @@ for name in ['build', 'graph', 'graphviz', 'lexer', + 'line_printer', 'manifest_parser', 'metrics', 'state', diff --git a/src/build.cc b/src/build.cc index ae47a50..a98dfda 100644 --- a/src/build.cc +++ b/src/build.cc @@ -19,14 +19,6 @@ #include #include -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif - #if defined(__SVR4) && defined(__sun) #include #endif @@ -80,25 +72,12 @@ BuildStatus::BuildStatus(const BuildConfig& config) : config_(config), start_time_millis_(GetTimeMillis()), started_edges_(0), finished_edges_(0), total_edges_(0), - have_blank_line_(true), progress_status_format_(NULL), + progress_status_format_(NULL), overall_rate_(), current_rate_(config.parallelism) { -#ifndef _WIN32 - const char* term = getenv("TERM"); - smart_terminal_ = isatty(1) && term && string(term) != "dumb"; -#else - // 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. if (config_.verbosity != BuildConfig::NORMAL) - smart_terminal_ = false; + printer_.smart_terminal_ = false; progress_status_format_ = getenv("NINJA_STATUS"); if (!progress_status_format_) @@ -133,11 +112,11 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (config_.verbosity == BuildConfig::QUIET) return; - if (smart_terminal_) + if (printer_.smart_terminal_) PrintStatus(edge); if (!success || !output.empty()) { - if (smart_terminal_) + if (printer_.smart_terminal_) printf("\n"); // Print the command that is spewing before printing its output. @@ -157,7 +136,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, // thousands of parallel compile commands.) // TODO: There should be a flag to disable escape code stripping. string final_output; - if (!smart_terminal_) + if (!printer_.smart_terminal_) final_output = StripAnsiEscapeCodes(output); else final_output = output; @@ -165,12 +144,12 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (!final_output.empty()) printf("%s", final_output.c_str()); - have_blank_line_ = true; + printer_.have_blank_line_ = true; } } void BuildStatus::BuildFinished() { - if (smart_terminal_ && !have_blank_line_) + if (printer_.smart_terminal_ && !printer_.have_blank_line_) printf("\n"); } @@ -267,72 +246,14 @@ void BuildStatus::PrintStatus(Edge* edge) { if (to_print.empty() || force_full_command) to_print = edge->GetBinding("command"); -#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 - } - if (finished_edges_ == 0) { overall_rate_.Restart(); current_rate_.Restart(); } to_print = FormatProgressStatus(progress_status_format_) + to_print; - 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; - if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) { - to_print = ElideMiddle(to_print, size.ws_col); - } -#else - // Don't use the full width or console will move to next line. - size_t width = static_cast(csbi.dwSize.X) - 1; - to_print = ElideMiddle(to_print, width); -#endif - } - - if (smart_terminal_ && !force_full_command) { -#ifndef _WIN32 - printf("%s", to_print.c_str()); - printf("\x1B[K"); // Clear to end of line. - fflush(stdout); - have_blank_line_ = false; -#else - // We don't want to have the cursor spamming back and forth, so - // use WriteConsoleOutput instead which updates the contents of - // the buffer, but doesn't move the cursor position. - GetConsoleScreenBufferInfo(console_, &csbi); - COORD buf_size = { csbi.dwSize.X, 1 }; - COORD zero_zero = { 0, 0 }; - SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, - (SHORT)(csbi.dwCursorPosition.X + csbi.dwSize.X - 1), - csbi.dwCursorPosition.Y }; - CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X]; - memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X); - for (int i = 0; i < csbi.dwSize.X; ++i) { - char_data[i].Char.AsciiChar = ' '; - char_data[i].Attributes = csbi.wAttributes; - } - for (size_t i = 0; i < to_print.size(); ++i) - char_data[i].Char.AsciiChar = to_print[i]; - WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target); - delete[] char_data; - have_blank_line_ = false; -#endif - } else { - printf("%s\n", to_print.c_str()); - } + printer_.Print(to_print, + force_full_command ? LinePrinter::FULL : LinePrinter::SHORT); } Plan::Plan() : command_edges_(0), wanted_edges_(0) {} diff --git a/src/build.h b/src/build.h index 5747170..52c277a 100644 --- a/src/build.h +++ b/src/build.h @@ -25,6 +25,7 @@ #include "graph.h" // XXX needed for DependencyScan; should rearrange. #include "exit_status.h" +#include "line_printer.h" #include "metrics.h" #include "util.h" // int64_t @@ -198,14 +199,12 @@ struct BuildStatus { int started_edges_, finished_edges_, total_edges_; - bool have_blank_line_; - /// Map of running edge to time the edge started running. typedef map RunningEdgeMap; RunningEdgeMap running_edges_; - /// Whether we can do fancy terminal control codes. - bool smart_terminal_; + /// Prints progress output. + LinePrinter printer_; /// The custom progress status format to use. const char* progress_status_format_; diff --git a/src/line_printer.cc b/src/line_printer.cc new file mode 100644 index 0000000..54f0a5b --- /dev/null +++ b/src/line_printer.cc @@ -0,0 +1,98 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "line_printer.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include "util.h" + +LinePrinter::LinePrinter() : have_blank_line_(true) { +#ifndef _WIN32 + const char* term = getenv("TERM"); + smart_terminal_ = isatty(1) && term && string(term) != "dumb"; +#else + // 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 +} + +void LinePrinter::Print(std::string to_print, LineType type) { +#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 + } + + if (smart_terminal_ && type == SHORT) { +#ifdef _WIN32 + // Don't use the full width or console will move to next line. + size_t width = static_cast(csbi.dwSize.X) - 1; + to_print = ElideMiddle(to_print, width); + // We don't want to have the cursor spamming back and forth, so + // use WriteConsoleOutput instead which updates the contents of + // the buffer, but doesn't move the cursor position. + GetConsoleScreenBufferInfo(console_, &csbi); + COORD buf_size = { csbi.dwSize.X, 1 }; + COORD zero_zero = { 0, 0 }; + SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, + (SHORT)(csbi.dwCursorPosition.X + csbi.dwSize.X - 1), + csbi.dwCursorPosition.Y }; + CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X]; + memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X); + for (int i = 0; i < csbi.dwSize.X; ++i) { + char_data[i].Char.AsciiChar = ' '; + char_data[i].Attributes = csbi.wAttributes; + } + for (size_t i = 0; i < to_print.size(); ++i) + char_data[i].Char.AsciiChar = to_print[i]; + WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target); + delete[] char_data; +#else + // Limit output to width of the terminal if provided so we don't cause + // line-wrapping. + winsize size; + if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) { + to_print = ElideMiddle(to_print, size.ws_col); + } + printf("%s", to_print.c_str()); + printf("\x1B[K"); // Clear to end of line. + fflush(stdout); +#endif + + have_blank_line_ = false; + } else { + printf("%s\n", to_print.c_str()); + } +} diff --git a/src/line_printer.h b/src/line_printer.h new file mode 100644 index 0000000..6002667 --- /dev/null +++ b/src/line_printer.h @@ -0,0 +1,37 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NINJA_LINE_PRINTER_H_ +#define NINJA_LINE_PRINTER_H_ + +#include + +class LinePrinter { + public: + LinePrinter(); + + enum LineType { + FULL, + SHORT + }; + void Print(std::string to_print, LineType type); + + //private: + /// Whether we can do fancy terminal control codes. + bool smart_terminal_; + + bool have_blank_line_; +}; + +#endif // NINJA_LINE_PRINTER_H_ diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 0e0f882..f257ee3 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -1,3 +1,17 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "gtest/gtest.h" /// A test result printer that's less wordy than gtest's default. -- cgit v0.12 From 7920d7032a8833004fc281fb5b090787888d0fd0 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:11:34 -0700 Subject: refactor; no intended functionality change --- src/build.cc | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/build.cc b/src/build.cc index a98dfda..efa4559 100644 --- a/src/build.cc +++ b/src/build.cc @@ -115,13 +115,18 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (printer_.smart_terminal_) PrintStatus(edge); - if (!success || !output.empty()) { - if (printer_.smart_terminal_) + if (!success) { + if (!printer_.have_blank_line_) printf("\n"); // Print the command that is spewing before printing its output. - if (!success) - printf("FAILED: %s\n", edge->EvaluateCommand().c_str()); + printf("FAILED: %s\n", edge->EvaluateCommand().c_str()); + printer_.have_blank_line_ = true; + } + + if (!output.empty()) { + if (!printer_.have_blank_line_) + printf("\n"); // ninja sets stdout and stderr of subprocesses to a pipe, to be able to // check if the output is empty. Some compilers, e.g. clang, check @@ -141,10 +146,11 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, else final_output = output; - if (!final_output.empty()) + if (!final_output.empty()) { printf("%s", final_output.c_str()); - - printer_.have_blank_line_ = true; + if (*final_output.rbegin() == '\n') + printer_.have_blank_line_ = true; + } } } -- cgit v0.12 From c78e1eea9a385340a1d47d5402f5a7c41f99c95d Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 17:18:30 -0700 Subject: Write the depslog version in binary instead of text. This way, it doubles as a byte-order marker. The header is now exactly one line in a hex editor, and it's still relatively easy to look at the version in a text editor. --- src/deps_log.cc | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 5590a32..8946e32 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -25,10 +25,10 @@ #include "state.h" #include "util.h" -namespace { -const char kFileSignature[] = "# ninja deps v%d\n"; +// The version is stored as 4 bytes after the signature and also serves as a +// byte order mark. Signature and version combined are 16 bytes long. +const char kFileSignature[] = "# ninjadeps\n"; const int kCurrentVersion = 1; -} // anonymous namespace DepsLog::~DepsLog() { Close(); @@ -47,7 +47,11 @@ bool DepsLog::OpenForWrite(const string& path, string* err) { fseek(file_, 0, SEEK_END); if (ftell(file_) == 0) { - if (fprintf(file_, kFileSignature, kCurrentVersion) < 0) { + if (fwrite(kFileSignature, sizeof(kFileSignature) - 1, 1, file_) < 1) { + *err = strerror(errno); + return false; + } + if (fwrite(&kCurrentVersion, 4, 1, file_) < 1) { *err = strerror(errno); return false; } @@ -137,7 +141,10 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return false; } int version = 0; - sscanf(buf, kFileSignature, &version); + if (fread(&version, 4, 1, f) < 1) { + *err = strerror(errno); + return false; + } if (version != kCurrentVersion) { *err = "bad deps log signature or version; starting over"; fclose(f); -- cgit v0.12 From f872a912c57879767a7715094790aa4380badce2 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:14:29 -0700 Subject: move printing of new lines into LinePrinter. --- src/build.cc | 24 +++++------------------- src/line_printer.cc | 7 +++++++ src/line_printer.h | 2 ++ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/build.cc b/src/build.cc index efa4559..4ed586f 100644 --- a/src/build.cc +++ b/src/build.cc @@ -115,19 +115,11 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (printer_.smart_terminal_) PrintStatus(edge); - if (!success) { - if (!printer_.have_blank_line_) - printf("\n"); - - // Print the command that is spewing before printing its output. - printf("FAILED: %s\n", edge->EvaluateCommand().c_str()); - printer_.have_blank_line_ = true; - } + // Print the command that is spewing before printing its output. + if (!success) + printer_.PrintOnNewLine("FAILED: " + edge->EvaluateCommand() + "\n"); if (!output.empty()) { - if (!printer_.have_blank_line_) - printf("\n"); - // ninja sets stdout and stderr of subprocesses to a pipe, to be able to // check if the output is empty. Some compilers, e.g. clang, check // isatty(stderr) to decide if they should print colored output. @@ -145,18 +137,12 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, final_output = StripAnsiEscapeCodes(output); else final_output = output; - - if (!final_output.empty()) { - printf("%s", final_output.c_str()); - if (*final_output.rbegin() == '\n') - printer_.have_blank_line_ = true; - } + printer_.PrintOnNewLine(final_output); } } void BuildStatus::BuildFinished() { - if (printer_.smart_terminal_ && !printer_.have_blank_line_) - printf("\n"); + printer_.PrintOnNewLine(""); } string BuildStatus::FormatProgressStatus( diff --git a/src/line_printer.cc b/src/line_printer.cc index 54f0a5b..bfa737e 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -96,3 +96,10 @@ void LinePrinter::Print(std::string to_print, LineType type) { printf("%s\n", to_print.c_str()); } } + +void LinePrinter::PrintOnNewLine(const string& to_print) { + if (!have_blank_line_) + printf("\n"); + printf("%s", to_print.c_str()); + have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n'; +} diff --git a/src/line_printer.h b/src/line_printer.h index 6002667..b3cb163 100644 --- a/src/line_printer.h +++ b/src/line_printer.h @@ -27,6 +27,8 @@ class LinePrinter { }; void Print(std::string to_print, LineType type); + void PrintOnNewLine(const std::string& to_print); + //private: /// Whether we can do fancy terminal control codes. bool smart_terminal_; -- cgit v0.12 From a95b0819703aefc6a4f74004d6639f2cebd1e5d2 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:19:27 -0700 Subject: Make LinePrinter members private, add comments. --- src/build.cc | 6 +++--- src/line_printer.h | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/build.cc b/src/build.cc index 4ed586f..340a3d9 100644 --- a/src/build.cc +++ b/src/build.cc @@ -77,7 +77,7 @@ BuildStatus::BuildStatus(const BuildConfig& config) // Don't do anything fancy in verbose mode. if (config_.verbosity != BuildConfig::NORMAL) - printer_.smart_terminal_ = false; + printer_.set_smart_terminal(false); progress_status_format_ = getenv("NINJA_STATUS"); if (!progress_status_format_) @@ -112,7 +112,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (config_.verbosity == BuildConfig::QUIET) return; - if (printer_.smart_terminal_) + if (printer_.is_smart_terminal()) PrintStatus(edge); // Print the command that is spewing before printing its output. @@ -133,7 +133,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, // thousands of parallel compile commands.) // TODO: There should be a flag to disable escape code stripping. string final_output; - if (!printer_.smart_terminal_) + if (!printer_.is_smart_terminal()) final_output = StripAnsiEscapeCodes(output); else final_output = output; diff --git a/src/line_printer.h b/src/line_printer.h index b3cb163..78510ea 100644 --- a/src/line_printer.h +++ b/src/line_printer.h @@ -17,22 +17,31 @@ #include +/// Prints lines of text, possibly overprinting previously printed lines +/// if the terminal supports it. class LinePrinter { public: LinePrinter(); + bool is_smart_terminal() const { return smart_terminal_; } + void set_smart_terminal(bool smart) { smart_terminal_ = smart; } + enum LineType { FULL, SHORT }; + /// Overprints the current line. If type is SHORT, elides to_print to fit on + /// one line. void Print(std::string to_print, LineType type); + /// Prints a string on a new line, not overprinting previous output. void PrintOnNewLine(const std::string& to_print); - //private: + private: /// Whether we can do fancy terminal control codes. bool smart_terminal_; + /// Whether the caret is at the beginning of a blank line. bool have_blank_line_; }; -- cgit v0.12 From 6c274da149719fd13150ab0f1f853c524e128f1e Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:28:12 -0700 Subject: Use LinePrinter in test runner. --- src/ninja_test.cc | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/ninja_test.cc b/src/ninja_test.cc index f257ee3..5808e39 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -14,46 +14,48 @@ #include "gtest/gtest.h" +#include "line_printer.h" + +std::string StringPrintf(const char* format, ...) { + const int N = 1024; + char buf[N]; + + va_list ap; + va_start(ap, format); + vsnprintf(buf, N, format, ap); + va_end(ap); + + return buf; +} + /// A test result printer that's less wordy than gtest's default. class LaconicPrinter : public testing::EmptyTestEventListener { public: - LaconicPrinter() : have_blank_line_(false), smart_terminal_(true) {} - virtual void OnTestStart(const testing::TestInfo& test_info) { - printf("\r%s.%s starting.", test_info.test_case_name(), test_info.name()); - printf("\x1B[K"); // Clear to end of line. - fflush(stdout); - have_blank_line_ = false; + printer_.Print(StringPrintf("%s.%s starting", test_info.test_case_name(), + test_info.name()), LinePrinter::SHORT); } virtual void OnTestPartResult( const testing::TestPartResult& test_part_result) { if (!test_part_result.failed()) return; - if (!have_blank_line_ && smart_terminal_) - printf("\n"); - printf("*** Failure in %s:%d\n%s\n", - test_part_result.file_name(), - test_part_result.line_number(), - test_part_result.summary()); - have_blank_line_ = true; + printer_.PrintOnNewLine(StringPrintf( + "*** Failure in %s:%d\n%s\n", test_part_result.file_name(), + test_part_result.line_number(), test_part_result.summary())); } virtual void OnTestEnd(const testing::TestInfo& test_info) { - printf("\r%s.%s ending.", test_info.test_case_name(), test_info.name()); - printf("\x1B[K"); // Clear to end of line. - fflush(stdout); - have_blank_line_ = false; + printer_.Print(StringPrintf("%s.%s ending", test_info.test_case_name(), + test_info.name()), LinePrinter::SHORT); } virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { - if (!have_blank_line_ && smart_terminal_) - printf("\n"); + printer_.PrintOnNewLine(""); } private: - bool have_blank_line_; - bool smart_terminal_; + LinePrinter printer_; }; int main(int argc, char **argv) { -- cgit v0.12 From 45fc9492718aab1256152edc112671214587f020 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:41:50 -0700 Subject: Make ninja_test output nicer. --- src/ninja_test.cc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 5808e39..8e431ab 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -31,9 +31,16 @@ std::string StringPrintf(const char* format, ...) { /// A test result printer that's less wordy than gtest's default. class LaconicPrinter : public testing::EmptyTestEventListener { public: + LaconicPrinter() : tests_started_(0), test_count_(0) {} + virtual void OnTestProgramStart(const testing::UnitTest& unit_test) { + test_count_ = unit_test.test_to_run_count(); + } + virtual void OnTestStart(const testing::TestInfo& test_info) { - printer_.Print(StringPrintf("%s.%s starting", test_info.test_case_name(), - test_info.name()), LinePrinter::SHORT); + ++tests_started_; + printer_.Print(StringPrintf("[%d/%d] %s.%s", tests_started_, test_count_, + test_info.test_case_name(), test_info.name()), + LinePrinter::SHORT); } virtual void OnTestPartResult( @@ -45,17 +52,14 @@ class LaconicPrinter : public testing::EmptyTestEventListener { test_part_result.line_number(), test_part_result.summary())); } - virtual void OnTestEnd(const testing::TestInfo& test_info) { - printer_.Print(StringPrintf("%s.%s ending", test_info.test_case_name(), - test_info.name()), LinePrinter::SHORT); - } - virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { - printer_.PrintOnNewLine(""); + printer_.PrintOnNewLine(unit_test.Passed() ? "passed\n" : "failed\n"); } private: LinePrinter printer_; + int tests_started_; + int test_count_; }; int main(int argc, char **argv) { -- cgit v0.12 From 0a2fc151976277d8c0319cdc4ee3b1932b673d91 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 8 Apr 2013 21:43:49 -0700 Subject: add a straightforward deps log test, fix the other one The first test I wrote was wrong; write a simpler test that exercises the "no failures" code paths, then fix the second test and the bugs it exposed. --- src/build.cc | 23 ++++++++++---- src/build.h | 3 +- src/build_test.cc | 91 +++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/build.cc b/src/build.cc index 5e874df..7de4b16 100644 --- a/src/build.cc +++ b/src/build.cc @@ -801,10 +801,12 @@ void Builder::FinishCommand(CommandRunner::Result* result) { // can fail, which makes the command fail from a build perspective. vector deps_nodes; + TimeStamp deps_mtime = 0; string deps_type = edge->GetBinding("deps"); if (result->success() && !deps_type.empty()) { string extract_err; - if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { + if (!ExtractDeps(result, deps_type, &deps_nodes, &deps_mtime, + &extract_err)) { if (!result->output.empty()) result->output.append("\n"); result->output.append(extract_err); @@ -875,10 +877,12 @@ void Builder::FinishCommand(CommandRunner::Result* result) { if (!deps_type.empty()) { assert(edge->outputs_.size() == 1 && "should have been rejected by parser"); Node* out = edge->outputs_[0]; - TimeStamp mtime = disk_interface_->Stat(out->path()); - // XXX we could reuse the restat logic to avoid a second stat, - // but in practice we only care about the single output. - scan_.deps_log()->RecordDeps(out, mtime, deps_nodes); + if (!deps_mtime) { + // On Windows there's no separate file to compare against; just reuse + // the output's mtime. + deps_mtime = disk_interface_->Stat(out->path()); + } + scan_.deps_log()->RecordDeps(out, deps_mtime, deps_nodes); } } @@ -886,6 +890,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { bool Builder::ExtractDeps(CommandRunner::Result* result, const string& deps_type, vector* deps_nodes, + TimeStamp* deps_mtime, string* err) { #ifdef _WIN32 if (deps_type == "msvc") { @@ -900,7 +905,13 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, if (deps_type == "gcc") { string depfile = result->edge->GetBinding("depfile"); if (depfile.empty()) { - *err = string("edge with deps=gcc but no depfile makes no sense\n"); + *err = string("edge with deps=gcc but no depfile makes no sense"); + return false; + } + + *deps_mtime = disk_interface_->Stat(depfile); + if (*deps_mtime <= 0) { + *err = string("unable to read depfile"); return false; } diff --git a/src/build.h b/src/build.h index fb5fd10..d5f01cd 100644 --- a/src/build.h +++ b/src/build.h @@ -177,7 +177,8 @@ struct Builder { private: bool ExtractDeps(CommandRunner::Result* result, const string& deps_type, - vector* deps_nodes, string* err); + vector* deps_nodes, TimeStamp* deps_mtime, + string* err); DiskInterface* disk_interface_; DependencyScan scan_; diff --git a/src/build_test.cc b/src/build_test.cc index 7df742f..2a0fa0f 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1388,7 +1388,7 @@ TEST_F(BuildTest, FailedDepsParse) { // These deps will fail to parse, as they should only have one // path to the left of the colon. - fs_.Create("in1.d", "XXX YYY"); + fs_.Create("in1.d", "AAA BBB"); EXPECT_FALSE(builder_.Build(&err)); EXPECT_EQ("subcommand failed", err); @@ -1417,10 +1417,8 @@ struct BuildWithDepsLogTest : public BuildTest { void* builder_; }; -TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { - // Don't make use of the class's built-in Builder etc. so that we - // can construct objects with the DepsLog in place. - +/// Run a straightforwad build where the deps log is used. +TEST_F(BuildWithDepsLogTest, Straightforward) { string err; // Note: in1 was created by the superclass SetUp(). const char* manifest = @@ -1447,6 +1445,8 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { // The deps file should have been removed. EXPECT_EQ(0, fs_.Stat("in1.d")); + // Recreate it for the next step. + fs_.Create("in1.d", "out: in2"); deps_log.Close(); builder.command_runner_.release(); } @@ -1456,12 +1456,9 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); - // Pretend that the build aborted before the deps were - // removed, leaving behind an obsolete .d file, but after - // the output was written. - fs_.Create("in1.d", "XXX"); + // Touch the file only mentioned in the deps. fs_.Tick(); - fs_.Create("out", ""); + fs_.Create("in2", ""); // Run the build again. DepsLog deps_log; @@ -1476,6 +1473,80 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { EXPECT_TRUE(builder.Build(&err)); EXPECT_EQ("", err); + // We should have rebuilt the output due to in2 being + // out of date. + EXPECT_EQ(1u, command_runner_.commands_ran_.size()); + + builder.command_runner_.release(); + } +} + +/// Verify that obsolete deps still cause a rebuild. +TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { + string err; + // Note: in1 was created by the superclass SetUp(). + const char* manifest = + "build out: cat in1\n" + " deps = gcc\n" + " depfile = in1.d\n"; + { + // Create the obsolete deps, then run a build to incorporate them. + // The idea is that the inputs/outputs are newer than the logged + // deps. + fs_.Create("in1.d", "out: "); + fs_.Tick(); + + fs_.Create("in1", ""); + + 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); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + fs_.Create("out", ""); + // The deps file should have been removed. + EXPECT_EQ(0, fs_.Stat("in1.d")); + deps_log.Close(); + builder.command_runner_.release(); + } + + // Now we should be in a situation where in1/out2 both have recent + // timestamps but the deps are old. Verify we rebuild. + fs_.Tick(); + + { + State state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + 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); + + // Recreate the deps here just to prove the old recorded deps are + // the problem. + fs_.Create("in1.d", "out: "); + + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + // We should have rebuilt the output due to the deps being // out of date. EXPECT_EQ(1u, command_runner_.commands_ran_.size()); -- cgit v0.12 From 88013d2f407d8948f9a2d6dc2f80e99e12f8adbf Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:48:51 -0700 Subject: Try to fix build on Linux. --- src/line_printer.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/line_printer.cc b/src/line_printer.cc index bfa737e..d30dd2c 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -14,6 +14,8 @@ #include "line_printer.h" +#include +#include #ifdef _WIN32 #include #else -- cgit v0.12 From 5b04aadbb2051a613b4a3fe28e21eb4527f2952f Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 8 Apr 2013 21:53:25 -0700 Subject: Try to fix build on Linux more. --- src/ninja_test.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 8e431ab..3376050 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "gtest/gtest.h" +#include +#include +#include "gtest/gtest.h" #include "line_printer.h" std::string StringPrintf(const char* format, ...) { -- cgit v0.12 From 3e5dce7ce641c600384268db3feb74cb8aac25b3 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 9 Apr 2013 09:33:35 -0700 Subject: document deps --- doc/manual.asciidoc | 99 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index e606326..15359ec 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -580,6 +580,80 @@ Ninja always warns if the major versions of Ninja and the come up yet so it's difficult to predict what behavior might be required. +[[ref_headers]] +C/C++ header dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To get C/C++ header dependencies (or any other build dependency that +works in a similar way) correct Ninja has some extra functionality. + +The problem with headers is that the full list of files that a given +source file depends on can only be discovered by the compiler: +different preprocessor defines and include paths cause different files +to be used. Some compilers can emit this information while building, +and Ninja can use that to get its dependencies perfect. + +Consider: if the file has never been compiled, it must be built anyway, +generating the header dependencies as a side effect. If any file is +later modified (even in a way that changes which headers it depends +on) the modification will cause a rebuild as well, keeping the +dependencies up to date. + +When loading these special dependencies, Ninja implicitly adds extra +build edges such that it is not an error if the listed dependency is +missing. This allows you to delete a header file and rebuild without +the build aborting due to a missing input. + +depfile +^^^^^^^ + +`gcc` (and other compilers like `clang`) support emitting dependency +information in the syntax of a Makefile. (Any command that can write +dependencies in this form can be used, not just `gcc`.) + +To bring this information into Ninja requires cooperation. On the +Ninja side, the `depfile` attribute on the `build` must point to a +path where this data is written. (Ninja only supports the limited +subset of the Makefile syntax emitted by compilers.) Then the command +must know to write dependencies into the `depfile` path. +Use it like in the following example: + +---- +rule cc + depfile = $out.d + command = gcc -MMD -MF $out.d [other gcc flags here] +---- + +The `-MMD` flag to `gcc` tells it to output header dependencies, and +the `-MF` flag tells it where to write them. + +deps +^^^^ + +_(Available since Ninja 1.3.)_ + +It turns out that for large projects (and particularly on Windows, +where the file system is slow) loading these dependency files on +startup is slow. + +Ninja 1.3 can instead process dependencies just after they're generated +and save a compacted form of the same information in a Ninja-internal +database. + +Ninja supports this processing in two forms. + +1. `deps = gcc` specifies that the tool outputs `gcc`-style dependencies + in the form of Makefiles. Adding this to the above example will + cause Ninja to process the `depfile` immediately after the + compilation finishes, then delete the `.d` file (which is only used + as a temporary). + +2. `deps = msvc` specifies that the tool outputs header dependencies + in the form produced by Visual Studio's compiler's + http://msdn.microsoft.com/en-us/library/hdkef6tk(v=vs.90).aspx[`/showIncludes` + flag]. Briefly, this means the tool outputs specially-formatted lines + to its stdout. No `depfile` attribute is necessary. + Ninja file reference -------------------- @@ -666,6 +740,7 @@ line. If a line is indented more than the previous one, it's considered part of its parent's scope; if it is indented less than the previous one, it closes the previous scope. +[[ref_toplevel]] Top-level variables ~~~~~~~~~~~~~~~~~~~ @@ -695,22 +770,14 @@ keys. `depfile`:: path to an optional `Makefile` that contains extra _implicit dependencies_ (see <>). This is explicitly to support `gcc` and its `-M` - family of flags, which output the list of headers a given `.c` file - depends on. -+ -Use it like in the following example: -+ ----- -rule cc - depfile = $out.d - command = gcc -MMD -MF $out.d [other gcc flags here] ----- -+ -When loading a `depfile`, Ninja implicitly adds edges such that it is -not an error if the listed dependency is missing. This allows you to -delete a depfile-discovered header file and rebuild, without the build -aborting due to a missing input. + dependency types>>). This is explicitly to support C/C++ header + dependencies; see <>. + +`deps`:: _(Available since Ninja 1.3.)_ if present, must be one of + `gcc` or `msvc` to specify special dependency processing. See + <>. The generated database is + stored as `.ninja_deps` in the `builddir`, see <>. `description`:: a short description of the command, used to pretty-print the command as it's running. The `-v` flag controls whether to print -- cgit v0.12 From eee4910fb7189773074e4c89d4ba249704ba5d11 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 9 Apr 2013 09:58:54 -0700 Subject: On invalid depslog header, restart build instead of showing an error. Also add tests for invalid headers. --- src/deps_log.cc | 14 +++++--------- src/deps_log_test.cc | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 8946e32..79daba5 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -136,16 +136,12 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return false; } - if (!fgets(buf, sizeof(buf), f)) { - *err = strerror(errno); - return false; - } + bool valid_header = true; int version = 0; - if (fread(&version, 4, 1, f) < 1) { - *err = strerror(errno); - return false; - } - if (version != kCurrentVersion) { + if (!fgets(buf, sizeof(buf), f) || fread(&version, 4, 1, f) < 1) + valid_header = false; + if (!valid_header || strcmp(buf, kFileSignature) != 0 || + version != kCurrentVersion) { *err = "bad deps log signature or version; starting over"; fclose(f); unlink(path.c_str()); diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 2d91c0e..06fffeb 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -192,4 +192,30 @@ TEST_F(DepsLogTest, Recompact) { } } +// Verify that invalid file headers cause a new build. +TEST_F(DepsLogTest, InvalidHeader) { + const char *kInvalidHeaders[] = { + "", // Emtpy file. + "# ninjad", // Truncated first line. + "# ninjadeps\n", // No version int. + "# ninjadeps\n\001\002", // Truncated version int. + "# ninjadeps\n\001\002\003\004" // Invalid version int. + }; + for (size_t i = 0; i < sizeof(kInvalidHeaders) / sizeof(kInvalidHeaders[0]); + ++i) { + FILE* deps_log = fopen(kTestFilename, "wb"); + ASSERT_TRUE(deps_log != NULL); + ASSERT_EQ( + strlen(kInvalidHeaders[i]), + fwrite(kInvalidHeaders[i], 1, strlen(kInvalidHeaders[i]), deps_log)); + ASSERT_EQ(0 ,fclose(deps_log)); + + string err; + DepsLog log; + State state; + ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); + EXPECT_EQ("bad deps log signature or version; starting over", err); + } +} + } // anonymous namespace -- cgit v0.12 From 808aa158be0d422a2ee5366a3f4eda29cd51c39a Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Tue, 9 Apr 2013 10:03:46 -0700 Subject: fix windows build after depslog --- bootstrap.py | 3 +-- src/build.cc | 2 +- src/deps_log.cc | 2 ++ src/line_printer.cc | 10 ++++++---- src/line_printer.h | 6 ++++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index 4d9bc84..f83b2d0 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -127,8 +127,7 @@ if options.verbose: if options.windows: print('Building ninja using itself...') - run([sys.executable, 'configure.py', '--with-ninja=%s' % binary] + - conf_args) + run([sys.executable, 'configure.py'] + conf_args) run(['./' + binary] + verbose) # Copy the new executable over the bootstrap one. diff --git a/src/build.cc b/src/build.cc index ab3d781..39e3e2a 100644 --- a/src/build.cc +++ b/src/build.cc @@ -247,7 +247,7 @@ void BuildStatus::PrintStatus(Edge* edge) { to_print = FormatProgressStatus(progress_status_format_) + to_print; printer_.Print(to_print, - force_full_command ? LinePrinter::FULL : LinePrinter::SHORT); + force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE); } Plan::Plan() : command_edges_(0), wanted_edges_(0) {} diff --git a/src/deps_log.cc b/src/deps_log.cc index 8946e32..55ed30a 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -18,7 +18,9 @@ #include #include #include +#ifndef _WIN32 #include +#endif #include "graph.h" #include "metrics.h" diff --git a/src/line_printer.cc b/src/line_printer.cc index d30dd2c..751fb07 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -57,7 +57,7 @@ void LinePrinter::Print(std::string to_print, LineType type) { #endif } - if (smart_terminal_ && type == SHORT) { + if (smart_terminal_ && type == ELIDE) { #ifdef _WIN32 // Don't use the full width or console will move to next line. size_t width = static_cast(csbi.dwSize.X) - 1; @@ -68,9 +68,11 @@ void LinePrinter::Print(std::string to_print, LineType type) { GetConsoleScreenBufferInfo(console_, &csbi); COORD buf_size = { csbi.dwSize.X, 1 }; COORD zero_zero = { 0, 0 }; - SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, - (SHORT)(csbi.dwCursorPosition.X + csbi.dwSize.X - 1), - csbi.dwCursorPosition.Y }; + SMALL_RECT target = { + csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, + static_cast(csbi.dwCursorPosition.X + csbi.dwSize.X - 1), + csbi.dwCursorPosition.Y + }; CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X]; memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X); for (int i = 0; i < csbi.dwSize.X; ++i) { diff --git a/src/line_printer.h b/src/line_printer.h index 78510ea..54620da 100644 --- a/src/line_printer.h +++ b/src/line_printer.h @@ -28,9 +28,9 @@ class LinePrinter { enum LineType { FULL, - SHORT + ELIDE }; - /// Overprints the current line. If type is SHORT, elides to_print to fit on + /// Overprints the current line. If type is ELIDE, elides to_print to fit on /// one line. void Print(std::string to_print, LineType type); @@ -43,6 +43,8 @@ class LinePrinter { /// Whether the caret is at the beginning of a blank line. bool have_blank_line_; + + void* console_; }; #endif // NINJA_LINE_PRINTER_H_ -- cgit v0.12 From dc4aa9fbbb8c9678ca615c3011344297f4e2fe8e Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Tue, 9 Apr 2013 10:05:23 -0700 Subject: add ifdef around console_ --- src/line_printer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/line_printer.h b/src/line_printer.h index 54620da..4226c92 100644 --- a/src/line_printer.h +++ b/src/line_printer.h @@ -44,7 +44,9 @@ class LinePrinter { /// Whether the caret is at the beginning of a blank line. bool have_blank_line_; +#ifdef _WIN32 void* console_; +#endif }; #endif // NINJA_LINE_PRINTER_H_ -- cgit v0.12 From 78effcb85dbb45633f6735e48a815bd43d5de578 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Tue, 9 Apr 2013 10:44:54 -0700 Subject: fix test --- src/build.h | 4 ---- src/ninja_test.cc | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/build.h b/src/build.h index 9a16990..ca75ade 100644 --- a/src/build.h +++ b/src/build.h @@ -275,10 +275,6 @@ struct BuildStatus { mutable RateInfo overall_rate_; mutable SlidingRateInfo current_rate_; - -#ifdef _WIN32 - void* console_; -#endif }; #endif // NINJA_BUILD_H_ diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 3376050..02f2c6b 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -42,7 +42,7 @@ class LaconicPrinter : public testing::EmptyTestEventListener { ++tests_started_; printer_.Print(StringPrintf("[%d/%d] %s.%s", tests_started_, test_count_, test_info.test_case_name(), test_info.name()), - LinePrinter::SHORT); + LinePrinter::ELIDE); } virtual void OnTestPartResult( -- cgit v0.12 From 7dba46cbe77543223d896a82bab078920b214091 Mon Sep 17 00:00:00 2001 From: yannicklm Date: Tue, 9 Apr 2013 20:13:48 +0200 Subject: ninja.vim: add deps as keyword new in ninja 1.3 --- misc/ninja.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/ninja.vim b/misc/ninja.vim index 841902f..ec120d8 100644 --- a/misc/ninja.vim +++ b/misc/ninja.vim @@ -2,7 +2,7 @@ " Language: ninja build file as described at " http://martine.github.com/ninja/manual.html " Version: 1.3 -" Last Change: 2012/12/14 +" Last Change: 2013/04/09 " Maintainer: Nicolas Weber " Version 1.2 of this script is in the upstream vim repository and will be " included in the next vim release. If you change this, please send your change @@ -36,7 +36,7 @@ syn match ninjaKeyword "^subninja\>" " let assignments. " manifest_parser.cc, ParseRule() syn region ninjaRule start="^rule" end="^\ze\S" contains=ALL transparent -syn keyword ninjaRuleCommand contained command depfile description generator +syn keyword ninjaRuleCommand contained command deps depfile description generator \ pool restat rspfile rspfile_content syn region ninjaPool start="^pool" end="^\ze\S" contains=ALL transparent -- cgit v0.12 From f6dcd3537036c6a2549056a5dbca081a230cfc92 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 9 Apr 2013 12:44:31 -0700 Subject: fix typo in comment --- src/deps_log_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 06fffeb..e9cea21 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -195,7 +195,7 @@ TEST_F(DepsLogTest, Recompact) { // Verify that invalid file headers cause a new build. TEST_F(DepsLogTest, InvalidHeader) { const char *kInvalidHeaders[] = { - "", // Emtpy file. + "", // Empty file. "# ninjad", // Truncated first line. "# ninjadeps\n", // No version int. "# ninjadeps\n\001\002", // Truncated version int. -- cgit v0.12 From 87fdd8482d1f47c296f980e641d8250ab820c0eb Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 9 Apr 2013 10:07:17 -0700 Subject: bigger manual --- doc/style.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/style.css b/doc/style.css index fc22ec1..4287fc6 100644 --- a/doc/style.css +++ b/doc/style.css @@ -1,9 +1,8 @@ body { margin: 5ex 10ex; - max-width: 40em; - line-height: 1.4; + max-width: 80ex; + line-height: 1.5; font-family: sans-serif; - font-size: 0.8em; } h1, h2, h3 { font-weight: normal; @@ -21,8 +20,12 @@ pre { code { color: #007; } -.chapter { +div.chapter { margin-top: 4em; + border-top: solid 2px black; +} +.section .title { + font-size: 1.3em; } p { margin-top: 0; -- cgit v0.12 From eaa4d6c22c5aeae1ef42a85a62dbedf86d546b07 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Tue, 9 Apr 2013 10:15:18 -0700 Subject: update some windows references in the docs Drop the timing info, it's too variable to commit to a manual. --- doc/manual.asciidoc | 41 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 15359ec..0c0d761 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -136,19 +136,15 @@ but they are not part of make itself.) Using Ninja for your project ---------------------------- -Ninja currently works on Unix-like systems. It's seen the most testing -on Linux (and has the best performance there) but it runs fine on Mac -OS X and FreeBSD. Ninja has some preliminary Windows support but the -full details of the implementation -- like how to get C header -interdependencies correct and fast when using MSVC's compiler -- is -not yet complete. +Ninja currently works on Unix-like systems and Windows. It's seen the +most testing on Linux (and has the best performance there) but it runs +fine on Mac OS X and FreeBSD. If your project is small, Ninja's speed impact is likely unnoticeable. -Some build timing numbers are included below. (However, even for -small projects it sometimes turns out that Ninja's limited syntax -forces simpler build rules that result in faster builds.) Another way -to say this is that if you're happy with the edit-compile cycle time -of your project already then Ninja won't help. +(However, even for small projects it sometimes turns out that Ninja's +limited syntax forces simpler build rules that result in faster +builds.) Another way to say this is that if you're happy with the +edit-compile cycle time of your project already then Ninja won't help. There are many other build systems that are more user-friendly or featureful than Ninja itself. For some recommendations: the Ninja @@ -160,21 +156,11 @@ Ninja's benefit comes from using it in conjunction with a smarter meta-build system. http://code.google.com/p/gyp/[gyp]:: The meta-build system used to -generate build files for Google Chrome. gyp can generate Ninja files -for Linux and Mac and is used by many Chrome developers; support for -Windows is in progress. See the +generate build files for Google Chrome and related projects (v8, +node.js). gyp can generate Ninja files for all platforms supported by +Chrome. See the http://code.google.com/p/chromium/wiki/NinjaBuild[Chromium Ninja -documentation for more details]. gyp is relatively unpopular outside -of the Chrome and v8 world. - -* For Chrome (~30k source files), Ninja reduced no-op builds from - around 15 seconds to under one second. -* https://plus.google.com/108996039294665965197/posts/SfhrFAhRyyd[A - Mozilla developer compares build systems]: "While chromium's full - build is 2.15x slower than firefox's, a nop build is 78.2x faster! - That is really noticeable during development. No incremental build - of firefox can be faster than 57.9s, which means that in practice - almost all of them will be over a minute." +documentation for more details]. http://www.cmake.org/[CMake]:: A widely used meta-build system that can generate Ninja files on Linux as of CMake version 2.8.8. (There @@ -183,11 +169,6 @@ uses Ninja on Windows for their buildbots, but those platforms are not yet officially supported by CMake as the full test suite doesn't pass.) -* For building Blender, one user reported "Single file rebuild is 0.97 - sec, same on makefiles was 3.7sec." -* For building LLVM on Windows, one user reported no-op build times: - "ninja: 0.4s / MSBuild: 11s / jom: 53s". - others:: Ninja ought to fit perfectly into other meta-build software like http://industriousone.com/premake[premake]. If you do this work, please let us know! -- cgit v0.12 From 5bc973539dfd92a77de9ec41c4c0ee579f62b3e5 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 09:31:23 -0700 Subject: more windows doc updates --- doc/manual.asciidoc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 0c0d761..295f93c 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -106,9 +106,9 @@ Here are some of the features Ninja adds to make. (These sorts of features can often be implemented using more complicated Makefiles, but they are not part of make itself.) -* A Ninja rule may point at a path for extra implicit dependency - information. This makes it easy to get header dependencies correct - for C/C++ code. +* Ninja has special support for discovering extra dependencies at build + time, making it easy to get <> + correct for C/C++ code. * A build edge may have multiple outputs. @@ -633,7 +633,14 @@ Ninja supports this processing in two forms. in the form produced by Visual Studio's compiler's http://msdn.microsoft.com/en-us/library/hdkef6tk(v=vs.90).aspx[`/showIncludes` flag]. Briefly, this means the tool outputs specially-formatted lines - to its stdout. No `depfile` attribute is necessary. + to its stdout. Ninja then filters these lines from the displayed + output. No `depfile` attribute is necessary. ++ +---- +rule cc + deps = msvc + command = cl /showIncludes -c $in /Fo$out +---- Ninja file reference -- cgit v0.12 From 13c715c24d9e1826c81a761594c0f26cab72833d Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 09:34:34 -0700 Subject: monospace flags --- doc/manual.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 295f93c..d482b5d 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -202,7 +202,8 @@ Several placeholders are available: `%u`:: The number of remaining edges to start. `%f`:: The number of finished edges. `%o`:: Overall rate of finished edges per second -`%c`:: Current rate of finished edges per second (average over builds specified by -j or its default) +`%c`:: Current rate of finished edges per second (average over builds +specified by `-j` or its default) `%e`:: Elapsed time in seconds. _(Available since Ninja 1.2.)_ `%%`:: A plain `%` character. @@ -420,7 +421,7 @@ or a build statement. No matter what pools you specify, ninja will never run more concurrent jobs than the default parallelism, or the number of jobs specified on the command -line (with -j). +line (with `-j`). ---------------- # No more than 4 links at a time. -- cgit v0.12 From 6e5cd6f6c69644c67b0103e52d6171b7905ef42b Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 09:43:07 -0700 Subject: mention mtimes --- doc/manual.asciidoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index d482b5d..9523bfd 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -285,8 +285,9 @@ Conceptual overview ~~~~~~~~~~~~~~~~~~~ Ninja evaluates a graph of dependencies between files, and runs -whichever commands are necessary to make your build target up to date. -If you are familiar with Make, Ninja is very similar. +whichever commands are necessary to make your build target up to date +as determined by file modification times. If you are familiar with +Make, Ninja is very similar. A build file (default name: `build.ninja`) provides a list of _rules_ -- short names for longer commands, like how to run the compiler -- -- cgit v0.12 From b8e7500180efe3dfa86f76b02def94eafd3e490d Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 09:43:25 -0700 Subject: make unittest match module name --- misc/ninja_syntax_test.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++ misc/ninja_test.py | 152 ---------------------------------------------- 2 files changed, 152 insertions(+), 152 deletions(-) create mode 100755 misc/ninja_syntax_test.py delete mode 100755 misc/ninja_test.py diff --git a/misc/ninja_syntax_test.py b/misc/ninja_syntax_test.py new file mode 100755 index 0000000..2aef7ff --- /dev/null +++ b/misc/ninja_syntax_test.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python + +# Copyright 2011 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import ninja_syntax + +LONGWORD = 'a' * 10 +LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5 +INDENT = ' ' + +class TestLineWordWrap(unittest.TestCase): + def setUp(self): + self.out = StringIO() + self.n = ninja_syntax.Writer(self.out, width=8) + + def test_single_long_word(self): + # We shouldn't wrap a single long word. + self.n._line(LONGWORD) + self.assertEqual(LONGWORD + '\n', self.out.getvalue()) + + def test_few_long_words(self): + # We should wrap a line where the second word is overlong. + self.n._line(' '.join(['x', LONGWORD, 'y'])) + self.assertEqual(' $\n'.join(['x', + INDENT + LONGWORD, + INDENT + 'y']) + '\n', + self.out.getvalue()) + + def test_short_words_indented(self): + # Test that indent is taking into acount when breaking subsequent lines. + # The second line should not be ' to tree', as that's longer than the + # test layout width of 8. + self.n._line('line_one to tree') + self.assertEqual('''\ +line_one $ + to $ + tree +''', + self.out.getvalue()) + + def test_few_long_words_indented(self): + # Check wrapping in the presence of indenting. + self.n._line(' '.join(['x', LONGWORD, 'y']), indent=1) + self.assertEqual(' $\n'.join([' ' + 'x', + ' ' + INDENT + LONGWORD, + ' ' + INDENT + 'y']) + '\n', + self.out.getvalue()) + + def test_escaped_spaces(self): + self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y'])) + self.assertEqual(' $\n'.join(['x', + INDENT + LONGWORDWITHSPACES, + INDENT + 'y']) + '\n', + self.out.getvalue()) + + def test_fit_many_words(self): + self.n = ninja_syntax.Writer(self.out, width=78) + self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1) + self.assertEqual('''\ + command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $ + ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $ + ../out/Debug/gen/chrome/theme_resources_large.pak +''', + self.out.getvalue()) + + def test_leading_space(self): + self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping + self.n.variable('foo', ['', '-bar', '-somethinglong'], 0) + self.assertEqual('''\ +foo = -bar $ + -somethinglong +''', + self.out.getvalue()) + + def test_embedded_dollar_dollar(self): + self.n = ninja_syntax.Writer(self.out, width=15) # force wrapping + self.n.variable('foo', ['a$$b', '-somethinglong'], 0) + self.assertEqual('''\ +foo = a$$b $ + -somethinglong +''', + self.out.getvalue()) + + def test_two_embedded_dollar_dollars(self): + self.n = ninja_syntax.Writer(self.out, width=17) # force wrapping + self.n.variable('foo', ['a$$b', '-somethinglong'], 0) + self.assertEqual('''\ +foo = a$$b $ + -somethinglong +''', + self.out.getvalue()) + + def test_leading_dollar_dollar(self): + self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping + self.n.variable('foo', ['$$b', '-somethinglong'], 0) + self.assertEqual('''\ +foo = $$b $ + -somethinglong +''', + self.out.getvalue()) + + def test_trailing_dollar_dollar(self): + self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping + self.n.variable('foo', ['a$$', '-somethinglong'], 0) + self.assertEqual('''\ +foo = a$$ $ + -somethinglong +''', + self.out.getvalue()) + +class TestBuild(unittest.TestCase): + def setUp(self): + self.out = StringIO() + self.n = ninja_syntax.Writer(self.out) + + def test_variables_dict(self): + self.n.build('out', 'cc', 'in', variables={'name': 'value'}) + self.assertEqual('''\ +build out: cc in + name = value +''', + self.out.getvalue()) + + def test_variables_list(self): + self.n.build('out', 'cc', 'in', variables=[('name', 'value')]) + self.assertEqual('''\ +build out: cc in + name = value +''', + self.out.getvalue()) + +if __name__ == '__main__': + unittest.main() diff --git a/misc/ninja_test.py b/misc/ninja_test.py deleted file mode 100755 index 2aef7ff..0000000 --- a/misc/ninja_test.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2011 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -import ninja_syntax - -LONGWORD = 'a' * 10 -LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5 -INDENT = ' ' - -class TestLineWordWrap(unittest.TestCase): - def setUp(self): - self.out = StringIO() - self.n = ninja_syntax.Writer(self.out, width=8) - - def test_single_long_word(self): - # We shouldn't wrap a single long word. - self.n._line(LONGWORD) - self.assertEqual(LONGWORD + '\n', self.out.getvalue()) - - def test_few_long_words(self): - # We should wrap a line where the second word is overlong. - self.n._line(' '.join(['x', LONGWORD, 'y'])) - self.assertEqual(' $\n'.join(['x', - INDENT + LONGWORD, - INDENT + 'y']) + '\n', - self.out.getvalue()) - - def test_short_words_indented(self): - # Test that indent is taking into acount when breaking subsequent lines. - # The second line should not be ' to tree', as that's longer than the - # test layout width of 8. - self.n._line('line_one to tree') - self.assertEqual('''\ -line_one $ - to $ - tree -''', - self.out.getvalue()) - - def test_few_long_words_indented(self): - # Check wrapping in the presence of indenting. - self.n._line(' '.join(['x', LONGWORD, 'y']), indent=1) - self.assertEqual(' $\n'.join([' ' + 'x', - ' ' + INDENT + LONGWORD, - ' ' + INDENT + 'y']) + '\n', - self.out.getvalue()) - - def test_escaped_spaces(self): - self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y'])) - self.assertEqual(' $\n'.join(['x', - INDENT + LONGWORDWITHSPACES, - INDENT + 'y']) + '\n', - self.out.getvalue()) - - def test_fit_many_words(self): - self.n = ninja_syntax.Writer(self.out, width=78) - self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1) - self.assertEqual('''\ - command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $ - ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $ - ../out/Debug/gen/chrome/theme_resources_large.pak -''', - self.out.getvalue()) - - def test_leading_space(self): - self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping - self.n.variable('foo', ['', '-bar', '-somethinglong'], 0) - self.assertEqual('''\ -foo = -bar $ - -somethinglong -''', - self.out.getvalue()) - - def test_embedded_dollar_dollar(self): - self.n = ninja_syntax.Writer(self.out, width=15) # force wrapping - self.n.variable('foo', ['a$$b', '-somethinglong'], 0) - self.assertEqual('''\ -foo = a$$b $ - -somethinglong -''', - self.out.getvalue()) - - def test_two_embedded_dollar_dollars(self): - self.n = ninja_syntax.Writer(self.out, width=17) # force wrapping - self.n.variable('foo', ['a$$b', '-somethinglong'], 0) - self.assertEqual('''\ -foo = a$$b $ - -somethinglong -''', - self.out.getvalue()) - - def test_leading_dollar_dollar(self): - self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping - self.n.variable('foo', ['$$b', '-somethinglong'], 0) - self.assertEqual('''\ -foo = $$b $ - -somethinglong -''', - self.out.getvalue()) - - def test_trailing_dollar_dollar(self): - self.n = ninja_syntax.Writer(self.out, width=14) # force wrapping - self.n.variable('foo', ['a$$', '-somethinglong'], 0) - self.assertEqual('''\ -foo = a$$ $ - -somethinglong -''', - self.out.getvalue()) - -class TestBuild(unittest.TestCase): - def setUp(self): - self.out = StringIO() - self.n = ninja_syntax.Writer(self.out) - - def test_variables_dict(self): - self.n.build('out', 'cc', 'in', variables={'name': 'value'}) - self.assertEqual('''\ -build out: cc in - name = value -''', - self.out.getvalue()) - - def test_variables_list(self): - self.n.build('out', 'cc', 'in', variables=[('name', 'value')]) - self.assertEqual('''\ -build out: cc in - name = value -''', - self.out.getvalue()) - -if __name__ == '__main__': - unittest.main() -- cgit v0.12 From 8c0d41c7e1b4ebb8b183a9e5eae52c90d99bd196 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 10:02:27 -0700 Subject: more minor manual touchups --- doc/manual.asciidoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 9523bfd..40c4d7e 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -15,8 +15,8 @@ to be fast. It is born from http://neugierig.org/software/chromium/notes/2011/02/ninja.html[my work on the Chromium browser project], which has over 30,000 source files and whose other build systems (including one built from custom -non-recursive Makefiles) can take ten seconds to start building after -changing one file. Ninja is under a second. +non-recursive Makefiles) would take ten seconds to start building +after changing one file. Ninja is under a second. Philosophical overview ~~~~~~~~~~~~~~~~~~~~~~ @@ -89,7 +89,7 @@ create your project's `.ninja` files. Comparison to Make ~~~~~~~~~~~~~~~~~~ -Ninja is closest in spirit and functionality to make, relying on +Ninja is closest in spirit and functionality to Make, relying on simple dependencies between file timestamps. But fundamentally, make has a lot of _features_: suffix rules, @@ -102,7 +102,7 @@ builds correct while punting most complexity to generation of the ninja input files. Ninja by itself is unlikely to be useful for most projects. -Here are some of the features Ninja adds to make. (These sorts of +Here are some of the features Ninja adds to Make. (These sorts of features can often be implemented using more complicated Makefiles, but they are not part of make itself.) @@ -878,7 +878,7 @@ example, the `demo` rule prints "this is a demo of bar". ---- rule demo - command = echo "this is a demo of $foo' + command = echo "this is a demo of $foo" build out: demo foo = bar -- cgit v0.12 From ec6b0e7af9a2605538b6265e66adfe75bdc019f7 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 10:03:55 -0700 Subject: move pool docs down under "more details" The earlier section is a tutorial-style overview. The latter section is for side features like phony rules and header dependencies. Pools fit in with the latter. --- doc/manual.asciidoc | 111 ++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 40c4d7e..aa5644d 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -403,62 +403,6 @@ If the top-level Ninja file is specified as an output of any build statement and it is out of date, Ninja will rebuild and reload it before building the targets requested by the user. -Pools -~~~~~ - -_Available since Ninja 1.1._ - -Pools allow you to allocate one or more rules or edges a finite number -of concurrent jobs which is more tightly restricted than the default -parallelism. - -This can be useful, for example, to restrict a particular expensive rule -(like link steps for huge executables), or to restrict particular build -statements which you know perform poorly when run concurrently. - -Each pool has a `depth` variable which is specified in the build file. -The pool is then referred to with the `pool` variable on either a rule -or a build statement. - -No matter what pools you specify, ninja will never run more concurrent jobs -than the default parallelism, or the number of jobs specified on the command -line (with `-j`). - ----------------- -# No more than 4 links at a time. -pool link_pool - depth = 4 - -# No more than 1 heavy object at a time. -pool heavy_object_pool - depth = 1 - -rule link - ... - pool = link_pool - -rule cc - ... - -# The link_pool is used here. Only 4 links will run concurrently. -build foo.exe: link input.obj - -# A build statement can be exempted from its rule's pool by setting an -# empty pool. This effectively puts the build statement back into the default -# pool, which has infinite depth. -build other.exe: link input.obj - pool = - -# A build statement can specify a pool directly. -# Only one of these builds will run at a time. -build heavy_object1.obj: cc heavy_obj1.cc - pool = heavy_object_pool -build heavy_object2.obj: cc heavy_obj2.cc - pool = heavy_object_pool - ----------------- - - Generating Ninja files from code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -644,6 +588,61 @@ rule cc command = cl /showIncludes -c $in /Fo$out ---- +Pools +~~~~~ + +_Available since Ninja 1.1._ + +Pools allow you to allocate one or more rules or edges a finite number +of concurrent jobs which is more tightly restricted than the default +parallelism. + +This can be useful, for example, to restrict a particular expensive rule +(like link steps for huge executables), or to restrict particular build +statements which you know perform poorly when run concurrently. + +Each pool has a `depth` variable which is specified in the build file. +The pool is then referred to with the `pool` variable on either a rule +or a build statement. + +No matter what pools you specify, ninja will never run more concurrent jobs +than the default parallelism, or the number of jobs specified on the command +line (with `-j`). + +---------------- +# No more than 4 links at a time. +pool link_pool + depth = 4 + +# No more than 1 heavy object at a time. +pool heavy_object_pool + depth = 1 + +rule link + ... + pool = link_pool + +rule cc + ... + +# The link_pool is used here. Only 4 links will run concurrently. +build foo.exe: link input.obj + +# A build statement can be exempted from its rule's pool by setting an +# empty pool. This effectively puts the build statement back into the default +# pool, which has infinite depth. +build other.exe: link input.obj + pool = + +# A build statement can specify a pool directly. +# Only one of these builds will run at a time. +build heavy_object1.obj: cc heavy_obj1.cc + pool = heavy_object_pool +build heavy_object2.obj: cc heavy_obj2.cc + pool = heavy_object_pool + +---------------- + Ninja file reference -------------------- -- cgit v0.12 From 07f16eb41e9c266fe402a816c5e54f7479ca7fd4 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 10:08:51 -0700 Subject: slightly smaller subsections --- doc/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/style.css b/doc/style.css index 4287fc6..5d14a1c 100644 --- a/doc/style.css +++ b/doc/style.css @@ -27,6 +27,9 @@ div.chapter { .section .title { font-size: 1.3em; } +.section .section .title { + font-size: 1.2em; +} p { margin-top: 0; } -- cgit v0.12 From 808393fcf01d5211ab062acd3dea46ed8041f829 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 12:10:10 -0700 Subject: include mtimes in deplog explain --- src/graph.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graph.cc b/src/graph.cc index 2614882..b245e52 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -187,7 +187,8 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge, // Dirty if the output is newer than the deps. if (deps_mtime && output->mtime() > deps_mtime) { - EXPLAIN("stored deps info out of date for for %s", output->path().c_str()); + EXPLAIN("stored deps info out of date for for %s (%d vs %d)", + output->path().c_str(), deps_mtime, output->mtime()); return true; } -- cgit v0.12 From 53339104c51139f7ed5f1265bae3062d157f5311 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 12:49:23 -0700 Subject: always use output timestamp for deps Using the timestamp of the .d file was wrong. It can be written at a different time than the output, and it is immediately deleted after parsing; only the output remains for subsequent timestamp comparison purposes, so always use the output's timestamp. (This is how the code worked on Windows already.) --- src/build.cc | 17 ++--------------- src/build.h | 3 +-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/build.cc b/src/build.cc index 39e3e2a..199749f 100644 --- a/src/build.cc +++ b/src/build.cc @@ -714,12 +714,10 @@ void Builder::FinishCommand(CommandRunner::Result* result) { // can fail, which makes the command fail from a build perspective. vector deps_nodes; - TimeStamp deps_mtime = 0; string deps_type = edge->GetBinding("deps"); if (result->success() && !deps_type.empty()) { string extract_err; - if (!ExtractDeps(result, deps_type, &deps_nodes, &deps_mtime, - &extract_err)) { + if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { if (!result->output.empty()) result->output.append("\n"); result->output.append(extract_err); @@ -790,11 +788,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { if (!deps_type.empty()) { assert(edge->outputs_.size() == 1 && "should have been rejected by parser"); Node* out = edge->outputs_[0]; - if (!deps_mtime) { - // On Windows there's no separate file to compare against; just reuse - // the output's mtime. - deps_mtime = disk_interface_->Stat(out->path()); - } + TimeStamp deps_mtime = disk_interface_->Stat(out->path()); scan_.deps_log()->RecordDeps(out, deps_mtime, deps_nodes); } @@ -803,7 +797,6 @@ void Builder::FinishCommand(CommandRunner::Result* result) { bool Builder::ExtractDeps(CommandRunner::Result* result, const string& deps_type, vector* deps_nodes, - TimeStamp* deps_mtime, string* err) { #ifdef _WIN32 if (deps_type == "msvc") { @@ -822,12 +815,6 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, return false; } - *deps_mtime = disk_interface_->Stat(depfile); - if (*deps_mtime <= 0) { - *err = string("unable to read depfile"); - return false; - } - string content = disk_interface_->ReadFile(depfile, err); if (!err->empty()) return false; diff --git a/src/build.h b/src/build.h index ca75ade..5cbd2a6 100644 --- a/src/build.h +++ b/src/build.h @@ -178,8 +178,7 @@ struct Builder { private: bool ExtractDeps(CommandRunner::Result* result, const string& deps_type, - vector* deps_nodes, TimeStamp* deps_mtime, - string* err); + vector* deps_nodes, string* err); DiskInterface* disk_interface_; DependencyScan scan_; -- cgit v0.12 From 461fec29aa6dce66fdf431256744f67b5e9ba6b9 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Wed, 10 Apr 2013 12:51:04 -0700 Subject: build ninja itself in deps mode Hopefully will help flush out bugs. --- configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.py b/configure.py index dd26906..b40dafb 100755 --- a/configure.py +++ b/configure.py @@ -189,13 +189,13 @@ n.newline() if platform == 'windows': n.rule('cxx', command='$cxx /showIncludes $cflags -c $in /Fo$out', - depfile='$out.d', description='CXX $out', deps='msvc') else: n.rule('cxx', command='$cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out', depfile='$out.d', + deps='gcc', description='CXX $out') n.newline() -- cgit v0.12 From 84b629e474178da1462d04d0059be89a39832bd0 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Wed, 10 Apr 2013 20:16:24 -0700 Subject: try again on vs2012 build fixes --- configure.py | 3 ++- src/build_test.cc | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/configure.py b/configure.py index b40dafb..c526c1a 100755 --- a/configure.py +++ b/configure.py @@ -127,6 +127,7 @@ if platform == 'windows': # We never have strings or arrays larger than 2**31. '/wd4267', '/DNOMINMAX', '/D_CRT_SECURE_NO_WARNINGS', + '/D_VARIADIC_MAX=10', '/DNINJA_PYTHON="%s"' % options.with_python] ldflags = ['/DEBUG', '/libpath:$builddir'] if not options.debug: @@ -321,7 +322,7 @@ if options.with_gtest: gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include')) if platform == 'windows': - gtest_cflags = '/nologo /EHsc /Zi ' + gtest_all_incs + gtest_cflags = '/nologo /EHsc /Zi /D_VARIADIC_MAX=10 ' + gtest_all_incs else: gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs objs += n.build(built('gtest-all' + objext), 'cxx', diff --git a/src/build_test.cc b/src/build_test.cc index 2a0fa0f..1854387 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -242,8 +242,8 @@ TEST_F(PlanTest, PoolsWithDepthTwo) { )); // Mark all the out* nodes dirty for (int i = 0; i < 3; ++i) { - GetNode("out" + string(1, '1' + i))->MarkDirty(); - GetNode("outb" + string(1, '1' + i))->MarkDirty(); + GetNode("out" + string(1, '1' + static_cast(i)))->MarkDirty(); + GetNode("outb" + string(1, '1' + static_cast(i)))->MarkDirty(); } GetNode("allTheThings")->MarkDirty(); -- cgit v0.12 From 524e7f6089db5c619bc1f2a97ce3a6d55b18c5be Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 11 Apr 2013 10:03:48 -0700 Subject: windows build fix How did this ever work? --- src/msvc_helper_main-win32.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc index 3192821..ef91450 100644 --- a/src/msvc_helper_main-win32.cc +++ b/src/msvc_helper_main-win32.cc @@ -59,7 +59,8 @@ void WriteDepFileOrDie(const char* object_path, const CLParser& parse) { Fatal("writing %s", depfile_path.c_str()); } const set& headers = parse.includes_; - for (set::iterator i = headers.begin(); i != headers.end(); ++i) { + for (set::const_iterator i = headers.begin(); + i != headers.end(); ++i) { if (fprintf(depfile, "%s\n", EscapeForDepfile(*i).c_str()) < 0) { unlink(object_path); fclose(depfile); -- cgit v0.12 From 2aa23a8bacaeea8155d50acbe55f1cd0dfae5a1c Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 11 Apr 2013 11:28:28 -0700 Subject: fix test I intentionally changed the semantics of the code that affected this test, but didn't update the test. Oops. --- src/build_test.cc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/build_test.cc b/src/build_test.cc index 2a0fa0f..ea1c493 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1481,7 +1481,10 @@ TEST_F(BuildWithDepsLogTest, Straightforward) { } } -/// Verify that obsolete deps still cause a rebuild. +/// Verify that obsolete dependency info causes a rebuild. +/// 1) Run a successful build where everything has time t, record deps. +/// 2) Move input/output to time t+1 -- despite files in alignment, +/// should still need to rebuild due to deps at older time. TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { string err; // Note: in1 was created by the superclass SetUp(). @@ -1490,13 +1493,9 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { " deps = gcc\n" " depfile = in1.d\n"; { - // Create the obsolete deps, then run a build to incorporate them. - // The idea is that the inputs/outputs are newer than the logged - // deps. - fs_.Create("in1.d", "out: "); - fs_.Tick(); - + // Run an ordinary build that gathers dependencies. fs_.Create("in1", ""); + fs_.Create("in1.d", "out: "); State state; ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); @@ -1514,16 +1513,18 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { EXPECT_TRUE(builder.Build(&err)); EXPECT_EQ("", err); - fs_.Create("out", ""); - // The deps file should have been removed. - EXPECT_EQ(0, fs_.Stat("in1.d")); deps_log.Close(); builder.command_runner_.release(); } - // Now we should be in a situation where in1/out2 both have recent - // timestamps but the deps are old. Verify we rebuild. + // Push all files one tick forward so that only the deps are out + // of date. fs_.Tick(); + fs_.Create("in1", ""); + fs_.Create("out", ""); + + // The deps file should have been removed, so no need to timestamp it. + EXPECT_EQ(0, fs_.Stat("in1.d")); { State state; @@ -1540,8 +1541,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { EXPECT_TRUE(builder.AddTarget("out", &err)); ASSERT_EQ("", err); - // Recreate the deps here just to prove the old recorded deps are - // the problem. + // Recreate the deps file here because the build expects them to exist. fs_.Create("in1.d", "out: "); EXPECT_TRUE(builder.Build(&err)); -- cgit v0.12 From 4c4460e0cae20d64eb2d6240e10cdb4d0ea757fd Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 11 Apr 2013 12:46:34 -0700 Subject: windows: always extract dependencies, even on compile failure We always want to filter /showIncludes output. --- src/build.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/build.cc b/src/build.cc index 199749f..fed3065 100644 --- a/src/build.cc +++ b/src/build.cc @@ -710,14 +710,16 @@ void Builder::FinishCommand(CommandRunner::Result* result) { Edge* edge = result->edge; // First try to extract dependencies from the result, if any. - // This must happen first as it filters the command output and - // can fail, which makes the command fail from a build perspective. - + // This must happen first as it filters the command output (we want + // to filter /showIncludes output, even on compile failure) and + // extraction itself can fail, which makes the command fail from a + // build perspective. vector deps_nodes; string deps_type = edge->GetBinding("deps"); - if (result->success() && !deps_type.empty()) { + if (!deps_type.empty()) { string extract_err; - if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err)) { + if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err) && + result->success()) { if (!result->output.empty()) result->output.append("\n"); result->output.append(extract_err); -- cgit v0.12 From 4497d0f247d1772c75949668d9c7351ae54565ce Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 11 Apr 2013 13:50:36 -0700 Subject: add HACKING note on cl.exe-via-wine --- HACKING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HACKING.md b/HACKING.md index 885d2d7..11edf74 100644 --- a/HACKING.md +++ b/HACKING.md @@ -172,3 +172,9 @@ Then run: * Build `ninja.exe` using a Linux ninja binary: `/path/to/linux/ninja` * Run: `./ninja.exe` (implicitly runs through wine(!)) +### Using Microsoft compilers on Linux (extremely flaky) + +The trick is to install just the compilers, and not all of Visual Studio, +by following [these instructions][win7sdk]. + +[win7sdk]: http://www.kegel.com/wine/cl-howto-win7sdk.html -- cgit v0.12 From 368f5bc64ddd2dcea2662d6c932157314be3e09b Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 11 Apr 2013 14:05:13 -0700 Subject: bootstrap: make --windows usable for cl.exe-on-wine --- bootstrap.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index f83b2d0..8feaf79 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -33,7 +33,7 @@ parser.add_option('--x64', action='store_true', help='force 64-bit build (Windows)',) # TODO: make this --platform to match configure.py. parser.add_option('--windows', action='store_true', - help='force native Windows build (when using Cygwin Python)', + help='force native Windows build', default=sys.platform.startswith('win32')) (options, conf_args) = parser.parse_args() @@ -82,15 +82,17 @@ for src in glob.glob('src/*.cc'): if options.windows: sources.append('src/getopt.c') -vcdir = os.environ.get('VCINSTALLDIR') -if vcdir: - if options.x64: - cl = [os.path.join(vcdir, 'bin', 'x86_amd64', 'cl.exe')] - if not os.path.exists(cl[0]): - cl = [os.path.join(vcdir, 'bin', 'amd64', 'cl.exe')] - else: - cl = [os.path.join(vcdir, 'bin', 'cl.exe')] - args = cl + ['/nologo', '/EHsc', '/DNOMINMAX'] +if options.windows: + cl = 'cl' + vcdir = os.environ.get('VCINSTALLDIR') + if vcdir: + if options.x64: + cl = os.path.join(vcdir, 'bin', 'x86_amd64', 'cl.exe') + if not os.path.exists(cl): + cl = os.path.join(vcdir, 'bin', 'amd64', 'cl.exe') + else: + cl = os.path.join(vcdir, 'bin', 'cl.exe') + args = [cl, '/nologo', '/EHsc', '/DNOMINMAX'] else: args = shlex.split(os.environ.get('CXX', 'g++')) cflags.extend(['-Wno-deprecated', @@ -107,7 +109,7 @@ binary = 'ninja.bootstrap' if options.windows: binary = 'ninja.bootstrap.exe' args.extend(sources) -if vcdir: +if options.windows: args.extend(['/link', '/out:' + binary]) else: args.extend(['-o', binary]) -- cgit v0.12 From 1b9c7bb51a97e6d3738cdad2d5520295b667ef31 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 16 Apr 2013 12:18:39 -0700 Subject: Note that version 1.3 of the vim syntax file has been sent upstream. Also fix an issue noticed by Bram during integration: Make sure to set "cpo" correctly. This matches other vim syntax files and is required to make sure that the backslash continuation works with all .vimrc files (see `:help line-continuation`). --- misc/ninja.vim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/misc/ninja.vim b/misc/ninja.vim index ec120d8..d813267 100644 --- a/misc/ninja.vim +++ b/misc/ninja.vim @@ -2,9 +2,9 @@ " Language: ninja build file as described at " http://martine.github.com/ninja/manual.html " Version: 1.3 -" Last Change: 2013/04/09 +" Last Change: 2013/04/16 " Maintainer: Nicolas Weber -" Version 1.2 of this script is in the upstream vim repository and will be +" Version 1.3 of this script is in the upstream vim repository and will be " included in the next vim release. If you change this, please send your change " upstream. @@ -16,6 +16,9 @@ if exists("b:current_syntax") finish endif +let s:cpo_save = &cpo +set cpo&vim + syn case match syn match ninjaComment /#.*/ contains=@Spell @@ -73,3 +76,6 @@ hi def link ninjaSimpleVar ninjaVar hi def link ninjaVar Identifier let b:current_syntax = "ninja" + +let &cpo = s:cpo_save +unlet s:cpo_save -- cgit v0.12 From bce105ead963eef8822e4b7a38e9e46ec8d06d2b Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Wed, 17 Apr 2013 13:05:50 -0700 Subject: reset count in LaconicPrinter for when using --gtest_repeat --- src/ninja_test.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 02f2c6b..b772441 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -38,6 +38,11 @@ class LaconicPrinter : public testing::EmptyTestEventListener { test_count_ = unit_test.test_to_run_count(); } + virtual void OnTestIterationStart(const testing::UnitTest& test_info, + int iteration) { + tests_started_ = 0; + } + virtual void OnTestStart(const testing::TestInfo& test_info) { ++tests_started_; printer_.Print(StringPrintf("[%d/%d] %s.%s", tests_started_, test_count_, -- cgit v0.12 From c5114830b854335c8e7229f28741f1d3fd6c3840 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 15 Apr 2013 14:41:10 -0700 Subject: fix --profile=pprof on newer ubuntus The --as-needed default for ld would drop -lprofiler. --- configure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.py b/configure.py index c526c1a..bfcf2ec 100755 --- a/configure.py +++ b/configure.py @@ -166,7 +166,8 @@ else: cflags.append('-pg') ldflags.append('-pg') elif options.profile == 'pprof': - libs.append('-lprofiler') + cflags.append('-fno-omit-frame-pointer') + libs.extend(['-Wl,--no-as-needed', '-lprofiler']) def shell_escape(str): """Escape str such that it's interpreted as a single argument by -- cgit v0.12 From 9e0443bee9c9d21f795f95f4585d578e26336dad Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Wed, 17 Apr 2013 17:30:06 -0700 Subject: also print iteration for gtest_repeat=-1 --- src/ninja_test.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ninja_test.cc b/src/ninja_test.cc index b772441..f091cc8 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -33,7 +33,7 @@ std::string StringPrintf(const char* format, ...) { /// A test result printer that's less wordy than gtest's default. class LaconicPrinter : public testing::EmptyTestEventListener { public: - LaconicPrinter() : tests_started_(0), test_count_(0) {} + LaconicPrinter() : tests_started_(0), test_count_(0), iteration_(0) {} virtual void OnTestProgramStart(const testing::UnitTest& unit_test) { test_count_ = unit_test.test_to_run_count(); } @@ -41,13 +41,20 @@ class LaconicPrinter : public testing::EmptyTestEventListener { virtual void OnTestIterationStart(const testing::UnitTest& test_info, int iteration) { tests_started_ = 0; + iteration_ = iteration; } virtual void OnTestStart(const testing::TestInfo& test_info) { ++tests_started_; - printer_.Print(StringPrintf("[%d/%d] %s.%s", tests_started_, test_count_, - test_info.test_case_name(), test_info.name()), - LinePrinter::ELIDE); + printer_.Print( + StringPrintf("[%d/%d%s] %s.%s", + tests_started_, + test_count_, + iteration_ ? StringPrintf(" iter %d", iteration_).c_str() + : "", + test_info.test_case_name(), + test_info.name()), + LinePrinter::ELIDE); } virtual void OnTestPartResult( @@ -67,6 +74,7 @@ class LaconicPrinter : public testing::EmptyTestEventListener { LinePrinter printer_; int tests_started_; int test_count_; + int iteration_; }; int main(int argc, char **argv) { -- cgit v0.12 From 9b6b1fd44a589c3725947280ff5a2ce8fc50a387 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 18 Apr 2013 11:27:20 -0700 Subject: drop std:: qualifiers on more stl datatypes We "using namespace std" anywhere we need a std::string or a std::vector. --- src/build.h | 3 +-- src/edit_distance.cc | 4 ++-- src/line_printer.cc | 2 +- src/line_printer.h | 5 +++-- src/manifest_parser_test.cc | 2 +- src/ninja_test.cc | 2 +- src/util_test.cc | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/build.h b/src/build.h index 5cbd2a6..2715c0c 100644 --- a/src/build.h +++ b/src/build.h @@ -268,7 +268,7 @@ struct BuildStatus { double rate_; Stopwatch stopwatch_; const size_t N; - std::queue times_; + queue times_; int last_update_; }; @@ -277,4 +277,3 @@ struct BuildStatus { }; #endif // NINJA_BUILD_H_ - diff --git a/src/edit_distance.cc b/src/edit_distance.cc index 22db4fe..cc4483f 100644 --- a/src/edit_distance.cc +++ b/src/edit_distance.cc @@ -32,8 +32,8 @@ int EditDistance(const StringPiece& s1, int m = s1.len_; int n = s2.len_; - std::vector previous(n + 1); - std::vector current(n + 1); + vector previous(n + 1); + vector current(n + 1); for (int i = 0; i <= n; ++i) previous[i] = i; diff --git a/src/line_printer.cc b/src/line_printer.cc index 751fb07..a75eb05 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -42,7 +42,7 @@ LinePrinter::LinePrinter() : have_blank_line_(true) { #endif } -void LinePrinter::Print(std::string to_print, LineType type) { +void LinePrinter::Print(string to_print, LineType type) { #ifdef _WIN32 CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(console_, &csbi); diff --git a/src/line_printer.h b/src/line_printer.h index 4226c92..c292464 100644 --- a/src/line_printer.h +++ b/src/line_printer.h @@ -16,6 +16,7 @@ #define NINJA_LINE_PRINTER_H_ #include +using namespace std; /// Prints lines of text, possibly overprinting previously printed lines /// if the terminal supports it. @@ -32,10 +33,10 @@ class LinePrinter { }; /// Overprints the current line. If type is ELIDE, elides to_print to fit on /// one line. - void Print(std::string to_print, LineType type); + void Print(string to_print, LineType type); /// Prints a string on a new line, not overprinting previous output. - void PrintOnNewLine(const std::string& to_print); + void PrintOnNewLine(const string& to_print); private: /// Whether we can do fancy terminal control codes. diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 76d235d..be749f2 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -701,7 +701,7 @@ TEST_F(ParserTest, DefaultStatements) { "default $third\n")); string err; - std::vector nodes = state.DefaultNodes(&err); + vector nodes = state.DefaultNodes(&err); EXPECT_EQ("", err); ASSERT_EQ(3u, nodes.size()); EXPECT_EQ("a", nodes[0]->path()); diff --git a/src/ninja_test.cc b/src/ninja_test.cc index f091cc8..31754f2 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -18,7 +18,7 @@ #include "gtest/gtest.h" #include "line_printer.h" -std::string StringPrintf(const char* format, ...) { +string StringPrintf(const char* format, ...) { const int N = 1024; char buf[N]; diff --git a/src/util_test.cc b/src/util_test.cc index 4776546..1e29053 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -101,7 +101,7 @@ TEST(CanonicalizePath, EmptyResult) { } TEST(CanonicalizePath, UpDir) { - std::string path, err; + string path, err; path = "../../foo/bar.h"; EXPECT_TRUE(CanonicalizePath(&path, &err)); EXPECT_EQ("../../foo/bar.h", path); -- cgit v0.12 From 0275ead49797dc5b5723d89205bafcd7c313a76d Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 19 Apr 2013 14:32:29 -0700 Subject: make DiskInterfaceTest.StatBadPath quiet Add a flag to temporarily suppress error output. Fixes issue #281. --- src/disk_interface.cc | 16 +++++++++++----- src/disk_interface.h | 4 ++++ src/disk_interface_test.cc | 2 ++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/disk_interface.cc b/src/disk_interface.cc index 7c557cd..ee3e99a 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -80,8 +80,10 @@ TimeStamp RealDiskInterface::Stat(const string& path) { // MSDN: "Naming Files, Paths, and Namespaces" // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) { - Error("Stat(%s): Filename longer than %i characters", - path.c_str(), MAX_PATH); + if (!quiet_) { + Error("Stat(%s): Filename longer than %i characters", + path.c_str(), MAX_PATH); + } return -1; } WIN32_FILE_ATTRIBUTE_DATA attrs; @@ -89,8 +91,10 @@ TimeStamp RealDiskInterface::Stat(const string& path) { DWORD err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) return 0; - Error("GetFileAttributesEx(%s): %s", path.c_str(), - GetLastErrorString().c_str()); + if (!quiet_) { + Error("GetFileAttributesEx(%s): %s", path.c_str(), + GetLastErrorString().c_str()); + } return -1; } const FILETIME& filetime = attrs.ftLastWriteTime; @@ -107,7 +111,9 @@ TimeStamp RealDiskInterface::Stat(const string& path) { if (stat(path.c_str(), &st) < 0) { if (errno == ENOENT || errno == ENOTDIR) return 0; - Error("stat(%s): %s", path.c_str(), strerror(errno)); + if (!quiet_) { + Error("stat(%s): %s", path.c_str(), strerror(errno)); + } return -1; } return st.st_mtime; diff --git a/src/disk_interface.h b/src/disk_interface.h index 55f8a21..ff1e21c 100644 --- a/src/disk_interface.h +++ b/src/disk_interface.h @@ -55,12 +55,16 @@ struct DiskInterface { /// Implementation of DiskInterface that actually hits the disk. struct RealDiskInterface : public DiskInterface { + RealDiskInterface() : quiet_(false) {} virtual ~RealDiskInterface() {} virtual TimeStamp Stat(const string& path); virtual bool MakeDir(const string& path); virtual bool WriteFile(const string& path, const string& contents); virtual string ReadFile(const string& path, string* err); virtual int RemoveFile(const string& path); + + /// Whether to print on errors. Used to make a test quieter. + bool quiet_; }; #endif // NINJA_DISK_INTERFACE_H_ diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index 9b43d0f..55822a6 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -60,6 +60,7 @@ TEST_F(DiskInterfaceTest, StatMissingFile) { } TEST_F(DiskInterfaceTest, StatBadPath) { + disk_.quiet_ = true; #ifdef _WIN32 string bad_path("cc:\\foo"); EXPECT_EQ(-1, disk_.Stat(bad_path)); @@ -67,6 +68,7 @@ TEST_F(DiskInterfaceTest, StatBadPath) { string too_long_name(512, 'x'); EXPECT_EQ(-1, disk_.Stat(too_long_name)); #endif + disk_.quiet_ = false; } TEST_F(DiskInterfaceTest, StatExistingFile) { -- cgit v0.12 From c57fd1a903cb342dc414307088352cdb550d3641 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 19 Apr 2013 14:38:23 -0700 Subject: adjust the wording in "multiple rules generate X" warning Ideally we'd detect this at build time and only warn if your build was affected, but that's hard to do. (Really we should just abort when this scenario is detected rather than continuing, but now users are relying on it.) Hopefully improves issue #543. --- src/state.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/state.cc b/src/state.cc index d2d5ebe..9b6160b 100644 --- a/src/state.cc +++ b/src/state.cc @@ -154,7 +154,8 @@ void State::AddOut(Edge* edge, StringPiece path) { edge->outputs_.push_back(node); if (node->in_edge()) { Warning("multiple rules generate %s. " - "build will not be correct; continuing anyway", + "builds involving this target will not be correct; " + "continuing anyway", path.AsString().c_str()); } node->set_in_edge(edge); -- cgit v0.12 From a83020b750e7b3c8c801fcf79ebe94d7dd4840dd Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sun, 21 Apr 2013 17:55:10 -0700 Subject: Don't record deps in dry runs. deps_log() is NULL during dry runs, so this fixes a crash. It also matches ninja 1.2.0's behavior as far as I can tell. Fixes issue #551. --- src/build.cc | 4 +++- src/build_test.cc | 31 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/build.cc b/src/build.cc index fed3065..c9842ce 100644 --- a/src/build.cc +++ b/src/build.cc @@ -715,7 +715,9 @@ void Builder::FinishCommand(CommandRunner::Result* result) { // extraction itself can fail, which makes the command fail from a // build perspective. vector deps_nodes; - string deps_type = edge->GetBinding("deps"); + string deps_type; + if (!config_.dry_run) + deps_type = edge->GetBinding("deps"); if (!deps_type.empty()) { string extract_err; if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err) && diff --git a/src/build_test.cc b/src/build_test.cc index 827998b..68a5142 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -1353,7 +1353,7 @@ TEST_F(BuildTest, PhonyWithNoInputs) { ASSERT_EQ(1u, command_runner_.commands_ran_.size()); } -TEST_F(BuildTest, DepsGccWithEmptyDeps) { +TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" " command = cc\n" @@ -1554,3 +1554,32 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { builder.command_runner_.release(); } } + +TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) { + const char* manifest = + "build out: cat in1\n" + " deps = gcc\n" + " depfile = in1.d\n"; + + fs_.Create("out", ""); + fs_.Tick(); + fs_.Create("in1", ""); + + State state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // The deps log is NULL in dry runs. + config_.dry_run = true; + Builder builder(&state, config_, NULL, NULL, &fs_); + builder.command_runner_.reset(&command_runner_); + command_runner_.commands_ran_.clear(); + + string err; + EXPECT_TRUE(builder.AddTarget("out", &err)); + ASSERT_EQ("", err); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); + + builder.command_runner_.release(); +} -- cgit v0.12 From 01ae2fbae0fc44171ecddcbdeee1a572b477a691 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sun, 21 Apr 2013 22:03:20 -0700 Subject: keep dry run more similar to normal run --- src/build.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/build.cc b/src/build.cc index c9842ce..5cf9d27 100644 --- a/src/build.cc +++ b/src/build.cc @@ -715,9 +715,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { // extraction itself can fail, which makes the command fail from a // build perspective. vector deps_nodes; - string deps_type; - if (!config_.dry_run) - deps_type = edge->GetBinding("deps"); + string deps_type = edge->GetBinding("deps"); if (!deps_type.empty()) { string extract_err; if (!ExtractDeps(result, deps_type, &deps_nodes, &extract_err) && @@ -789,7 +787,7 @@ void Builder::FinishCommand(CommandRunner::Result* result) { restat_mtime); } - if (!deps_type.empty()) { + if (!deps_type.empty() && !config_.dry_run) { assert(edge->outputs_.size() == 1 && "should have been rejected by parser"); Node* out = edge->outputs_[0]; TimeStamp deps_mtime = disk_interface_->Stat(out->path()); -- cgit v0.12 From 1561861eb795bf5a7e59d03a7239efa8bc263bac Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 22 Apr 2013 08:06:37 -0700 Subject: add a .travis.yml, trying out travis-ci.org --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c66d0d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: cpp +compiler: + - gcc + - clang +script: ./bootstrap.py && ./ninja ninja_test && ./ninja_test -- cgit v0.12 From 154a51945008e7ff87fef08f1ed805ddc8946033 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Mon, 22 Apr 2013 08:12:09 -0700 Subject: travis: pull in gtest before building --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c66d0d2..aec9d7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,7 @@ language: cpp compiler: - gcc - clang -script: ./bootstrap.py && ./ninja ninja_test && ./ninja_test +before_install: + - sudo apt-get update -qq + - sudo apt-get install libgtest-dev +script: ./bootstrap.py && ./configure.py --with-gtest=/usr/src/gtest && ./ninja ninja_test && ./ninja_test -- cgit v0.12 From 89bc19b23d31263ea54ba1c802e4e0d03ae70118 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 26 Apr 2013 11:02:08 -0700 Subject: make DepsLogTest actually clean up on teardown I think I commented this out when tracking down a bug and forgot to uncomment it. --- src/deps_log_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index e9cea21..0172d24 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -28,7 +28,7 @@ struct DepsLogTest : public testing::Test { unlink(kTestFilename); } virtual void TearDown() { - //unlink(kTestFilename); + unlink(kTestFilename); } }; -- cgit v0.12 From a4c33ea48bdbecb3b0176681885c0192599b01fd Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 26 Apr 2013 11:21:57 -0700 Subject: add a test for truncated deps This doesn't yet exhibit the full problems with truncation, but it's a start. --- src/deps_log.h | 1 + src/deps_log_test.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/deps_log.h b/src/deps_log.h index 99b006e..4b357a8 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -86,6 +86,7 @@ struct DepsLog { /// Used for tests. const vector& nodes() const { return nodes_; } + const vector& deps() const { return deps_; } private: // Write a node name record, assigning it an id. diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 0172d24..40539a7 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -218,4 +218,64 @@ TEST_F(DepsLogTest, InvalidHeader) { } } +// Simulate what happens if a write gets interrupted and the resulting +// file is truncated. +TEST_F(DepsLogTest, Truncated) { + // Create a file with some entries. + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + + deps.clear(); + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar2.h")); + log.RecordDeps(state.GetNode("out2.o"), 2, deps); + + log.Close(); + } + + // Get the file size. + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + + // Try reloading at truncated sizes. + // Track how many nodes/deps were found; they should decrease with + // smaller sizes. + int node_count = 5; + int deps_count = 2; + for (int size = (int)st.st_size; size > 0; --size) { + ASSERT_EQ(0, truncate(kTestFilename, size)); + + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); + if (!err.empty()) { + // At some point the log will be so short as to be unparseable. + break; + } + + ASSERT_GE(node_count, log.nodes().size()); + node_count = log.nodes().size(); + + // Count how many non-NULL deps entries there are. + int new_deps_count = 0; + for (vector::const_iterator i = log.deps().begin(); + i != log.deps().end(); ++i) { + if (*i) + ++new_deps_count; + } + ASSERT_GE(deps_count, new_deps_count); + deps_count = new_deps_count; + } +} + } // anonymous namespace -- cgit v0.12 From 896b81c1fe50b11012a34365a11b8fcaba33350f Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 26 Apr 2013 11:25:21 -0700 Subject: disable SubprocessTest.SetWithLots on travis-ci We run into fork() limits on their VM. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index aec9d7f..d7bee6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ compiler: before_install: - sudo apt-get update -qq - sudo apt-get install libgtest-dev -script: ./bootstrap.py && ./configure.py --with-gtest=/usr/src/gtest && ./ninja ninja_test && ./ninja_test +script: ./bootstrap.py && ./configure.py --with-gtest=/usr/src/gtest && ./ninja ninja_test && ./ninja_test --gtest_filter=-SubprocessTest.SetWithLots -- cgit v0.12 From b549d18748a82d2a0e0efccb5ac6d9e3714f5234 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 27 Apr 2013 13:39:04 -0700 Subject: deps log: recover on truncated entry If a read fails while reading an entry, truncate the log to the last successfully read entry. This prevents corruption when a subsequent run appends another entry. --- src/deps_log.cc | 41 +++++++++++++++++++++++++----- src/deps_log_test.cc | 71 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index ceb75ce..5e5bba9 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -152,15 +152,23 @@ bool DepsLog::Load(const string& path, State* state, string* err) { return true; } + long offset; + bool read_failed = false; for (;;) { + offset = ftell(f); + uint16_t size; - if (fread(&size, 2, 1, f) < 1) + if (fread(&size, 2, 1, f) < 1) { + read_failed = true; break; + } bool is_deps = (size >> 15) != 0; size = size & 0x7FFF; - if (fread(buf, size, 1, f) < 1) + if (fread(buf, size, 1, f) < 1) { + read_failed = true; break; + } if (is_deps) { assert(size % 4 == 0); @@ -195,16 +203,37 @@ bool DepsLog::Load(const string& path, State* state, string* err) { nodes_.push_back(node); } } - if (ferror(f)) { - *err = strerror(ferror(f)); - return false; + + if (read_failed) { + // An error occurred while loading; try to recover by truncating the + // file to the last fully-read record. + if (ferror(f)) { + *err = strerror(ferror(f)); + } else { + *err = "premature end of file"; + } + fclose(f); + + if (truncate(path.c_str(), offset) < 0) { + *err = strerror(errno); + return false; + } + + // The truncate succeeded; we'll just report the load error as a + // warning because the build can proceed. + *err += "; recovering"; + return true; } + fclose(f); + return true; } DepsLog::Deps* DepsLog::GetDeps(Node* node) { - if (node->id() < 0) + // Abort if the node has no id (never referenced in the deps) or if + // there's no deps recorded for the node. + if (node->id() < 0 || node->id() >= deps_.size()) return NULL; return deps_[node->id()]; } diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 40539a7..9623d17 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -218,8 +218,7 @@ TEST_F(DepsLogTest, InvalidHeader) { } } -// Simulate what happens if a write gets interrupted and the resulting -// file is truncated. +// Simulate what happens when loading a truncated log file. TEST_F(DepsLogTest, Truncated) { // Create a file with some entries. { @@ -263,7 +262,7 @@ TEST_F(DepsLogTest, Truncated) { break; } - ASSERT_GE(node_count, log.nodes().size()); + ASSERT_GE(node_count, (int)log.nodes().size()); node_count = log.nodes().size(); // Count how many non-NULL deps entries there are. @@ -278,4 +277,70 @@ TEST_F(DepsLogTest, Truncated) { } } +// Run the truncation-recovery logic. +TEST_F(DepsLogTest, TruncatedRecovery) { + // Create a file with some entries. + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar.h")); + log.RecordDeps(state.GetNode("out.o"), 1, deps); + + deps.clear(); + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar2.h")); + log.RecordDeps(state.GetNode("out2.o"), 2, deps); + + log.Close(); + } + + // Shorten the file, corrupting the last record. + struct stat st; + ASSERT_EQ(0, stat(kTestFilename, &st)); + ASSERT_EQ(0, truncate(kTestFilename, st.st_size - 2)); + + // Load the file again, add an entry. + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); + ASSERT_EQ("premature end of file; recovering", err); + err.clear(); + + // The truncated entry should've been discarded. + EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o"))); + + EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); + ASSERT_EQ("", err); + + // Add a new entry. + vector deps; + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("bar2.h")); + log.RecordDeps(state.GetNode("out2.o"), 3, deps); + + log.Close(); + } + + // Load the file a third time to verify appending after a mangled + // entry doesn't break things. + { + State state; + DepsLog log; + string err; + EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); + + // The truncated entry should exist. + DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o")); + ASSERT_TRUE(deps); + } +} + } // anonymous namespace -- cgit v0.12 From f4226200f27e26dfef8cc9f8bc8c6e179d285edd Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 27 Apr 2013 14:27:24 -0700 Subject: fix warning --- src/deps_log.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 5e5bba9..eb1ea2b 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -233,7 +233,7 @@ bool DepsLog::Load(const string& path, State* state, string* err) { DepsLog::Deps* DepsLog::GetDeps(Node* node) { // Abort if the node has no id (never referenced in the deps) or if // there's no deps recorded for the node. - if (node->id() < 0 || node->id() >= deps_.size()) + if (node->id() < 0 || node->id() >= (int)deps_.size()) return NULL; return deps_[node->id()]; } -- cgit v0.12 From 3699cb6aa2fb29125bf725cb50fbcc593da8a8a1 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Sat, 27 Apr 2013 14:37:26 -0700 Subject: don't count eof as truncated --- src/deps_log.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index eb1ea2b..c52503b 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -159,7 +159,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { uint16_t size; if (fread(&size, 2, 1, f) < 1) { - read_failed = true; + if (!feof(f)) + read_failed = true; break; } bool is_deps = (size >> 15) != 0; -- cgit v0.12 From 2b26bf2bda4b13e6c560c07e7ab20bf6abfed53e Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 29 Apr 2013 00:36:34 -0700 Subject: Don't crash during deps log recompaction if there's more than one deps entry. Part of issue #554. --- src/deps_log.cc | 2 ++ src/deps_log_test.cc | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/deps_log.cc b/src/deps_log.cc index c52503b..5ecdeef 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -257,6 +257,8 @@ bool DepsLog::Recompact(const string& path, string* err) { // Write out all deps again. for (int old_id = 0; old_id < (int)deps_.size(); ++old_id) { Deps* deps = deps_[old_id]; + if (!deps) continue; // If nodes_[old_id] is a leaf, it has no deps. + if (!new_log.RecordDeps(nodes_[old_id], deps->mtime, deps->node_count, deps->nodes)) { new_log.Close(); diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 9623d17..ac4bdeb 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -137,6 +137,12 @@ TEST_F(DepsLogTest, Recompact) { deps.push_back(state.GetNode("foo.h")); deps.push_back(state.GetNode("bar.h")); log.RecordDeps(state.GetNode("out.o"), 1, deps); + + deps.clear(); + deps.push_back(state.GetNode("foo.h")); + deps.push_back(state.GetNode("baz.h")); + log.RecordDeps(state.GetNode("other_out.o"), 1, deps); + log.Close(); struct stat st; -- cgit v0.12 From 92f48ca30762447864ef808cf62ccd1ca76cd23d Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 29 Apr 2013 00:42:21 -0700 Subject: Make sure that recompaction writes a pristine new depslog. ...even if a prior run of DepsLog::Recompact() exited without cleaning up, for example due to a crash or because someone added a `return true;` in the middle of the function while debugging. Or because someone hits ctrl-c during deps log recompaction. No test, because I can't think of a way to trigger this scenario programmatically. Part of issue #554. --- src/deps_log.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/deps_log.cc b/src/deps_log.cc index c52503b..d0d0202 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -244,6 +244,11 @@ bool DepsLog::Recompact(const string& path, string* err) { printf("Recompacting deps...\n"); string temp_path = path + ".recompact"; + + // OpenForWrite() opens for append. Make sure it's not appending to a + // left-over file from a previous recompaction attempt that crashed somehow. + unlink(temp_path.c_str()); + DepsLog new_log; if (!new_log.OpenForWrite(temp_path, err)) return false; -- cgit v0.12 From bb10325339898d2833a9f24e9e0252b2c257beb1 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 29 Apr 2013 08:57:31 -0700 Subject: Introduce a Truncate() function that works on POSIX and Windows. Hopefully fixes the build on Windows. --- src/build_log_test.cc | 10 +--------- src/deps_log.cc | 4 +--- src/deps_log_test.cc | 4 ++-- src/util.cc | 20 ++++++++++++++++++++ src/util.h | 3 +++ 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/build_log_test.cc b/src/build_log_test.cc index 2dd6500..4639bc9 100644 --- a/src/build_log_test.cc +++ b/src/build_log_test.cc @@ -143,15 +143,7 @@ TEST_F(BuildLogTest, Truncate) { log2.RecordCommand(state_.edges_[1], 20, 25); log2.Close(); -#ifndef _WIN32 - ASSERT_EQ(0, truncate(kTestFilename, size)); -#else - int fh; - fh = _sopen(kTestFilename, _O_RDWR | _O_CREAT, _SH_DENYNO, - _S_IREAD | _S_IWRITE); - ASSERT_EQ(0, _chsize(fh, size)); - _close(fh); -#endif + ASSERT_TRUE(Truncate(kTestFilename, size, &err)); BuildLog log3; err.clear(); diff --git a/src/deps_log.cc b/src/deps_log.cc index c52503b..9866540 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -215,10 +215,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { } fclose(f); - if (truncate(path.c_str(), offset) < 0) { - *err = strerror(errno); + if (!Truncate(path.c_str(), offset, err)) return false; - } // The truncate succeeded; we'll just report the load error as a // warning because the build can proceed. diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 9623d17..b3d6b74 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -251,11 +251,11 @@ TEST_F(DepsLogTest, Truncated) { int node_count = 5; int deps_count = 2; for (int size = (int)st.st_size; size > 0; --size) { - ASSERT_EQ(0, truncate(kTestFilename, size)); + string err; + ASSERT_TRUE(Truncate(kTestFilename, size, &err)); State state; DepsLog log; - string err; EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); if (!err.empty()) { // At some point the log will be so short as to be unparseable. diff --git a/src/util.cc b/src/util.cc index 91e8fad..e78dda3 100644 --- a/src/util.cc +++ b/src/util.cc @@ -29,6 +29,7 @@ #include #ifndef _WIN32 +#include #include #endif @@ -354,3 +355,22 @@ string ElideMiddle(const string& str, size_t width) { } return result; } + +bool Truncate(const string& path, size_t size, string* err) { +#ifdef _WIN32 + int fh; + fh = _sopen(kTestFilename, _O_RDWR | _O_CREAT, _SH_DENYNO, + _S_IREAD | _S_IWRITE); + int success = _chsize(fh, size); + _close(fh); +#else + int success = truncate(path.c_str(), size); +#endif + // Both truncate() and _chsize() return 0 on success and set errno and return + // -1 on failure. + if (success < 0) { + *err = strerror(errno); + return false; + } + return true; +} diff --git a/src/util.h b/src/util.h index 07381a1..9740565 100644 --- a/src/util.h +++ b/src/util.h @@ -76,6 +76,9 @@ double GetLoadAverage(); /// exceeds @a width. string ElideMiddle(const string& str, size_t width); +/// Truncates a file to the given size. +bool Truncate(const string& path, size_t size, string* err); + #ifdef _MSC_VER #define snprintf _snprintf #define fileno _fileno -- cgit v0.12 From e83743d8839a9bfcf3835448cc7b1c39c6e356a7 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 29 Apr 2013 10:20:55 -0700 Subject: Fix Windows build more. --- src/util.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.cc b/src/util.cc index e78dda3..127f8b1 100644 --- a/src/util.cc +++ b/src/util.cc @@ -359,7 +359,7 @@ string ElideMiddle(const string& str, size_t width) { bool Truncate(const string& path, size_t size, string* err) { #ifdef _WIN32 int fh; - fh = _sopen(kTestFilename, _O_RDWR | _O_CREAT, _SH_DENYNO, + fh = _sopen(path.c_str(), _O_RDWR | _O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); int success = _chsize(fh, size); _close(fh); -- cgit v0.12 From 1f7ae6fec9588b196b67251ff7aa3ba49fd6c6c0 Mon Sep 17 00:00:00 2001 From: Patrick von Reth Date: Tue, 30 Apr 2013 09:44:11 +0200 Subject: added missing windows include --- src/util.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util.cc b/src/util.cc index 127f8b1..a215dd2 100644 --- a/src/util.cc +++ b/src/util.cc @@ -17,6 +17,7 @@ #ifdef _WIN32 #include #include +#include #endif #include -- cgit v0.12 From 4c552c2c3cbc07acce9c1a379fee054a3f680100 Mon Sep 17 00:00:00 2001 From: Patrick von Reth Date: Tue, 30 Apr 2013 08:29:13 -0700 Subject: share platform support between configure/bootstrap --- bootstrap.py | 29 +++++++++++--------- configure.py | 77 ++++++++++++++++++++++++------------------------------ platform_helper.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 55 deletions(-) create mode 100644 platform_helper.py diff --git a/bootstrap.py b/bootstrap.py index 8feaf79..a7a8ba6 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -23,20 +23,26 @@ import errno import shlex import shutil import subprocess +import platform_helper os.chdir(os.path.dirname(os.path.abspath(__file__))) parser = OptionParser() + parser.add_option('--verbose', action='store_true', help='enable verbose build',) parser.add_option('--x64', action='store_true', help='force 64-bit build (Windows)',) # TODO: make this --platform to match configure.py. -parser.add_option('--windows', action='store_true', - help='force native Windows build', - default=sys.platform.startswith('win32')) +parser.add_option('--platform', + help='target platform (' + '/'.join(platform_helper.platforms()) + ')', + choices=platform_helper.platforms()) (options, conf_args) = parser.parse_args() + +platform = platform_helper.Platform(options.platform) +conf_args.append("--platform=" + platform.platform()) + def run(*args, **kwargs): returncode = subprocess.call(*args, **kwargs) if returncode != 0: @@ -46,7 +52,7 @@ def run(*args, **kwargs): # g++ call as well as in the later configure.py. cflags = os.environ.get('CFLAGS', '').split() ldflags = os.environ.get('LDFLAGS', '').split() -if sys.platform.startswith('freebsd'): +if platform.is_freebsd(): cflags.append('-I/usr/local/include') ldflags.append('-L/usr/local/lib') @@ -70,7 +76,7 @@ for src in glob.glob('src/*.cc'): if filename == 'browse.cc': # Depends on generated header. continue - if options.windows: + if platform.is_windows(): if src.endswith('-posix.cc'): continue else: @@ -79,10 +85,10 @@ for src in glob.glob('src/*.cc'): sources.append(src) -if options.windows: +if platform.is_windows(): sources.append('src/getopt.c') -if options.windows: +if platform.is_msvc(): cl = 'cl' vcdir = os.environ.get('VCINSTALLDIR') if vcdir: @@ -98,18 +104,17 @@ else: cflags.extend(['-Wno-deprecated', '-DNINJA_PYTHON="' + sys.executable + '"', '-DNINJA_BOOTSTRAP']) - if options.windows: + if platform.is_windows(): cflags.append('-D_WIN32_WINNT=0x0501') - conf_args.append("--platform=mingw") if options.x64: cflags.append('-m64') args.extend(cflags) args.extend(ldflags) binary = 'ninja.bootstrap' -if options.windows: +if platform.is_windows(): binary = 'ninja.bootstrap.exe' args.extend(sources) -if options.windows: +if platform.is_msvc(): args.extend(['/link', '/out:' + binary]) else: args.extend(['-o', binary]) @@ -127,7 +132,7 @@ verbose = [] if options.verbose: verbose = ['-v'] -if options.windows: +if platform.is_windows(): print('Building ninja using itself...') run([sys.executable, 'configure.py'] + conf_args) run(['./' + binary] + verbose) diff --git a/configure.py b/configure.py index bfcf2ec..1284deb 100755 --- a/configure.py +++ b/configure.py @@ -24,19 +24,19 @@ from __future__ import print_function from optparse import OptionParser import os import sys +import platform_helper sys.path.insert(0, 'misc') import ninja_syntax parser = OptionParser() -platforms = ['linux', 'freebsd', 'solaris', 'mingw', 'windows'] profilers = ['gmon', 'pprof'] parser.add_option('--platform', - help='target platform (' + '/'.join(platforms) + ')', - choices=platforms) + help='target platform (' + '/'.join(platform_helper.platforms()) + ')', + choices=platform_helper.platforms()) parser.add_option('--host', - help='host platform (' + '/'.join(platforms) + ')', - choices=platforms) + help='host platform (' + '/'.join(platform_helper.platforms()) + ')', + choices=platform_helper.platforms()) parser.add_option('--debug', action='store_true', help='enable debugging extras',) parser.add_option('--profile', metavar='TYPE', @@ -52,20 +52,11 @@ if args: print('ERROR: extra unparsed command-line arguments:', args) sys.exit(1) -platform = options.platform -if platform is None: - platform = sys.platform - if platform.startswith('linux'): - platform = 'linux' - elif platform.startswith('freebsd'): - platform = 'freebsd' - elif platform.startswith('solaris'): - platform = 'solaris' - elif platform.startswith('mingw'): - platform = 'mingw' - elif platform.startswith('win'): - platform = 'windows' -host = options.host or platform +platform = platform_helper.Platform(options.platform) +if options.host: + host = platform_helper.Platform(options.host) +else: + host = platform BUILD_FILENAME = 'build.ninja' buildfile = open(BUILD_FILENAME, 'w') @@ -85,7 +76,7 @@ n.newline() CXX = configure_env.get('CXX', 'g++') objext = '.o' -if platform == 'windows': +if platform.is_msvc(): CXX = 'cl' objext = '.obj' @@ -100,7 +91,7 @@ def cc(name, **kwargs): def cxx(name, **kwargs): return n.build(built(name + objext), 'cxx', src(name + '.cc'), **kwargs) def binary(name): - if platform in ('mingw', 'windows'): + if platform.is_windows(): exe = name + '.exe' n.build(name, 'phony', exe) return exe @@ -108,12 +99,12 @@ def binary(name): n.variable('builddir', 'build') n.variable('cxx', CXX) -if platform == 'windows': +if platform.is_msvc(): n.variable('ar', 'link') else: n.variable('ar', configure_env.get('AR', 'ar')) -if platform == 'windows': +if platform.is_msvc(): cflags = ['/nologo', # Don't print startup banner. '/Zi', # Create pdb with debug info. '/W4', # Highest warning level. @@ -149,17 +140,17 @@ else: cflags += ['-O2', '-DNDEBUG'] if 'clang' in os.path.basename(CXX): cflags += ['-fcolor-diagnostics'] - if platform == 'mingw': + if platform.is_mingw(): cflags += ['-D_WIN32_WINNT=0x0501'] ldflags = ['-L$builddir'] libs = [] -if platform == 'mingw': +if platform.is_mingw(): cflags.remove('-fvisibility=hidden'); ldflags.append('-static') -elif platform == 'sunos5': +elif platform.is_sunos5(): cflags.remove('-fvisibility=hidden') -elif platform == 'windows': +elif platform.is_msvc(): pass else: if options.profile == 'gmon': @@ -174,7 +165,7 @@ def shell_escape(str): the shell.""" # This isn't complete, but it's just enough to make NINJA_PYTHON work. - if platform in ('windows', 'mingw'): + if platform.is_windows(): return str if '"' in str: return "'%s'" % str.replace("'", "\\'") @@ -188,7 +179,7 @@ if 'LDFLAGS' in configure_env: n.variable('ldflags', ' '.join(shell_escape(flag) for flag in ldflags)) n.newline() -if platform == 'windows': +if platform.is_msvc(): n.rule('cxx', command='$cxx /showIncludes $cflags -c $in /Fo$out', description='CXX $out', @@ -201,11 +192,11 @@ else: description='CXX $out') n.newline() -if host == 'windows': +if host.is_msvc(): n.rule('ar', command='lib /nologo /ltcg /out:$out $in', description='LIB $out') -elif host == 'mingw': +elif host.is_mingw(): n.rule('ar', command='cmd /c $ar cqs $out.tmp $in && move /Y $out.tmp $out', description='AR $out') @@ -215,7 +206,7 @@ else: description='AR $out') n.newline() -if platform == 'windows': +if platform.is_msvc(): n.rule('link', command='$cxx $in $libs /nologo /link $ldflags /out:$out', description='LINK $out') @@ -227,7 +218,7 @@ n.newline() objs = [] -if platform not in ('solaris', 'mingw', 'windows'): +if not platform.is_windows() and not platform.is_solaris(): n.comment('browse_py.h is used to inline browse.py.') n.rule('inline', command='src/inline.sh $varname < $in > $out', @@ -280,24 +271,24 @@ for name in ['build', 'util', 'version']: objs += cxx(name) -if platform in ('mingw', 'windows'): +if platform.is_windows(): for name in ['subprocess-win32', 'includes_normalize-win32', 'msvc_helper-win32', 'msvc_helper_main-win32']: objs += cxx(name) - if platform == 'windows': + if platform.is_msvc(): objs += cxx('minidump-win32') objs += cc('getopt') else: objs += cxx('subprocess-posix') -if platform == 'windows': +if platform.is_msvc(): ninja_lib = n.build(built('ninja.lib'), 'ar', objs) else: ninja_lib = n.build(built('libninja.a'), 'ar', objs) n.newline() -if platform == 'windows': +if platform.is_msvc(): libs.append('ninja.lib') else: libs.append('-lninja') @@ -322,7 +313,7 @@ if options.with_gtest: path = options.with_gtest gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include')) - if platform == 'windows': + if platform.is_msvc(): gtest_cflags = '/nologo /EHsc /Zi /D_VARIADIC_MAX=10 ' + gtest_all_incs else: gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs @@ -333,7 +324,7 @@ if options.with_gtest: test_cflags.append('-I%s' % os.path.join(path, 'include')) else: # Use gtest from system. - if platform == 'windows': + if platform.is_msvc(): test_libs.extend(['gtest_main.lib', 'gtest.lib']) else: test_libs.extend(['-lgtest_main', '-lgtest']) @@ -355,11 +346,11 @@ for name in ['build_log_test', 'test', 'util_test']: objs += cxx(name, variables=[('cflags', '$test_cflags')]) -if platform in ('windows', 'mingw'): +if platform.is_windows(): for name in ['includes_normalize_test', 'msvc_helper_test']: objs += cxx(name, variables=[('cflags', test_cflags)]) -if platform != 'mingw' and platform != 'windows': +if not platform.is_windows(): test_libs.append('-lpthread') ninja_test = n.build(binary('ninja_test'), 'link', objs, implicit=ninja_lib, variables=[('ldflags', test_ldflags), @@ -422,7 +413,7 @@ n.build('doxygen', 'doxygen', doc('doxygen.config'), implicit=mainpage) n.newline() -if host != 'mingw': +if not host.is_mingw(): n.comment('Regenerate build files if build script changes.') n.rule('configure', command='${configure_env}%s configure.py $configure_args' % @@ -435,7 +426,7 @@ if host != 'mingw': n.default(ninja) n.newline() -if host == 'linux': +if host.is_linux(): n.comment('Packaging') n.rule('rpmbuild', command="misc/packaging/rpmbuild.sh", diff --git a/platform_helper.py b/platform_helper.py new file mode 100644 index 0000000..052c969 --- /dev/null +++ b/platform_helper.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# Copyright 2011 Google Inc. +# Copyright 2013 Patrick von Reth +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +def platforms(): + return ['linux', 'freebsd', 'solaris', 'sunos5', 'mingw', 'msvc'] + +class Platform( object ): + def __init__( self, platform): + self._platform = platform + if not self._platform is None: + return + self._platform = sys.platform + if self._platform.startswith('linux'): + self._platform = 'linux' + elif self._platform.startswith('freebsd'): + self._platform = 'freebsd' + elif self._platform.startswith('solaris'): + self._platform = 'solaris' + elif self._platform.startswith('mingw'): + self._platform = 'mingw' + elif self._platform.startswith('win'): + self._platform = 'msvc' + + + def platform(self): + return self._platform + + def is_linux(self): + return self._platform == 'linux' + + def is_mingw(self): + return self._platform == 'mingw' + + def is_msvc(self): + return self._platform == 'msvc' + + def is_windows(self): + return self.is_mingw() or self.is_msvc() + + def is_solaris(self): + return self._platform == 'solaris' + + def is_freebsd(self): + return self._platform == 'freebsd' + + def is_sunos5(self): + return self._platform == 'sunos5' -- cgit v0.12 From 8d03d2cf7e572b65cd242a44081b514338e4fd6b Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 30 Apr 2013 08:40:56 -0700 Subject: Move some DepsLog::Deps initialization code into its constructor. No functionality change. --- src/deps_log.cc | 5 +---- src/deps_log.h | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 8dc6e69..c2587af 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -179,10 +179,7 @@ bool DepsLog::Load(const string& path, State* state, string* err) { deps_data += 2; int deps_count = (size / 4) - 2; - Deps* deps = new Deps; - deps->mtime = mtime; - deps->node_count = deps_count; - deps->nodes = new Node*[deps_count]; + Deps* deps = new Deps(mtime, deps_count); for (int i = 0; i < deps_count; ++i) { assert(deps_data[i] < (int)nodes_.size()); assert(nodes_[deps_data[i]]); diff --git a/src/deps_log.h b/src/deps_log.h index 4b357a8..820997e 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -72,7 +72,8 @@ struct DepsLog { // Reading (startup-time) interface. struct Deps { - Deps() : mtime(-1), node_count(0), nodes(NULL) {} + Deps(int mtime, int node_count) + : mtime(mtime), node_count(node_count), nodes(new Node*[node_count]) {} ~Deps() { delete [] nodes; } int mtime; int node_count; -- cgit v0.12 From 20942b8cf86da96eb4288138fba72c2609cfd54d Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 30 Apr 2013 08:44:25 -0700 Subject: Move updating DepsLog's deps_ array into its own function. No functionality change. --- src/deps_log.cc | 18 ++++++++++++------ src/deps_log.h | 3 +++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index c2587af..8d90765 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -186,13 +186,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { deps->nodes[i] = nodes_[deps_data[i]]; } - if (out_id >= (int)deps_.size()) - deps_.resize(out_id + 1); - if (deps_[out_id]) { + if (UpdateDeps(out_id, deps)) ++dead_record_count_; - delete deps_[out_id]; - } - deps_[out_id] = deps; } else { StringPiece path(buf, size); Node* node = state->GetNode(path); @@ -281,6 +276,17 @@ bool DepsLog::Recompact(const string& path, string* err) { return true; } +bool DepsLog::UpdateDeps(int out_id, Deps* deps) { + if (out_id >= (int)deps_.size()) + deps_.resize(out_id + 1); + + bool delete_old = deps_[out_id] != NULL; + if (delete_old) + delete deps_[out_id]; + deps_[out_id] = deps; + return delete_old; +} + bool DepsLog::RecordId(Node* node) { uint16_t size = (uint16_t)node->path().size(); fwrite(&size, 2, 1, file_); diff --git a/src/deps_log.h b/src/deps_log.h index 820997e..7270916 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -90,6 +90,9 @@ struct DepsLog { const vector& deps() const { return deps_; } private: + // Updates the in-memory representation. Takes ownership of |deps|. + // Returns true if a prior deps record was deleted. + bool UpdateDeps(int out_id, Deps* deps); // Write a node name record, assigning it an id. bool RecordId(Node* node); -- cgit v0.12 From f70a90edc6681fef7e75c7bc7f2860d40c2490ee Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 30 Apr 2013 08:55:49 -0700 Subject: Let DepsLog::RecordDeps() update its in-memory representation. This is a behavior change, but it should be safe: Graph only queries the deps log at startup, before running any command. Nothing else currently queries the deps log. --- src/deps_log.cc | 7 +++++++ src/deps_log_test.cc | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 8d90765..fd7d107 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -106,6 +106,7 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, if (!made_change) return true; + // Update on-disk representation. uint16_t size = 4 * (1 + 1 + (uint16_t)node_count); size |= 0x8000; // Deps record: set high bit. fwrite(&size, 2, 1, file_); @@ -118,6 +119,12 @@ bool DepsLog::RecordDeps(Node* node, TimeStamp mtime, fwrite(&id, 4, 1, file_); } + // Update in-memory representation. + Deps* deps = new Deps(mtime, node_count); + for (int i = 0; i < node_count; ++i) + deps->nodes[i] = nodes[i]; + UpdateDeps(node->id(), deps); + return true; } diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 14f1572..31f0414 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -49,6 +49,13 @@ TEST_F(DepsLogTest, WriteRead) { deps.push_back(state1.GetNode("foo.h")); deps.push_back(state1.GetNode("bar2.h")); log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + + DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o")); + ASSERT_TRUE(log_deps); + ASSERT_EQ(1, log_deps->mtime); + ASSERT_EQ(2, log_deps->node_count); + ASSERT_EQ("foo.h", log_deps->nodes[0]->path()); + ASSERT_EQ("bar.h", log_deps->nodes[1]->path()); } log1.Close(); @@ -66,14 +73,13 @@ TEST_F(DepsLogTest, WriteRead) { ASSERT_EQ(node1->id(), node2->id()); } - // log1 has no deps entries, as it was only used for writing. - // Manually check the entries in log2. - DepsLog::Deps* deps = log2.GetDeps(state2.GetNode("out.o")); - ASSERT_TRUE(deps); - ASSERT_EQ(1, deps->mtime); - ASSERT_EQ(2, deps->node_count); - ASSERT_EQ("foo.h", deps->nodes[0]->path()); - ASSERT_EQ("bar.h", deps->nodes[1]->path()); + // Spot-check the entries in log2. + DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o")); + ASSERT_TRUE(log_deps); + ASSERT_EQ(2, log_deps->mtime); + ASSERT_EQ(2, log_deps->node_count); + ASSERT_EQ("foo.h", log_deps->nodes[0]->path()); + ASSERT_EQ("bar2.h", log_deps->nodes[1]->path()); } // Verify that adding the same deps twice doesn't grow the file. -- cgit v0.12 From 1eaacc8ccf26363ae3a820675721164873a31f3d Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 30 Apr 2013 09:11:09 -0700 Subject: Keep a DepsLog's data valid after a call to DepsLog::Recompact(). Previously, a DepsLog would become invalid after Recompact() was called, due to Recompact() making all node ids refer to a temporary DepsLog object constructed in Recompact(). --- src/deps_log.cc | 13 ++++++++----- src/deps_log_test.cc | 29 +++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index fd7d107..7dbd5b2 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -250,12 +250,11 @@ bool DepsLog::Recompact(const string& path, string* err) { if (!new_log.OpenForWrite(temp_path, err)) return false; - // Clear all known ids so that new ones can be reassigned. - for (vector::iterator i = nodes_.begin(); - i != nodes_.end(); ++i) { + // Clear all known ids so that new ones can be reassigned. The new indices + // will refer to the ordering in new_log, not in the current log. + for (vector::iterator i = nodes_.begin(); i != nodes_.end(); ++i) (*i)->set_id(-1); - } - + // Write out all deps again. for (int old_id = 0; old_id < (int)deps_.size(); ++old_id) { Deps* deps = deps_[old_id]; @@ -270,6 +269,10 @@ bool DepsLog::Recompact(const string& path, string* err) { new_log.Close(); + // All nodes now have ids that refer to new_log, so steal its data. + deps_.swap(new_log.deps_); + nodes_.swap(new_log.nodes_); + if (unlink(path.c_str()) < 0) { *err = strerror(errno); return false; diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index 31f0414..0591736 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -188,18 +188,43 @@ TEST_F(DepsLogTest, Recompact) { string err; ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); - DepsLog::Deps* deps = log.GetDeps(state.GetNode("out.o")); + Node* out = state.GetNode("out.o"); + DepsLog::Deps* deps = log.GetDeps(out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); ASSERT_EQ(1, deps->node_count); ASSERT_EQ("foo.h", deps->nodes[0]->path()); + Node* other_out = state.GetNode("other_out.o"); + deps = log.GetDeps(other_out); + ASSERT_TRUE(deps); + ASSERT_EQ(1, deps->mtime); + ASSERT_EQ(2, deps->node_count); + ASSERT_EQ("foo.h", deps->nodes[0]->path()); + ASSERT_EQ("baz.h", deps->nodes[1]->path()); + ASSERT_TRUE(log.Recompact(kTestFilename, &err)); + // The in-memory deps graph should still be valid after recompaction. + deps = log.GetDeps(out); + ASSERT_TRUE(deps); + ASSERT_EQ(1, deps->mtime); + ASSERT_EQ(1, deps->node_count); + ASSERT_EQ("foo.h", deps->nodes[0]->path()); + ASSERT_EQ(out, log.nodes()[out->id()]); + + deps = log.GetDeps(other_out); + ASSERT_TRUE(deps); + ASSERT_EQ(1, deps->mtime); + ASSERT_EQ(2, deps->node_count); + ASSERT_EQ("foo.h", deps->nodes[0]->path()); + ASSERT_EQ("baz.h", deps->nodes[1]->path()); + ASSERT_EQ(other_out, log.nodes()[other_out->id()]); + + // The file should have shrunk a bit for the smaller deps. struct stat st; ASSERT_EQ(0, stat(kTestFilename, &st)); int file_size_3 = (int)st.st_size; - // The file should have shrunk a bit for the smaller deps. ASSERT_LT(file_size_3, file_size_2); } } -- cgit v0.12 From c5ea04bfc5d59fb82e8a6f0d356770883939f751 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 30 Apr 2013 09:24:48 -0700 Subject: Recompact the deps log when it gets too big. Now that Recompact() keeps all data structures intact, it can just be called at the beginning of a build and the build will still work. --- src/deps_log.cc | 21 +++++++++++++++++++-- src/deps_log.h | 8 +++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/deps_log.cc b/src/deps_log.cc index 7dbd5b2..931cc77 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -37,6 +37,12 @@ DepsLog::~DepsLog() { } bool DepsLog::OpenForWrite(const string& path, string* err) { + if (needs_recompaction_) { + Close(); + if (!Recompact(path, err)) + return false; + } + file_ = fopen(path.c_str(), "ab"); if (!file_) { *err = strerror(errno); @@ -161,6 +167,8 @@ bool DepsLog::Load(const string& path, State* state, string* err) { long offset; bool read_failed = false; + int unique_dep_record_count = 0; + int total_dep_record_count = 0; for (;;) { offset = ftell(f); @@ -193,8 +201,9 @@ bool DepsLog::Load(const string& path, State* state, string* err) { deps->nodes[i] = nodes_[deps_data[i]]; } - if (UpdateDeps(out_id, deps)) - ++dead_record_count_; + total_dep_record_count++; + if (!UpdateDeps(out_id, deps)) + ++unique_dep_record_count; } else { StringPiece path(buf, size); Node* node = state->GetNode(path); @@ -225,6 +234,14 @@ bool DepsLog::Load(const string& path, State* state, string* err) { fclose(f); + // Rebuild the log if there are too many dead records. + int kMinCompactionEntryCount = 1000; + int kCompactionRatio = 3; + if (total_dep_record_count > kMinCompactionEntryCount && + total_dep_record_count > unique_dep_record_count * kCompactionRatio) { + needs_recompaction_ = true; + } + return true; } diff --git a/src/deps_log.h b/src/deps_log.h index 7270916..de0fe63 100644 --- a/src/deps_log.h +++ b/src/deps_log.h @@ -61,7 +61,7 @@ struct State; /// wins, allowing updates to just be appended to the file. A separate /// repacking step can run occasionally to remove dead records. struct DepsLog { - DepsLog() : dead_record_count_(0), file_(NULL) {} + DepsLog() : needs_recompaction_(false), file_(NULL) {} ~DepsLog(); // Writing (build-time) interface. @@ -96,11 +96,9 @@ struct DepsLog { // Write a node name record, assigning it an id. bool RecordId(Node* node); - /// Number of deps record read while loading the file that ended up - /// being unused (due to a latter entry superceding it). - int dead_record_count_; - + bool needs_recompaction_; FILE* file_; + /// Maps id -> Node. vector nodes_; /// Maps id -> deps of that id. -- cgit v0.12 From 65a58e37a7dd48cb336668351d1cab3db0e4d5fe Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 1 May 2013 11:03:27 -0700 Subject: Add support for OpenBSD. Use ppoll() on OpenBSD. Also, fix interrupt handling to recognize that on FreeBSD and OpenBSD, an interrupt might have been delivered even if pselect()/ppoll() don't return -1/EINTR. --- bootstrap.py | 2 +- platform_helper.py | 7 ++++++- src/subprocess-posix.cc | 30 +++++++++++++----------------- src/subprocess_test.cc | 4 ++-- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index a7a8ba6..0f6aa59 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -52,7 +52,7 @@ def run(*args, **kwargs): # g++ call as well as in the later configure.py. cflags = os.environ.get('CFLAGS', '').split() ldflags = os.environ.get('LDFLAGS', '').split() -if platform.is_freebsd(): +if platform.is_freebsd() or platform.is_openbsd(): cflags.append('-I/usr/local/include') ldflags.append('-L/usr/local/lib') diff --git a/platform_helper.py b/platform_helper.py index 052c969..cd7298b 100644 --- a/platform_helper.py +++ b/platform_helper.py @@ -18,7 +18,7 @@ import sys def platforms(): - return ['linux', 'freebsd', 'solaris', 'sunos5', 'mingw', 'msvc'] + return ['linux', 'freebsd', 'openbsd', 'solaris', 'sunos5', 'mingw', 'msvc'] class Platform( object ): def __init__( self, platform): @@ -30,6 +30,8 @@ class Platform( object ): self._platform = 'linux' elif self._platform.startswith('freebsd'): self._platform = 'freebsd' + elif self._platform.startswith('openbsd'): + self._platform = 'openbsd' elif self._platform.startswith('solaris'): self._platform = 'solaris' elif self._platform.startswith('mingw'): @@ -59,5 +61,8 @@ class Platform( object ): def is_freebsd(self): return self._platform == 'freebsd' + def is_openbsd(self): + return self._platform == 'openbsd' + def is_sunos5(self): return self._platform == 'sunos5' diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index 8f1a04e..fb6d272 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -49,12 +49,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) - // On linux we use ppoll in DoWork(); elsewhere we use pselect and so must - // avoid overly-large FDs. +#if !defined(linux) && !defined(__OpenBSD__) + // On Linux and OpenBSD, we use ppoll in DoWork(); elsewhere we use pselect + // and so must avoid overly-large FDs. if (fd_ >= static_cast(FD_SETSIZE)) Fatal("pipe: %s", strerror(EMFILE)); -#endif // !linux +#endif // !linux && !__OpenBSD__ SetCloseOnExec(fd_); pid_ = fork(); @@ -155,8 +155,6 @@ void SubprocessSet::SetInterruptedFlag(int signum) { } SubprocessSet::SubprocessSet() { - interrupted_ = false; - sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); @@ -189,7 +187,7 @@ Subprocess *SubprocessSet::Add(const string& command) { return subprocess; } -#ifdef linux +#if defined(linux) || defined(__OpenBSD__) bool SubprocessSet::DoWork() { vector fds; nfds_t nfds = 0; @@ -204,15 +202,14 @@ bool SubprocessSet::DoWork() { ++nfds; } + interrupted_ = false; 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; + return interrupted_; } nfds_t cur_nfd = 0; @@ -233,10 +230,10 @@ bool SubprocessSet::DoWork() { ++i; } - return false; + return interrupted_; } -#else // linux +#else // linux || __OpenBSD__ bool SubprocessSet::DoWork() { fd_set set; int nfds = 0; @@ -252,15 +249,14 @@ bool SubprocessSet::DoWork() { } } + interrupted_ = false; int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_); if (ret == -1) { if (errno != EINTR) { perror("ninja: pselect"); return false; } - bool interrupted = interrupted_; - interrupted_ = false; - return interrupted; + return interrupted_; } for (vector::iterator i = running_.begin(); @@ -277,9 +273,9 @@ bool SubprocessSet::DoWork() { ++i; } - return false; + return interrupted_; } -#endif // linux +#endif // linux || __OpenBSD__ Subprocess* SubprocessSet::NextFinished() { if (finished_.empty()) diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index c3175da..afd9008 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -152,7 +152,7 @@ TEST_F(SubprocessTest, SetWithMulti) { // OS X's process limit is less than 1025 by default // (|sysctl kern.maxprocperuid| is 709 on 10.7 and 10.8 and less prior to that). -#ifdef linux +#if defined(linux) || defined(__OpenBSD__) TEST_F(SubprocessTest, SetWithLots) { // Arbitrary big number; needs to be over 1024 to confirm we're no longer // hostage to pselect. @@ -179,7 +179,7 @@ TEST_F(SubprocessTest, SetWithLots) { } ASSERT_EQ(kNumProcs, subprocs_.finished_.size()); } -#endif // linux +#endif // linux || __OpenBSD__ // TODO: this test could work on Windows, just not sure how to simply // read stdin. -- cgit v0.12 From 071bd6e1e21de954d9012993eec37a67768532a4 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Wed, 1 May 2013 11:51:51 -0700 Subject: minor formatting change --- src/util.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util.cc b/src/util.cc index a215dd2..fa72dd2 100644 --- a/src/util.cc +++ b/src/util.cc @@ -359,9 +359,8 @@ string ElideMiddle(const string& str, size_t width) { bool Truncate(const string& path, size_t size, string* err) { #ifdef _WIN32 - int fh; - fh = _sopen(path.c_str(), _O_RDWR | _O_CREAT, _SH_DENYNO, - _S_IREAD | _S_IWRITE); + int fh = _sopen(path.c_str(), _O_RDWR | _O_CREAT, _SH_DENYNO, + _S_IREAD | _S_IWRITE); int success = _chsize(fh, size); _close(fh); #else -- cgit v0.12 From 1fe35cea0153cbd965bc1e4102c87371096051d5 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Wed, 1 May 2013 13:05:00 -0700 Subject: Don't poll pipes using POLLRDHUP POLLRDHUP is Linux-specific, and isn't necessary for polling pipes anyway. Linux and OpenBSD both return POLLHUP if no process has the pipe open for writing. --- src/subprocess-posix.cc | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index fb6d272..c56d147 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -25,13 +25,6 @@ #include #include -// Older versions of glibc (like 2.4) won't find this in . glibc -// 2.4 keeps it in , though attempting to include that -// will redefine the pollfd structure. -#ifndef POLLRDHUP -#define POLLRDHUP 0x2000 -#endif - #include "util.h" Subprocess::Subprocess() : fd_(-1), pid_(-1) { @@ -197,7 +190,7 @@ bool SubprocessSet::DoWork() { int fd = (*i)->fd_; if (fd < 0) continue; - pollfd pfd = { fd, POLLIN | POLLPRI | POLLRDHUP, 0 }; + pollfd pfd = { fd, POLLIN | POLLPRI, 0 }; fds.push_back(pfd); ++nfds; } -- cgit v0.12 From c204fa943c575d0f0fa62c2bc5caf286d268aa94 Mon Sep 17 00:00:00 2001 From: Robert Iannucci Date: Mon, 6 May 2013 14:51:55 -0700 Subject: Fix Pool tests. At any phase in the test where multiple edges are ready simultaneously, acquire all edges and sort them into a predictable order. This allows the test to execute deterministically regardless of the order of edge allocation. --- src/build_test.cc | 55 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/build_test.cc b/src/build_test.cc index 68a5142..90c328a 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -24,6 +24,26 @@ // to create Nodes and Edges. struct PlanTest : public StateTestWithBuiltinRules { Plan plan_; + + /// Because FindWork does not return Edges in any sort of predictable order, + // provide a means to get available Edges in order and in a format which is + // easy to write tests around. + void FindWorkSorted(deque* ret, int count) { + struct CompareEdgesByOutput { + static bool cmp(const Edge* a, const Edge* b) { + return a->outputs_[0]->path() < b->outputs_[0]->path(); + } + }; + + for (int i = 0; i < count; ++i) { + ASSERT_TRUE(plan_.more_to_do()); + Edge* edge = plan_.FindWork(); + ASSERT_TRUE(edge); + ret->push_back(edge); + } + ASSERT_FALSE(plan_.FindWork()); + sort(ret->begin(), ret->end(), CompareEdgesByOutput::cmp); + } }; TEST_F(PlanTest, Basic) { @@ -251,27 +271,21 @@ TEST_F(PlanTest, PoolsWithDepthTwo) { EXPECT_TRUE(plan_.AddTarget(GetNode("allTheThings"), &err)); ASSERT_EQ("", err); - // Grab the first 4 edges, out1 out2 outb1 outb2 deque edges; + FindWorkSorted(&edges, 5); + for (int i = 0; i < 4; ++i) { - ASSERT_TRUE(plan_.more_to_do()); - Edge* edge = plan_.FindWork(); - ASSERT_TRUE(edge); + Edge *edge = edges[i]; ASSERT_EQ("in", edge->inputs_[0]->path()); string base_name(i < 2 ? "out" : "outb"); ASSERT_EQ(base_name + string(1, '1' + (i % 2)), edge->outputs_[0]->path()); - edges.push_back(edge); } // outb3 is exempt because it has an empty pool - ASSERT_TRUE(plan_.more_to_do()); - Edge* edge = plan_.FindWork(); + Edge* edge = edges[4]; ASSERT_TRUE(edge); ASSERT_EQ("in", edge->inputs_[0]->path()); ASSERT_EQ("outb3", edge->outputs_[0]->path()); - edges.push_back(edge); - - ASSERT_FALSE(plan_.FindWork()); // finish out1 plan_.EdgeFinished(edges.front()); @@ -293,11 +307,11 @@ TEST_F(PlanTest, PoolsWithDepthTwo) { plan_.EdgeFinished(*it); } - Edge* final = plan_.FindWork(); - ASSERT_TRUE(final); - ASSERT_EQ("allTheThings", final->outputs_[0]->path()); + Edge* last = plan_.FindWork(); + ASSERT_TRUE(last); + ASSERT_EQ("allTheThings", last->outputs_[0]->path()); - plan_.EdgeFinished(final); + plan_.EdgeFinished(last); ASSERT_FALSE(plan_.more_to_do()); ASSERT_FALSE(plan_.FindWork()); @@ -334,25 +348,28 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { Edge* edge = NULL; - edge = plan_.FindWork(); - ASSERT_TRUE(edge); + deque initial_edges; + FindWorkSorted(&initial_edges, 2); + + edge = initial_edges[1]; // Foo first ASSERT_EQ("foo.cpp", edge->outputs_[0]->path()); plan_.EdgeFinished(edge); edge = plan_.FindWork(); ASSERT_TRUE(edge); + ASSERT_FALSE(plan_.FindWork()); ASSERT_EQ("foo.cpp", edge->inputs_[0]->path()); ASSERT_EQ("foo.cpp", edge->inputs_[1]->path()); ASSERT_EQ("foo.cpp.obj", edge->outputs_[0]->path()); plan_.EdgeFinished(edge); - edge = plan_.FindWork(); - ASSERT_TRUE(edge); + edge = initial_edges[0]; // Now for bar ASSERT_EQ("bar.cpp", edge->outputs_[0]->path()); plan_.EdgeFinished(edge); edge = plan_.FindWork(); ASSERT_TRUE(edge); + ASSERT_FALSE(plan_.FindWork()); ASSERT_EQ("bar.cpp", edge->inputs_[0]->path()); ASSERT_EQ("bar.cpp", edge->inputs_[1]->path()); ASSERT_EQ("bar.cpp.obj", edge->outputs_[0]->path()); @@ -360,6 +377,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { edge = plan_.FindWork(); ASSERT_TRUE(edge); + ASSERT_FALSE(plan_.FindWork()); ASSERT_EQ("foo.cpp.obj", edge->inputs_[0]->path()); ASSERT_EQ("bar.cpp.obj", edge->inputs_[1]->path()); ASSERT_EQ("libfoo.a", edge->outputs_[0]->path()); @@ -367,6 +385,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { edge = plan_.FindWork(); ASSERT_TRUE(edge); + ASSERT_FALSE(plan_.FindWork()); ASSERT_EQ("libfoo.a", edge->inputs_[0]->path()); ASSERT_EQ("all", edge->outputs_[0]->path()); plan_.EdgeFinished(edge); -- cgit v0.12 From f9615d074c69c96ac1c763e2a9b98d4267906a07 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Wed, 8 May 2013 10:01:15 -0700 Subject: Fix bootstrap on OS X. This was broken in 4c552c2c3cbc07acce9c1a379fee054a3f680100. --- platform_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform_helper.py b/platform_helper.py index cd7298b..97827c3 100644 --- a/platform_helper.py +++ b/platform_helper.py @@ -18,7 +18,8 @@ import sys def platforms(): - return ['linux', 'freebsd', 'openbsd', 'solaris', 'sunos5', 'mingw', 'msvc'] + return ['linux', 'darwin', 'freebsd', 'openbsd', 'solaris', 'sunos5', + 'mingw', 'msvc'] class Platform( object ): def __init__( self, platform): -- cgit v0.12 From e07a853a35d8712bb22be7909aef98632f7a3a5f Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sat, 11 May 2013 20:40:26 -0700 Subject: Remove a few unused includes. --- src/build_log.h | 1 - src/clean.cc | 3 --- src/manifest_parser.cc | 4 +--- src/manifest_parser.h | 3 --- src/manifest_parser_test.cc | 3 +++ src/ninja.cc | 3 --- src/state.h | 1 - src/subprocess-posix.cc | 2 -- 8 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/build_log.h b/src/build_log.h index 231bfd9..6eae89f 100644 --- a/src/build_log.h +++ b/src/build_log.h @@ -15,7 +15,6 @@ #ifndef NINJA_BUILD_LOG_H_ #define NINJA_BUILD_LOG_H_ -#include #include #include using namespace std; diff --git a/src/clean.cc b/src/clean.cc index 12afb98..5d1974e 100644 --- a/src/clean.cc +++ b/src/clean.cc @@ -15,10 +15,7 @@ #include "clean.h" #include -#include #include -#include -#include #include "disk_interface.h" #include "graph.h" diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index a581114..3593567 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -14,10 +14,8 @@ #include "manifest_parser.h" -#include -#include #include -#include +#include #include "graph.h" #include "metrics.h" diff --git a/src/manifest_parser.h b/src/manifest_parser.h index a08e5af..967dfdd 100644 --- a/src/manifest_parser.h +++ b/src/manifest_parser.h @@ -16,13 +16,10 @@ #define NINJA_MANIFEST_PARSER_H_ #include -#include -#include using namespace std; #include "lexer.h" -#include "string_piece.h" struct BindingEnv; struct EvalString; diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index be749f2..2638edc 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -14,6 +14,9 @@ #include "manifest_parser.h" +#include +#include + #include #include "graph.h" diff --git a/src/ninja.cc b/src/ninja.cc index 8ba1aa6..b4797ed 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -17,8 +17,6 @@ #include #include #include -#include -#include #ifdef _WIN32 #include "getopt.h" @@ -35,7 +33,6 @@ #include "deps_log.h" #include "clean.h" #include "disk_interface.h" -#include "edit_distance.h" #include "explain.h" #include "graph.h" #include "graphviz.h" diff --git a/src/state.h b/src/state.h index 7e3aead..bde75ff 100644 --- a/src/state.h +++ b/src/state.h @@ -16,7 +16,6 @@ #define NINJA_STATE_H_ #include -#include #include #include #include diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index c56d147..339edfe 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -14,8 +14,6 @@ #include "subprocess.h" -#include -#include #include #include #include -- cgit v0.12 From fdb3f677e9ed815733661389de6bd7967565a889 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Fri, 3 May 2013 13:16:55 -0700 Subject: delete obsolete todo --- bootstrap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap.py b/bootstrap.py index 0f6aa59..cff10ba 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -33,7 +33,6 @@ parser.add_option('--verbose', action='store_true', help='enable verbose build',) parser.add_option('--x64', action='store_true', help='force 64-bit build (Windows)',) -# TODO: make this --platform to match configure.py. parser.add_option('--platform', help='target platform (' + '/'.join(platform_helper.platforms()) + ')', choices=platform_helper.platforms()) -- cgit v0.12 From 31ef1415f208e04000424e6fc446fe4377bc7ed3 Mon Sep 17 00:00:00 2001 From: Evan Martin Date: Thu, 16 May 2013 16:21:41 -0700 Subject: call this version 1.3.0 --- src/version.cc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/version.cc b/src/version.cc index 8eb2e07..18fa96a 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.2.0.git"; +const char* kNinjaVersion = "1.3.0.git"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.'); @@ -51,7 +51,3 @@ void CheckNinjaVersion(const string& version) { kNinjaVersion, version.c_str()); } } - - - - -- cgit v0.12