dyndep: reconcile dyndep-specified outputs with depfile-specified inputs

When a path loaded from a depfile does not have a node, we create a new
node with a phony edge producing it.  If we later load a dyndep file
that specifies the same node as an output of a known edge, we previously
failed with a "multiple rules generate ..." error.  Instead, since the
conflicting edge was internally generated, replace the node's input edge
with the now-known real edge that produces it.
This commit is contained in:
Brad King 2021-03-24 15:38:35 -04:00
parent a5b1780487
commit fa577b6d53
4 changed files with 55 additions and 5 deletions

View File

@ -3115,6 +3115,48 @@ TEST_F(BuildTest, DyndepBuildDiscoverImplicitConnection) {
EXPECT_EQ("touch out out.imp", command_runner_.commands_ran_[2]);
}
TEST_F(BuildTest, DyndepBuildDiscoverOutputAndDepfileInput) {
// Verify that a dyndep file can be built and loaded to discover
// that one edge has an implicit output that is also reported by
// a depfile as an input of another edge.
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule touch\n"
" command = touch $out $out.imp\n"
"rule cp\n"
" command = cp $in $out\n"
"build dd: cp dd-in\n"
"build tmp: touch || dd\n"
" dyndep = dd\n"
"build out: cp tmp\n"
" depfile = out.d\n"
));
fs_.Create("out.d", "out: tmp.imp\n");
fs_.Create("dd-in",
"ninja_dyndep_version = 1\n"
"build tmp | tmp.imp: dyndep\n"
);
string err;
EXPECT_TRUE(builder_.AddTarget("out", &err));
ASSERT_EQ("", err);
// Loading the depfile gave tmp.imp a phony input edge.
ASSERT_TRUE(GetNode("tmp.imp")->in_edge()->is_phony());
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
// Loading the dyndep file gave tmp.imp a real input edge.
ASSERT_FALSE(GetNode("tmp.imp")->in_edge()->is_phony());
ASSERT_EQ(3u, command_runner_.commands_ran_.size());
EXPECT_EQ("cp dd-in dd", command_runner_.commands_ran_[0]);
EXPECT_EQ("touch tmp tmp.imp", command_runner_.commands_ran_[1]);
EXPECT_EQ("cp tmp out", command_runner_.commands_ran_[2]);
EXPECT_EQ(1u, fs_.files_created_.count("tmp.imp"));
EXPECT_TRUE(builder_.AlreadyUpToDate());
}
TEST_F(BuildTest, DyndepBuildDiscoverNowWantEdge) {
// Verify that a dyndep file can be built and loaded to discover
// that an edge is actually wanted due to a missing implicit output.

View File

@ -97,9 +97,15 @@ bool DyndepLoader::UpdateEdge(Edge* edge, Dyndeps const* dyndeps,
for (std::vector<Node*>::const_iterator i =
dyndeps->implicit_outputs_.begin();
i != dyndeps->implicit_outputs_.end(); ++i) {
if ((*i)->in_edge() != NULL) {
*err = "multiple rules generate " + (*i)->path();
return false;
if (Edge* old_in_edge = (*i)->in_edge()) {
// This node already has an edge producing it. Fail with an error
// unless the edge was generated by ImplicitDepLoader, in which
// case we can replace it with the now-known real producer.
if (!old_in_edge->generated_by_dep_loader_) {
*err = "multiple rules generate " + (*i)->path();
return false;
}
old_in_edge->outputs_.clear();
}
(*i)->set_in_edge(edge);
}

View File

@ -654,6 +654,7 @@ void ImplicitDepLoader::CreatePhonyInEdge(Node* node) {
return;
Edge* phony_edge = state_->AddEdge(&State::kPhonyRule);
phony_edge->generated_by_dep_loader_ = true;
node->set_in_edge(phony_edge);
phony_edge->outputs_.push_back(node);

View File

@ -147,8 +147,8 @@ struct Edge {
Edge()
: rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL), mark_(VisitNone),
id_(0), outputs_ready_(false), deps_loaded_(false),
deps_missing_(false), implicit_deps_(0), order_only_deps_(0),
implicit_outs_(0) {}
deps_missing_(false), generated_by_dep_loader_(false),
implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {}
/// Return true if all inputs' in-edges are ready.
bool AllInputsReady() const;
@ -182,6 +182,7 @@ struct Edge {
bool outputs_ready_;
bool deps_loaded_;
bool deps_missing_;
bool generated_by_dep_loader_;
const Rule& rule() const { return *rule_; }
Pool* pool() const { return pool_; }