From 15c7373ba8b69e6159bdccf22ac15168504708e4 Mon Sep 17 00:00:00 2001 From: Neri Marschik Date: Fri, 13 Feb 2026 16:46:41 +0900 Subject: [PATCH 1/4] Fix spurious rebuilds for phony targets with validations Phony targets with no inputs are considered dirty if their output is missing. However, phony targets used as anchors for validation dependencies (e.g. from GN groups) often have no regular inputs but should not force a rebuild if the validations are clean. This change updates RecomputeOutputDirty to check if a phony target has validation dependencies. If so, it is treated similarly to a target with inputs: it is only dirty if its dependencies are dirty, not automatically dirty because the output file is missing. This fixes an issue where targets depending on a phony validation anchor would rebuild on every run. --- src/graph.cc | 3 ++- src/graph_test.cc | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/graph.cc b/src/graph.cc index 227c615822..6fc4ba7b1d 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -285,7 +285,8 @@ bool DependencyScan::RecomputeOutputDirty(const Edge* edge, if (edge->is_phony()) { // Phony edges don't write any output. Outputs are only dirty if // there are no inputs and we're missing the output. - if (edge->inputs_.empty() && !output->exists()) { + if (edge->inputs_.empty() && edge->validations_.empty() && + !output->exists()) { explanations_.Record( output, "output %s of phony edge with no inputs doesn't exist", output->path().c_str()); diff --git a/src/graph_test.cc b/src/graph_test.cc index d29118ae3a..afd4d0eb96 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -1140,4 +1140,19 @@ TEST_F(GraphTest, EdgeQueuePriority) { EXPECT_TRUE(queue.empty()); } +TEST_F(GraphTest, PhonyOutputWithValidation) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, + "build valid: phony\n" + "build out: phony |@ valid\n")); + fs_.Create("valid", ""); + + string err; + std::vector validation_nodes; + EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &validation_nodes, &err)); + ASSERT_EQ("", err); + // Phony output with validation should not be dirty even if output is missing. + EXPECT_FALSE(GetNode("out")->dirty()); + ASSERT_EQ(1u, validation_nodes.size()); + EXPECT_EQ("valid", validation_nodes[0]->path()); +} From b5f70eed10a8239401830ef74d8bc0b64f9a8f47 Mon Sep 17 00:00:00 2001 From: Neri Marschik Date: Mon, 16 Feb 2026 13:52:32 +0900 Subject: [PATCH 2/4] fixup! Fix spurious rebuilds for phony targets with validations --- src/graph.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graph.cc b/src/graph.cc index 6fc4ba7b1d..6102b3999b 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -285,6 +285,8 @@ bool DependencyScan::RecomputeOutputDirty(const Edge* edge, if (edge->is_phony()) { // Phony edges don't write any output. Outputs are only dirty if // there are no inputs and we're missing the output. + // If a phony target has inputs or validations, they are used for dirty + // calculation instead of this fallback. if (edge->inputs_.empty() && edge->validations_.empty() && !output->exists()) { explanations_.Record( From 43aa6e3a34150460512b3576859af7afdefa17e8 Mon Sep 17 00:00:00 2001 From: Neri Marschik Date: Mon, 16 Feb 2026 13:57:30 +0900 Subject: [PATCH 3/4] fixup! fixup! Fix spurious rebuilds for phony targets with validations --- src/graph.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graph.cc b/src/graph.cc index 6102b3999b..e50e0716c5 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -284,7 +284,7 @@ bool DependencyScan::RecomputeOutputDirty(const Edge* edge, Node* output) { if (edge->is_phony()) { // Phony edges don't write any output. Outputs are only dirty if - // there are no inputs and we're missing the output. + // there are no inputs or validations and we're missing the output. // If a phony target has inputs or validations, they are used for dirty // calculation instead of this fallback. if (edge->inputs_.empty() && edge->validations_.empty() && From 03ec8c658e6a94babb9fe766c4fb77d2761a4d46 Mon Sep 17 00:00:00 2001 From: Neri Marschik Date: Mon, 16 Feb 2026 13:59:34 +0900 Subject: [PATCH 4/4] fixup! fixup! fixup! Fix spurious rebuilds for phony targets with validations --- src/graph.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graph.cc b/src/graph.cc index e50e0716c5..8e6ff697e0 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -285,8 +285,8 @@ bool DependencyScan::RecomputeOutputDirty(const Edge* edge, if (edge->is_phony()) { // Phony edges don't write any output. Outputs are only dirty if // there are no inputs or validations and we're missing the output. - // If a phony target has inputs or validations, they are used for dirty - // calculation instead of this fallback. + // If a phony target has inputs or validations, or the output exists, + // they are used for dirty calculation instead of this fallback. if (edge->inputs_.empty() && edge->validations_.empty() && !output->exists()) { explanations_.Record(