diff --git a/src/build_test.cc b/src/build_test.cc index 8152b4e312..db6a53d920 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -609,6 +609,18 @@ bool FakeCommandRunner::StartCommand(Edge* edge) { if (fs_->ReadFile(edge->inputs_[0]->path(), &content, &err) == DiskInterface::Okay) fs_->WriteFile(edge->outputs_[0]->path(), content); + } else if (edge->rule().name() == "cp-plus-bis") { + assert(!edge->inputs_.empty()); + assert(edge->outputs_.size() >= 1); + string content; + string err; + if (fs_->ReadFile(edge->inputs_[0]->path(), &content, &err) == + DiskInterface::Okay) { + fs_->Tick(); + fs_->WriteFile(edge->outputs_[0]->path() + ".bis", content); + fs_->Tick(); + fs_->WriteFile(edge->outputs_[0]->path(), content); + } } else if (edge->rule().name() == "touch-implicit-dep-out") { string dep = edge->GetBinding("test_dependency"); fs_->Tick(); @@ -4312,3 +4324,173 @@ TEST_F(BuildTest, ValidationWithCircularDependency) { EXPECT_FALSE(builder_.AddTarget("out", &err)); EXPECT_EQ("dependency cycle: validate -> validate_in -> validate", err); } + +TEST_F(BuildTest, RebuildMissingDynamicOutputs) { + string err; + BuildLog build_log; + + { + FakeCommandRunner command_runner(&fs_); + State state; + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0); + builder.command_runner_.reset(&command_runner); + + ASSERT_NO_FATAL_FAILURE( + AssertParse(&state, + "rule cp-plus-bis\n" + " command = cp $in $out && cp $in $out.bis\n" + " dynout = $out.dynout\n" + "build out: cp-plus-bis in\n")); + + fs_.Tick(); + fs_.Create("in", ""); + fs_.Create("out.dynout", "out.bis\n"); + + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, command_runner.commands_ran_.size()); + ASSERT_GT(fs_.Stat("out", &err), 0); + ASSERT_GT(fs_.Stat("out.bis", &err), 0); + // Make sure the dynout file has been removed after its + // information has been extracted in the deps log. + ASSERT_EQ(fs_.Stat("out.dynout", &err), 0); + + // all clean, no rebuild. + command_runner.commands_ran_.clear(); + state.Reset(); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_EQ("", err); + EXPECT_TRUE(builder.AlreadyUpToDate()); + + // Test dynamic output are rebuilt if they are deleted. + fs_.RemoveFile("out.bis"); + command_runner.commands_ran_.clear(); + state.Reset(); + + fs_.Create("out.dynout", "out.bis\n"); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, command_runner.commands_ran_.size()); + + builder.command_runner_.release(); + deps_log.Close(); + } + + // Create a new state to make sure outputs are reset + { + FakeCommandRunner command_runner(&fs_); + State state; + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + deps_log.Load("ninja_deps", &state, &err); + ASSERT_EQ("", err); + Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0); + builder.command_runner_.reset(&command_runner); + + ASSERT_NO_FATAL_FAILURE( + AssertParse(&state, + "rule cp-plus-bis\n" + " command = cp $in $out && cp $in $out.bis\n" + " dynout = $out.dynout\n" + "build out: cp-plus-bis in\n")); + + // all clean, no rebuild. + command_runner.commands_ran_.clear(); + state.Reset(); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_EQ("", err); + EXPECT_TRUE(builder.AlreadyUpToDate()); + + // Test dynamic output are rebuilt if they are deleted + // after having been rebuilt in order to make sure + // when dynamic output information was already exist they + // keep being valid for the next build. + fs_.RemoveFile("out.bis"); + command_runner.commands_ran_.clear(); + state.Reset(); + + // Recreate the dynout file because it is not created by the edge + fs_.Create("out.dynout", "out.bis\n"); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, command_runner.commands_ran_.size()); + + builder.command_runner_.release(); + deps_log.Close(); + } + + RealDiskInterface disk_interface; + disk_interface.RemoveFile("ninja_deps"); +} + +TEST_F(BuildTest, RebuildMissingDynamicOutputsWithRestat) { + string err; + + FakeCommandRunner command_runner(&fs_); + BuildLog build_log; + State state; + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + Builder builder(&state, config_, &build_log, &deps_log, &fs_, &status_, 0); + builder.command_runner_.reset(&command_runner); + + ASSERT_NO_FATAL_FAILURE( + AssertParse(&state, + "rule cp-plus-bis\n" + " command = cp $in $out && cp $in $out.bis\n" + " dynout = $out.dynout\n" + " restat = 1\n" + "build out: cp-plus-bis in\n")); + + fs_.Tick(); + fs_.Create("in", ""); + fs_.Tick(); + fs_.Create("out.dynout", "out.bis\n"); + fs_.Tick(); + + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, command_runner.commands_ran_.size()); + ASSERT_GT(fs_.Stat("out", &err), 0); + ASSERT_GT(fs_.Stat("out.bis", &err), 0); + // Make sure the dynout file has been removed after its + // information has been extracted in the deps log. + ASSERT_EQ(fs_.Stat("out.dynout", &err), 0); + + // all clean, no rebuild. + command_runner.commands_ran_.clear(); + state.Reset(); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_EQ("", err); + EXPECT_TRUE(builder.AlreadyUpToDate()); + + fs_.RemoveFile("out.bis"); + command_runner.commands_ran_.clear(); + state.Reset(); + + // Recreate the dynout file because it is not created by the edge + fs_.Create("out.dynout", "out.bis\n"); + EXPECT_TRUE(builder.AddTarget("out", &err)); + EXPECT_TRUE(builder.Build(&err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, command_runner.commands_ran_.size()); + + // Make sure the dynout file has been removed after its + // information has been extracted in the deps log. + ASSERT_EQ(fs_.Stat("out.dynout", &err), 0); + + builder.command_runner_.release(); + + deps_log.Close(); + RealDiskInterface disk_interface; + disk_interface.RemoveFile("ninja_deps"); +} diff --git a/src/clean_test.cc b/src/clean_test.cc index 6519f8af75..0c94b6ef43 100644 --- a/src/clean_test.cc +++ b/src/clean_test.cc @@ -619,4 +619,38 @@ TEST_F(CleanDeadTest, CleanDeadPreservesInputs) { EXPECT_NE(0, fs_.Stat("out2", &err)); log2.Close(); } + +TEST_F(CleanTest, CleanDynamicOutputs) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cp-plus-bis\n" +" command = cp $in $out && cp $in $out.bis\n" +" dynout = $out.dynout\n" +"build out: cp-plus-bis in\n" +)); + fs_.Create("out", ""); + fs_.Create("out.bis", ""); + + string err; + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + Node* out = state_.LookupNode("out"); + std::vector nodes; + Node* out_bis = state_.GetNode("out.bis", 0); + nodes.push_back(out_bis); + deps_log.RecordDeps(out, 0, nodes, 1); + + Cleaner cleaner(&state_, config_, &fs_, &deps_log); + EXPECT_EQ(0, cleaner.CleanAll()); + EXPECT_EQ(2, cleaner.cleaned_files_count()); + EXPECT_EQ(2u, fs_.files_removed_.size()); + + EXPECT_EQ(0, fs_.Stat("out", &err)); + EXPECT_EQ(0, fs_.Stat("out.bis", &err)); + + deps_log.Close(); + RealDiskInterface disk_interface; + disk_interface.RemoveFile("ninja_deps"); +} + } // anonymous namespace diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index cb1c925532..705f17c784 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -50,7 +50,8 @@ TEST_F(DepsLogTest, WriteRead) { vector deps; deps.push_back(state1.GetNode("foo.h", 0)); deps.push_back(state1.GetNode("bar.h", 0)); - log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps); + deps.push_back(state1.GetNode("out.bis", 0)); + log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps, 1); deps.clear(); deps.push_back(state1.GetNode("foo.h", 0)); @@ -60,9 +61,11 @@ TEST_F(DepsLogTest, WriteRead) { DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0)); ASSERT_TRUE(log_deps); ASSERT_EQ(1, log_deps->mtime); - ASSERT_EQ(2, log_deps->node_count); + ASSERT_EQ(3, log_deps->node_count); + ASSERT_EQ(1, log_deps->outputs_count); ASSERT_EQ("foo.h", log_deps->nodes[0]->path()); ASSERT_EQ("bar.h", log_deps->nodes[1]->path()); + ASSERT_EQ("out.bis", log_deps->nodes[2]->path()); } log1.Close(); diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index c5a1fe8fd2..96d8893668 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -64,6 +64,7 @@ TEST_F(ParserTest, RuleAttributes) { " command = a\n" " depfile = a\n" " deps = a\n" +" dynout = a\n" " description = a\n" " generator = a\n" " restat = a\n"