bool dirty() const { return dirty_; }
void MarkDirty();
+ void MarkDependentsDirty();
FileStat* file_;
bool dirty_;
if (dirty_)
return; // We already know.
- if (in_edge_) // No input edges means never dirty.
- dirty_ = true;
+ dirty_ = true;
+ MarkDependentsDirty();
+}
+
+void Node::MarkDependentsDirty() {
for (vector<Edge*>::iterator i = out_edges_.begin(); i != out_edges_.end(); ++i)
(*i)->MarkDirty(this);
}
struct Plan {
explicit Plan(State* state) : state_(state) {}
- Node* AddTarget(const string& path);
- bool AddTarget(Node* node);
+ Node* AddTarget(const string& path, string* err);
+ bool AddTarget(Node* node, string* err);
Edge* FindWork();
void EdgeFinished(Edge* edge);
Plan(const Plan&);
};
-Node* Plan::AddTarget(const string& path) {
+Node* Plan::AddTarget(const string& path, string* err) {
Node* node = state_->GetNode(path);
- AddTarget(node);
+ AddTarget(node, err);
return node;
}
-bool Plan::AddTarget(Node* node) {
- if (!node->dirty())
+bool Plan::AddTarget(Node* node, string* err) {
+ Edge* edge = node->in_edge_;
+ if (!edge) { // Leaf node.
+ if (node->dirty_) {
+ *err = "'" + node->file_->path_ + "' missing and no known rule to make it";
+ return false;
+ }
return false;
+ }
- Edge* edge = node->in_edge_;
- assert(edge); // Only nodes with in-edges can be dirty.
+ assert(edge);
+ if (!node->dirty())
+ return false;
want_.insert(node);
bool awaiting_inputs = false;
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end(); ++i) {
- if (AddTarget(*i))
+ if (AddTarget(*i, err))
awaiting_inputs = true;
+ else if (err && !err->empty())
+ return false;
}
if (!awaiting_inputs)
Builder(State* state) : plan_(state), stat_helper_(&default_stat_helper_) {}
virtual ~Builder() {}
- Node* AddTarget(const string& name) {
+ Node* AddTarget(const string& name, string* err) {
Node* node = plan_.state_->GetNode(name);
node->file_->StatIfNecessary(stat_helper_);
if (node->in_edge_)
node->in_edge_->RecomputeDirty(stat_helper_);
- plan_.AddTarget(node);
+ if (!node->dirty_) {
+ *err = "target is clean; nothing to do";
+ return NULL;
+ }
+ if (!plan_.AddTarget(node, err))
+ return NULL;
return node;
}
bool Build(Shell* shell, string* err);
EXPECT_FALSE(state.GetNode("out")->dirty());
state.stat_cache()->GetFile("in1")->Touch(1);
- EXPECT_FALSE(state.GetNode("in1")->dirty());
+ EXPECT_TRUE(state.GetNode("in1")->dirty());
EXPECT_FALSE(state.GetNode("in2")->dirty());
EXPECT_TRUE(state.GetNode("out")->dirty());
-
- Plan plan(&state);
- plan.AddTarget("out");
- edge = plan.FindWork();
- ASSERT_TRUE(edge);
- EXPECT_EQ("cat in1 in2 > out", edge->EvaluateCommand());
- ASSERT_FALSE(plan.FindWork());
}
struct TestEnv : public EvalString::Env {
"build cat12: cat cat1 cat2\n");
}
+ // Mark a path dirty.
void Dirty(const string& path);
+ // Mark dependents of a path dirty.
+ void Touch(const string& path);
// shell override
virtual bool RunCommand(Edge* edge);
// StatHelper
virtual int Stat(const string& path) {
- return 0; // Not found.
+ return now_;
}
Builder builder_;
GetNode(path)->MarkDirty();
}
+void BuildTest::Touch(const string& path) {
+ GetNode(path)->MarkDependentsDirty();
+}
+
bool BuildTest::RunCommand(Edge* edge) {
commands_ran_.push_back(edge->EvaluateCommand());
if (edge->rule_->name_ == "cat") {
}
TEST_F(BuildTest, NoWork) {
- builder_.AddTarget("bin");
string err;
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("no work to do", err);
// Given a dirty target with one ready input,
// we should rebuild the target.
Dirty("cat1");
- builder_.AddTarget("cat1");
string err;
+ ASSERT_TRUE(builder_.AddTarget("cat1", &err));
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("", err);
TEST_F(BuildTest, OneStep2) {
// Given a target with one dirty input,
// we should rebuild the target.
- Dirty("in1");
- builder_.AddTarget("cat1");
+ Dirty("cat1");
string err;
+ EXPECT_TRUE(builder_.AddTarget("cat1", &err));
+ ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("", err);
}
TEST_F(BuildTest, TwoStep) {
- // Dirtying in1 requires rebuilding both intermediate files
+ // Modifying in1 requires rebuilding both intermediate files
// and the final file.
- Dirty("in1");
- builder_.AddTarget("cat12");
+ Touch("in1");
string err;
+ EXPECT_TRUE(builder_.AddTarget("cat12", &err));
+ ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(3, commands_ran_.size());
EXPECT_EQ("cat in1 in2 > cat2", commands_ran_[1]);
EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[2]);
- // Dirtying in2 requires rebuilding one intermediate file
+ // Modifying in2 requires rebuilding one intermediate file
// and the final file.
- Dirty("in2");
- builder_.AddTarget("cat12");
+ Touch("in2");
+ ASSERT_TRUE(builder_.AddTarget("cat12", &err));
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(5, commands_ran_.size());
"build c4: cat c3\n"
"build c5: cat c4\n"));
- Dirty("c1"); // Will recursively dirty all the way to c5.
+ Touch("c1"); // Will recursively dirty all the way to c5.
string err;
- builder_.AddTarget("c5");
+ EXPECT_TRUE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(this, &err));
EXPECT_EQ("", err);
ASSERT_EQ(4, commands_ran_.size());
+ err.clear();
commands_ran_.clear();
- builder_.AddTarget("c5");
+ EXPECT_FALSE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("target is clean; nothing to do", err);
EXPECT_TRUE(builder_.Build(this, &err));
ASSERT_EQ(0, commands_ran_.size());
- Dirty("c4"); // Will recursively dirty all the way to c5.
+ Touch("c3"); // Will recursively dirty through to c5.
+ err.clear();
commands_ran_.clear();
- builder_.AddTarget("c5");
+ EXPECT_TRUE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(this, &err));
ASSERT_EQ(2, commands_ran_.size()); // 3->4, 4->5
}
+TEST_F(BuildTest, MissingInput) {
+ string err;
+ Dirty("in1");
+ EXPECT_FALSE(builder_.AddTarget("cat1", &err));
+ EXPECT_EQ("'in1' missing and no known rule to make it", err);
+}
+
struct StatTest : public StateTestWithBuiltinRules,
public StatHelper {
virtual int Stat(const string& path);
ASSERT_EQ("in11", stats_[2]);
}
-
TEST_F(StatTest, Middle) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build out: cat mid\n"