summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2021-10-01 07:15:04 -0700
committerGitHub <noreply@github.com>2021-10-01 07:15:04 -0700
commitb337bbfd57172b8222048848feabc911af107d1a (patch)
tree87af303b6b96dd62340f88d6abc937c259616e8c /src
parenta280868e9c2c791a0d1529c7002786a117bd16fc (diff)
parent48ed0bb4b433eba43583379336ba13b5489ab2aa (diff)
downloadninja-b337bbfd57172b8222048848feabc911af107d1a.tar.gz
Merge pull request #1964 from rascani/fix-phony-edges
Set output mtime of phony edges to the latest inputs
Diffstat (limited to 'src')
-rw-r--r--src/build_test.cc145
-rw-r--r--src/graph.cc24
-rw-r--r--src/graph.h27
-rw-r--r--src/graph_test.cc37
4 files changed, 227 insertions, 6 deletions
diff --git a/src/build_test.cc b/src/build_test.cc
index e0c43b1..8b6dca2 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -1182,6 +1182,151 @@ TEST_F(BuildTest, PhonySelfReference) {
EXPECT_TRUE(builder_.AlreadyUpToDate());
}
+// There are 6 different cases for phony rules:
+//
+// 1. output edge does not exist, inputs are not real
+// 2. output edge does not exist, no inputs
+// 3. output edge does not exist, inputs are real, newest mtime is M
+// 4. output edge is real, inputs are not real
+// 5. output edge is real, no inputs
+// 6. output edge is real, inputs are real, newest mtime is M
+//
+// Expected results :
+// 1. Edge is marked as clean, mtime is newest mtime of dependents.
+// Touching inputs will cause dependents to rebuild.
+// 2. Edge is marked as dirty, causing dependent edges to always rebuild
+// 3. Edge is marked as clean, mtime is newest mtime of dependents.
+// Touching inputs will cause dependents to rebuild.
+// 4. Edge is marked as clean, mtime is newest mtime of dependents.
+// Touching inputs will cause dependents to rebuild.
+// 5. Edge is marked as dirty, causing dependent edges to always rebuild
+// 6. Edge is marked as clean, mtime is newest mtime of dependents.
+// Touching inputs will cause dependents to rebuild.
+void TestPhonyUseCase(BuildTest* t, int i) {
+ State& state_ = t->state_;
+ Builder& builder_ = t->builder_;
+ FakeCommandRunner& command_runner_ = t->command_runner_;
+ VirtualFileSystem& fs_ = t->fs_;
+
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch $out\n"
+"build notreal: phony blank\n"
+"build phony1: phony notreal\n"
+"build phony2: phony\n"
+"build phony3: phony blank\n"
+"build phony4: phony notreal\n"
+"build phony5: phony\n"
+"build phony6: phony blank\n"
+"\n"
+"build test1: touch phony1\n"
+"build test2: touch phony2\n"
+"build test3: touch phony3\n"
+"build test4: touch phony4\n"
+"build test5: touch phony5\n"
+"build test6: touch phony6\n"
+));
+
+ // Set up test.
+ builder_.command_runner_.reset(&command_runner_);
+
+ fs_.Create("blank", ""); // a "real" file
+ EXPECT_TRUE(builder_.AddTarget("test1", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("test2", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("test3", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("test4", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("test5", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.AddTarget("test6", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+
+ string ci;
+ ci += static_cast<char>('0' + i);
+
+ // Tests 1, 3, 4, and 6 should rebuild when the input is updated.
+ if (i != 2 && i != 5) {
+ Node* testNode = t->GetNode("test" + ci);
+ Node* phonyNode = t->GetNode("phony" + ci);
+ Node* inputNode = t->GetNode("blank");
+
+ state_.Reset();
+ TimeStamp startTime = fs_.now_;
+
+ // Build number 1
+ EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
+ ASSERT_EQ("", err);
+ if (!builder_.AlreadyUpToDate())
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+
+ // Touch the input file
+ state_.Reset();
+ command_runner_.commands_ran_.clear();
+ fs_.Tick();
+ fs_.Create("blank", ""); // a "real" file
+ EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
+ ASSERT_EQ("", err);
+
+ // Second build, expect testN edge to be rebuilt
+ // and phonyN node's mtime to be updated.
+ EXPECT_FALSE(builder_.AlreadyUpToDate());
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(string("touch test") + ci, command_runner_.commands_ran_[0]);
+ EXPECT_TRUE(builder_.AlreadyUpToDate());
+
+ TimeStamp inputTime = inputNode->mtime();
+
+ EXPECT_FALSE(phonyNode->exists());
+ EXPECT_FALSE(phonyNode->dirty());
+
+ EXPECT_GT(phonyNode->mtime(), startTime);
+ EXPECT_EQ(phonyNode->mtime(), inputTime);
+ ASSERT_TRUE(testNode->Stat(&fs_, &err));
+ EXPECT_TRUE(testNode->exists());
+ EXPECT_GT(testNode->mtime(), startTime);
+ } else {
+ // Tests 2 and 5: Expect dependents to always rebuild.
+
+ state_.Reset();
+ command_runner_.commands_ran_.clear();
+ fs_.Tick();
+ command_runner_.commands_ran_.clear();
+ EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
+ ASSERT_EQ("", err);
+ EXPECT_FALSE(builder_.AlreadyUpToDate());
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("touch test" + ci, command_runner_.commands_ran_[0]);
+
+ state_.Reset();
+ command_runner_.commands_ran_.clear();
+ EXPECT_TRUE(builder_.AddTarget("test" + ci, &err));
+ ASSERT_EQ("", err);
+ EXPECT_FALSE(builder_.AlreadyUpToDate());
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("touch test" + ci, command_runner_.commands_ran_[0]);
+ }
+}
+
+TEST_F(BuildTest, PhonyUseCase1) { TestPhonyUseCase(this, 1); }
+TEST_F(BuildTest, PhonyUseCase2) { TestPhonyUseCase(this, 2); }
+TEST_F(BuildTest, PhonyUseCase3) { TestPhonyUseCase(this, 3); }
+TEST_F(BuildTest, PhonyUseCase4) { TestPhonyUseCase(this, 4); }
+TEST_F(BuildTest, PhonyUseCase5) { TestPhonyUseCase(this, 5); }
+TEST_F(BuildTest, PhonyUseCase6) { TestPhonyUseCase(this, 6); }
+
TEST_F(BuildTest, Fail) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule fail\n"
diff --git a/src/graph.cc b/src/graph.cc
index c142f0c..c875d3b 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -31,7 +31,19 @@
using namespace std;
bool Node::Stat(DiskInterface* disk_interface, string* err) {
- return (mtime_ = disk_interface->Stat(path_, err)) != -1;
+ METRIC_RECORD("node stat");
+ mtime_ = disk_interface->Stat(path_, err);
+ if (mtime_ == -1) {
+ return false;
+ }
+ exists_ = (mtime_ != 0) ? ExistenceStatusExists : ExistenceStatusMissing;
+ return true;
+}
+
+void Node::UpdatePhonyMtime(TimeStamp mtime) {
+ if (!exists()) {
+ mtime_ = std::max(mtime_, mtime);
+ }
}
bool DependencyScan::RecomputeDirty(Node* node, string* err) {
@@ -237,6 +249,14 @@ bool DependencyScan::RecomputeOutputDirty(const Edge* edge,
output->path().c_str());
return true;
}
+
+ // Update the mtime with the newest input. Dependents can thus call mtime()
+ // on the fake node and get the latest mtime of the dependencies
+ if (most_recent_input) {
+ output->UpdatePhonyMtime(most_recent_input->mtime());
+ }
+
+ // Phony edges are clean, nothing to do
return false;
}
@@ -487,7 +507,7 @@ string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
void Node::Dump(const char* prefix) const {
printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ",
prefix, path().c_str(), this,
- mtime(), mtime() ? "" : " (:missing)",
+ mtime(), exists() ? "" : " (:missing)",
dirty() ? " dirty" : " clean");
if (in_edge()) {
in_edge()->Dump("in-edge: ");
diff --git a/src/graph.h b/src/graph.h
index bb4f10c..fac8059 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -15,6 +15,7 @@
#ifndef NINJA_GRAPH_H_
#define NINJA_GRAPH_H_
+#include <algorithm>
#include <set>
#include <string>
#include <vector>
@@ -40,6 +41,7 @@ struct Node {
: path_(path),
slash_bits_(slash_bits),
mtime_(-1),
+ exists_(ExistenceStatusUnknown),
dirty_(false),
dyndep_pending_(false),
in_edge_(NULL),
@@ -48,6 +50,9 @@ struct Node {
/// Return false on error.
bool Stat(DiskInterface* disk_interface, std::string* err);
+ /// If the file doesn't exist, set the mtime_ from its dependencies
+ void UpdatePhonyMtime(TimeStamp mtime);
+
/// Return false on error.
bool StatIfNecessary(DiskInterface* disk_interface, std::string* err) {
if (status_known())
@@ -58,20 +63,24 @@ struct Node {
/// Mark as not-yet-stat()ed and not dirty.
void ResetState() {
mtime_ = -1;
+ exists_ = ExistenceStatusUnknown;
dirty_ = false;
}
/// Mark the Node as already-stat()ed and missing.
void MarkMissing() {
- mtime_ = 0;
+ if (mtime_ == -1) {
+ mtime_ = 0;
+ }
+ exists_ = ExistenceStatusMissing;
}
bool exists() const {
- return mtime_ != 0;
+ return exists_ == ExistenceStatusExists;
}
bool status_known() const {
- return mtime_ != -1;
+ return exists_ != ExistenceStatusUnknown;
}
const std::string& path() const { return path_; }
@@ -113,9 +122,19 @@ private:
/// Possible values of mtime_:
/// -1: file hasn't been examined
/// 0: we looked, and file doesn't exist
- /// >0: actual file's mtime
+ /// >0: actual file's mtime, or the latest mtime of its dependencies if it doesn't exist
TimeStamp mtime_;
+ enum ExistenceStatus {
+ /// The file hasn't been examined.
+ ExistenceStatusUnknown,
+ /// The file doesn't exist. mtime_ will be the latest mtime of its dependencies.
+ ExistenceStatusMissing,
+ /// The path is an actual file. mtime_ will be the file's mtime.
+ ExistenceStatusExists
+ };
+ ExistenceStatus exists_;
+
/// Dirty is true when the underlying file is out-of-date.
/// But note that Edge::outputs_ready_ is also used in judging which
/// edges to build.
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 6b4bb51..4f0de98 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -889,3 +889,40 @@ TEST_F(GraphTest, DyndepFileCircular) {
EXPECT_EQ(1u, edge->implicit_deps_);
EXPECT_EQ(1u, edge->order_only_deps_);
}
+
+// Check that phony's dependencies' mtimes are propagated.
+TEST_F(GraphTest, PhonyDepsMtimes) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n"
+" command = touch $out\n"
+"build in_ph: phony in1\n"
+"build out1: touch in_ph\n"
+));
+ fs_.Create("in1", "");
+ fs_.Create("out1", "");
+ Node* out1 = GetNode("out1");
+ Node* in1 = GetNode("in1");
+
+ EXPECT_TRUE(scan_.RecomputeDirty(out1, &err));
+ EXPECT_TRUE(!out1->dirty());
+
+ // Get the mtime of out1
+ ASSERT_TRUE(in1->Stat(&fs_, &err));
+ ASSERT_TRUE(out1->Stat(&fs_, &err));
+ TimeStamp out1Mtime1 = out1->mtime();
+ TimeStamp in1Mtime1 = in1->mtime();
+
+ // Touch in1. This should cause out1 to be dirty
+ state_.Reset();
+ fs_.Tick();
+ fs_.Create("in1", "");
+
+ ASSERT_TRUE(in1->Stat(&fs_, &err));
+ EXPECT_GT(in1->mtime(), in1Mtime1);
+
+ EXPECT_TRUE(scan_.RecomputeDirty(out1, &err));
+ EXPECT_GT(in1->mtime(), in1Mtime1);
+ EXPECT_EQ(out1->mtime(), out1Mtime1);
+ EXPECT_TRUE(out1->dirty());
+}