Revert phony targets to stamp targets

This CL reverts 4 previous commits that removed stamp files in favor of
phony alias targets. This is due to an issue found in the Chromium build
with incremental builds.

Reverts:
  f5f465b5 Fix runtime_deps for elided targets
  c808630c Move runtime_deps file for phony targets to /obj
  a9eaeb80 Switch remaining stamp targets to phony targets
  99f72f88 Switch source_set stamp targets to phony targets

Bug: 215
Change-Id: Id83ea946654e597f93012cf1f93fbe3e6704c1d3
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/10620
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index a16f16f..486354b 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -504,7 +504,6 @@
         'src/gn/bundle_data.cc',
         'src/gn/bundle_data_target_generator.cc',
         'src/gn/bundle_file_rule.cc',
-        'src/gn/builtin_tool.cc',
         'src/gn/c_include_iterator.cc',
         'src/gn/c_substitution_type.cc',
         'src/gn/c_tool.cc',
diff --git a/docs/reference.md b/docs/reference.md
index b321129..9dab518 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -2654,7 +2654,7 @@
   process_file_template").
 
   source sets and groups: this will return a list containing the path of the
-  phony target that Ninja completes once all outputs are generated. This
+  "stamp" file that Ninja will produce once all outputs are generated. This
   probably isn't very useful.
 ```
 
diff --git a/src/gn/builtin_tool.cc b/src/gn/builtin_tool.cc
deleted file mode 100644
index 8bc1e52..0000000
--- a/src/gn/builtin_tool.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/logging.h"
-#include "gn/builtin_tool.h"
-#include "gn/target.h"
-
-const char* BuiltinTool::kBuiltinToolPhony = "phony";
-
-BuiltinTool::BuiltinTool(const char* n) : Tool(n) {
-  CHECK(ValidateName(n));
-}
-
-BuiltinTool::~BuiltinTool() = default;
-
-BuiltinTool* BuiltinTool::AsBuiltin() {
-  return this;
-}
-const BuiltinTool* BuiltinTool::AsBuiltin() const {
-  return this;
-}
-
-bool BuiltinTool::ValidateName(const char* name) const {
-  return name == kBuiltinToolPhony;
-}
-
-void BuiltinTool::SetComplete() {
-  SetToolComplete();
-}
-
-bool BuiltinTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
-  // Initialize default vars.
-  return Tool::InitTool(scope, toolchain, err);
-}
-
-bool BuiltinTool::ValidateSubstitution(const Substitution* sub_type) const {
-  if (name_ == kBuiltinToolPhony)
-    return IsValidToolSubstitution(sub_type);
-  NOTREACHED();
-  return false;
-}
diff --git a/src/gn/builtin_tool.h b/src/gn/builtin_tool.h
deleted file mode 100644
index 00cc809..0000000
--- a/src/gn/builtin_tool.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef TOOLS_GN_BUILTIN_TOOL_H_
-#define TOOLS_GN_BUILTIN_TOOL_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "gn/substitution_list.h"
-#include "gn/substitution_pattern.h"
-#include "gn/tool.h"
-
-// A built-in tool that is always available regardless of toolchain. So far, the
-// only example of this is the phony rule that ninja provides.
-class BuiltinTool : public Tool {
- public:
-  // Builtin tools
-  static const char* kBuiltinToolPhony;
-
-  explicit BuiltinTool(const char* n);
-  ~BuiltinTool();
-
-  // Manual RTTI and required functions ---------------------------------------
-
-  bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
-  bool ValidateName(const char* name) const override;
-  void SetComplete() override;
-  bool ValidateSubstitution(const Substitution* sub_type) const override;
-
-  BuiltinTool* AsBuiltin() override;
-  const BuiltinTool* AsBuiltin() const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BuiltinTool);
-};
-
-#endif  // TOOLS_GN_BUILTIN_TOOL_H_
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
index 1d2b899..97d2629 100644
--- a/src/gn/commands.cc
+++ b/src/gn/commands.cc
@@ -328,13 +328,8 @@
     // Use the link output file if there is one, otherwise fall back to the
     // dependency output file (for actions, for example).
     OutputFile output_file = target->link_output_file();
-    if (output_file.value().empty() && target->dependency_output_file_or_phony())
-      output_file = *target->dependency_output_file_or_phony();
-
-    // This output might be an omitted phony target, but that would mean we
-    // don't have an output file to list.
     if (output_file.value().empty())
-      continue;
+      output_file = target->dependency_output_file();
 
     SourceFile output_as_source = output_file.AsSourceFile(build_settings);
     std::string result =
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
index ab06545..ae6d4d9 100644
--- a/src/gn/filesystem_utils.cc
+++ b/src/gn/filesystem_utils.cc
@@ -1028,8 +1028,6 @@
     result.value().append("gen/");
   else if (type == BuildDirType::OBJ)
     result.value().append("obj/");
-  else if (type == BuildDirType::PHONY)
-    result.value().append("phony/");
   return result;
 }
 
diff --git a/src/gn/filesystem_utils.h b/src/gn/filesystem_utils.h
index 9ba3b4e..830478a 100644
--- a/src/gn/filesystem_utils.h
+++ b/src/gn/filesystem_utils.h
@@ -232,12 +232,6 @@
 
   // Output file directory.
   OBJ,
-
-  // Phony file directory. As the name implies, this is not a real file
-  // directory, but a path that is used for the declaration of phony targets.
-  // This is done to avoid duplicate target names between real files and phony
-  // aliases that point to them.
-  PHONY,
 };
 
 // In different contexts, different information is known about the toolchain in
diff --git a/src/gn/function_get_target_outputs.cc b/src/gn/function_get_target_outputs.cc
index 6c5041e..fa851f3 100644
--- a/src/gn/function_get_target_outputs.cc
+++ b/src/gn/function_get_target_outputs.cc
@@ -46,7 +46,7 @@
   process_file_template").
 
   source sets and groups: this will return a list containing the path of the
-  phony target that Ninja completes once all outputs are generated. This
+  "stamp" file that Ninja will produce once all outputs are generated. This
   probably isn't very useful.
 
 Example
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc
index c8a8437..28f28c7 100644
--- a/src/gn/ninja_action_target_writer.cc
+++ b/src/gn/ninja_action_target_writer.cc
@@ -39,12 +39,12 @@
     extra_hard_deps.push_back(pair.ptr);
 
   // For ACTIONs, the input deps appear only once in the generated ninja
-  // file, so WriteInputDepsPhonyAndGetDep() won't create a phony rule
+  // file, so WriteInputDepsStampAndGetDep() won't create a stamp file
   // and the action will just depend on all the input deps directly.
-  size_t num_output_uses =
+  size_t num_stamp_uses =
       target_->output_type() == Target::ACTION ? 1u : target_->sources().size();
   std::vector<OutputFile> input_deps =
-      WriteInputDepsPhonyAndGetDep(extra_hard_deps, num_output_uses);
+      WriteInputDepsStampAndGetDep(extra_hard_deps, num_stamp_uses);
   out_ << std::endl;
 
   // Collects all output files for writing below.
@@ -83,17 +83,15 @@
   }
   out_ << std::endl;
 
-  // Write the phony, which also depends on all data deps. These are needed at
+  // Write the stamp, which also depends on all data deps. These are needed at
   // runtime and should be compiled when the action is, but don't need to be
   // done before we run the action.
   // TODO(thakis): If the action has just a single output, make things depend
-  // on that output directly without writing a phony target.
+  // on that output directly without writing a stamp file.
   std::vector<OutputFile> data_outs;
-  for (const auto& dep : target_->data_deps()) {
-    if (dep.ptr->dependency_output_file_or_phony())
-      data_outs.push_back(*dep.ptr->dependency_output_file_or_phony());
-  }
-  WritePhonyForTarget(output_files, data_outs);
+  for (const auto& dep : target_->data_deps())
+    data_outs.push_back(dep.ptr->dependency_output_file());
+  WriteStampForTarget(output_files, data_outs);
 }
 
 std::string NinjaActionTargetWriter::WriteRuleDefinition() {
diff --git a/src/gn/ninja_action_target_writer_unittest.cc b/src/gn/ninja_action_target_writer_unittest.cc
index 74f7f90..eb03df1 100644
--- a/src/gn/ninja_action_target_writer_unittest.cc
+++ b/src/gn/ninja_action_target_writer_unittest.cc
@@ -67,7 +67,7 @@
 
 build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
 
-build phony/foo/bar: phony foo.out
+build obj/foo/bar.stamp: stamp foo.out
 )";
   EXPECT_EQ(expected, out.str()) << expected << "--" << out.str();
 }
@@ -112,7 +112,7 @@
 build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
   pool = console
 
-build phony/foo/bar: phony foo.out
+build obj/foo/bar.stamp: stamp foo.out
 )";
   EXPECT_EQ(expected, out.str());
 }
@@ -153,7 +153,7 @@
       "build foo.out: __foo_bar___rule | ../../foo/script.py "
       "../../foo/included.txt ../../foo/source.txt\n"
       "\n"
-      "build phony/foo/bar: phony foo.out\n";
+      "build obj/foo/bar.stamp: stamp foo.out\n";
   EXPECT_EQ(expected_linux, out.str());
 }
 
@@ -162,9 +162,9 @@
   TestWithScope setup;
 
   // Some dependencies that the action can depend on. Use actions for these
-  // so they have a nice platform-independent phony target that can appear in
-  // the output (rather than having to worry about how the current platform
-  // names binaries).
+  // so they have a nice platform-independent stamp file that can appear in the
+  // output (rather than having to worry about how the current platform names
+  // binaries).
   Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
   dep.set_output_type(Target::ACTION);
   dep.visibility().SetPublic();
@@ -215,18 +215,18 @@
 #endif
       "  description = ACTION //foo:bar()\n"
       "  restat = 1\n"
-      "build phony/foo/bar.inputdeps: phony ../../foo/script.py "
-      "../../foo/included.txt phony/foo/dep\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+      "../../foo/included.txt obj/foo/dep.stamp\n"
       "\n"
       "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
-      "phony/foo/bar.inputdeps\n"
+      "obj/foo/bar.inputdeps.stamp\n"
       "  source_name_part = input1\n"
       "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
-      "phony/foo/bar.inputdeps\n"
+      "obj/foo/bar.inputdeps.stamp\n"
       "  source_name_part = input2\n"
       "\n"
-      "build phony/foo/bar: "
-      "phony input1.out input2.out || phony/foo/datadep\n";
+      "build obj/foo/bar.stamp: "
+      "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
 
   std::string out_str = out.str();
 #if defined(OS_WIN)
@@ -280,21 +280,21 @@
 #endif
       "  description = ACTION //foo:bar()\n"
       "  restat = 1\n"
-      "build phony/foo/bar.inputdeps: phony ../../foo/script.py "
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
       "../../foo/included.txt\n"
       "\n"
       "build input1.out: __foo_bar___rule ../../foo/input1.txt"
-      " | phony/foo/bar.inputdeps\n"
+      " | obj/foo/bar.inputdeps.stamp\n"
       "  source_name_part = input1\n"
       "  depfile = gen/input1.d\n"
       "  deps = gcc\n"
       "build input2.out: __foo_bar___rule ../../foo/input2.txt"
-      " | phony/foo/bar.inputdeps\n"
+      " | obj/foo/bar.inputdeps.stamp\n"
       "  source_name_part = input2\n"
       "  depfile = gen/input2.d\n"
       "  deps = gcc\n"
       "\n"
-      "build phony/foo/bar: phony input1.out input2.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
   EXPECT_EQ(expected_linux, out.str());
 }
 
@@ -348,7 +348,7 @@
       // Substitution for the rspfile contents.
       "  source_name_part = input1\n"
       "\n"
-      "build phony/foo/bar: phony input1.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out\n";
   EXPECT_EQ(expected_linux, out.str());
 }
 
@@ -399,7 +399,7 @@
       "  source_file_part = input1.txt\n"
       "  pool = foo_pool\n"
       "\n"
-      "build phony/foo/bar: phony input1.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out\n";
   EXPECT_EQ(expected_linux, out.str());
 }
 
@@ -440,9 +440,9 @@
         "  restat = 1\n"
         "\n"
         "build foo.out: __foo_foo___rule | ../../foo/script.py"
-        " ../../foo/input1.txt phony/foo/dep\n"
+        " ../../foo/input1.txt obj/foo/dep.stamp\n"
         "\n"
-        "build phony/foo/foo: phony foo.out\n";
+        "build obj/foo/foo.stamp: stamp foo.out\n";
     EXPECT_EQ(expected_linux, out.str());
   }
 
@@ -468,11 +468,11 @@
         "  description = ACTION //bar:bar()\n"
         "  restat = 1\n"
         "\n"
-        // Do not have phony/foo/dep as dependency.
+        // Do not have obj/foo/dep.stamp as dependency.
         "build bar.out: __bar_bar___rule | ../../bar/script.py"
-        " ../../bar/input1.txt phony/foo/foo\n"
+        " ../../bar/input1.txt obj/foo/foo.stamp\n"
         "\n"
-        "build phony/bar/bar: phony bar.out\n";
+        "build obj/bar/bar.stamp: stamp bar.out\n";
     EXPECT_EQ(expected_linux, out.str());
   }
 }
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index 08df4c1..08e8c93 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -7,10 +7,10 @@
 #include <sstream>
 
 #include "base/strings/string_util.h"
-#include "gn/builtin_tool.h"
 #include "gn/config_values_extractors.h"
 #include "gn/deps_iterator.h"
 #include "gn/filesystem_utils.h"
+#include "gn/general_tool.h"
 #include "gn/ninja_c_binary_target_writer.h"
 #include "gn/ninja_rust_binary_target_writer.h"
 #include "gn/ninja_target_command_util.h"
@@ -50,8 +50,8 @@
   writer.Run();
 }
 
-std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsPhonyAndGetDep(
-    size_t num_output_uses) const {
+std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampAndGetDep(
+    size_t num_stamp_uses) const {
   CHECK(target_->toolchain()) << "Toolchain not set on target "
                               << target_->label().GetUserVisibleName(true);
 
@@ -65,8 +65,8 @@
   if (inputs.size() == 0)
     return std::vector<OutputFile>();  // No inputs
 
-  // If we only have one input, return it directly instead of writing a phony
-  // target for it.
+  // If we only have one input, return it directly instead of writing a stamp
+  // file for it.
   if (inputs.size() == 1) {
     return std::vector<OutputFile>{
       OutputFile(settings_->build_settings(), *inputs[0])};
@@ -76,22 +76,21 @@
   for (const SourceFile* source : inputs)
     outs.push_back(OutputFile(settings_->build_settings(), *source));
 
-  // If there are multiple inputs, but the phony target would be referenced only
+  // If there are multiple inputs, but the stamp file would be referenced only
   // once, don't write it but depend on the inputs directly.
-  if (num_output_uses == 1u)
+  if (num_stamp_uses == 1u)
     return outs;
 
-  // Make a phony target. We don't need to worry about an empty phony target, as
-  // those would have been peeled off already.
-  CHECK(!inputs.empty());
-  OutputFile phony_file =
-      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-  phony_file.value().append(target_->label().name());
-  phony_file.value().append(".inputs");
+  // Make a stamp file.
+  OutputFile stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  stamp_file.value().append(target_->label().name());
+  stamp_file.value().append(".inputs.stamp");
 
   out_ << "build ";
-  path_output_.WriteFile(out_, phony_file);
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony;
+  path_output_.WriteFile(out_, stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
 
   // File inputs.
   for (const auto* input : inputs) {
@@ -100,12 +99,12 @@
   }
 
   out_ << std::endl;
-  return {phony_file};
+  return {stamp_file};
 }
 
-void NinjaBinaryTargetWriter::WriteSourceSetPhony(
+void NinjaBinaryTargetWriter::WriteSourceSetStamp(
     const std::vector<OutputFile>& object_files) {
-  // The phony rule for source sets is generally not used, since targets that
+  // The stamp rule for source sets is generally not used, since targets that
   // depend on this will reference the object files directly. However, writing
   // this rule allows the user to type the name of the target and get a build
   // which can be convenient for development.
@@ -117,12 +116,10 @@
   DCHECK(classified_deps.extra_object_files.empty());
 
   std::vector<OutputFile> order_only_deps;
-  for (auto* dep : classified_deps.non_linkable_deps) {
-    if (dep->dependency_output_file_or_phony())
-      order_only_deps.push_back(*dep->dependency_output_file_or_phony());
-  }
+  for (auto* dep : classified_deps.non_linkable_deps)
+    order_only_deps.push_back(dep->dependency_output_file());
 
-  WritePhonyForTarget(object_files, order_only_deps);
+  WriteStampForTarget(object_files, order_only_deps);
 }
 
 NinjaBinaryTargetWriter::ClassifiedDeps
@@ -180,7 +177,7 @@
       AddSourceSetFiles(dep, &classified_deps->extra_object_files);
 
     // Add the source set itself as a non-linkable dependency on the current
-    // target. This will make sure that anything the source set's phony target
+    // target. This will make sure that anything the source set's stamp file
     // depends on (like data deps) are also built before the current target
     // can be complete. Otherwise, these will be skipped since this target
     // will depend only on the source set's object files.
diff --git a/src/gn/ninja_binary_target_writer.h b/src/gn/ninja_binary_target_writer.h
index fcf90bf..76a8a4e 100644
--- a/src/gn/ninja_binary_target_writer.h
+++ b/src/gn/ninja_binary_target_writer.h
@@ -33,18 +33,18 @@
     UniqueVector<const Target*> swiftmodule_deps;
   };
 
-  // Writes to the output stream a phony rule for inputs, and
-  // returns the target to be appended to source rules that encodes the
+  // Writes to the output stream a stamp rule for inputs, and
+  // returns the file to be appended to source rules that encodes the
   // implicit dependencies for the current target.
-  // If num_output_uses is small, this might return all input dependencies
-  // directly, without writing a phony rule.
+  // If num_stamp_uses is small, this might return all input dependencies
+  // directly, without writing a stamp file.
   // If there are no implicit dependencies and no extra target dependencies
   // are passed in, this returns an empty vector.
-  std::vector<OutputFile> WriteInputsPhonyAndGetDep(
-      size_t num_phony_uses) const;
+  std::vector<OutputFile> WriteInputsStampAndGetDep(
+      size_t num_stamp_uses) const;
 
-  // Writes the phony line for a source set. These are not linked.
-  void WriteSourceSetPhony(const std::vector<OutputFile>& object_files);
+  // Writes the stamp line for a source set. These are not linked.
+  void WriteSourceSetStamp(const std::vector<OutputFile>& object_files);
 
   // Gets all target dependencies and classifies them, as well as accumulates
   // object files from source sets we need to link.
diff --git a/src/gn/ninja_binary_target_writer_unittest.cc b/src/gn/ninja_binary_target_writer_unittest.cc
index afcc4d6..970aa82 100644
--- a/src/gn/ninja_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_binary_target_writer_unittest.cc
@@ -44,7 +44,7 @@
       "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
       "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
       "\n"
-      "build phony/foo/bar: phony obj/foo/bar.input1.o "
+      "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
       "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
@@ -71,7 +71,8 @@
       "target_out_dir = obj/foo\n"
       "target_output_name = bar\n"
       "\n"
-      "\n";
+      "\n"
+      "build obj/foo/bar.stamp: stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -137,7 +138,7 @@
         "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | "
         "../../foo/input1 ../../foo/input2\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.source1.o\n";
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.source1.o\n";
     std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
@@ -167,14 +168,14 @@
         "target_out_dir = obj/foo\n"
         "target_output_name = bar\n"
         "\n"
-        "build phony/foo/bar.inputs: phony "
+        "build obj/foo/bar.inputs.stamp: stamp "
         "../../foo/input1 ../../foo/input2\n"
         "build obj/foo/bar.source1.o: cxx ../../foo/source1.cc | "
-        "phony/foo/bar.inputs\n"
+        "obj/foo/bar.inputs.stamp\n"
         "build obj/foo/bar.source2.o: cxx ../../foo/source2.cc | "
-        "phony/foo/bar.inputs\n"
+        "obj/foo/bar.inputs.stamp\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.source1.o "
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.source1.o "
         "obj/foo/bar.source2.o\n";
     std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
index 50f1155..f801fb6 100644
--- a/src/gn/ninja_build_writer.cc
+++ b/src/gn/ninja_build_writer.cc
@@ -595,11 +595,8 @@
     EscapeOptions ninja_escape;
     ninja_escape.mode = ESCAPE_NINJA;
     for (const Target* target : default_toolchain_targets_) {
-      if (target->dependency_output_file_or_phony()) {
-        out_ << " $\n    ";
-        path_output_.WriteFile(out_,
-                               *target->dependency_output_file_or_phony());
-      }
+      out_ << " $\n    ";
+      path_output_.WriteFile(out_, target->dependency_output_file());
     }
   }
   out_ << std::endl;
@@ -608,13 +605,9 @@
     // Use the short name when available
     if (written_rules.find("default") != written_rules.end()) {
       out_ << "\ndefault default" << std::endl;
-    } else if (default_target->dependency_output_file_or_phony()) {
-      // If the default target does not have a dependency output file or phony,
-      // then the target specified as default is a no-op. We omit the default
-      // statement entirely to avoid ninja runtime failure.
+    } else {
       out_ << "\ndefault ";
-      path_output_.WriteFile(
-          out_, *default_target->dependency_output_file_or_phony());
+      path_output_.WriteFile(out_, default_target->dependency_output_file());
       out_ << std::endl;
     }
   } else if (!default_toolchain_targets_.empty()) {
@@ -632,12 +625,7 @@
   // Escape for special chars Ninja will handle.
   std::string escaped = EscapeString(phony_name, ninja_escape, nullptr);
 
-  // If the target doesn't have a dependency_output_file_or_phony, we should
-  // still emit the phony rule, but with no dependencies. This allows users to
-  // continue to use the phony rule, but it will effectively be a no-op.
   out_ << "build " << escaped << ": phony ";
-  if (target->dependency_output_file_or_phony()) {
-    path_output_.WriteFile(out_, *target->dependency_output_file_or_phony());
-  }
+  path_output_.WriteFile(out_, target->dependency_output_file());
   out_ << std::endl;
 }
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc
index f98787c..8bc6839 100644
--- a/src/gn/ninja_build_writer_unittest.cc
+++ b/src/gn/ninja_build_writer_unittest.cc
@@ -155,16 +155,16 @@
       "  depth = 42\n";
   const char expected_toolchain[] = "subninja toolchain.ninja\n";
   const char expected_targets[] =
-      "build bar: phony phony/bar/bar\n"
-      "build baz: phony phony/baz/baz\n"
-      "build foo$:bar: phony phony/foo/bar\n"
-      "build bar$:bar: phony phony/bar/bar\n"
-      "build baz$:baz: phony phony/baz/baz\n";
+      "build bar: phony obj/bar/bar.stamp\n"
+      "build baz: phony obj/baz/baz.stamp\n"
+      "build foo$:bar: phony obj/foo/bar.stamp\n"
+      "build bar$:bar: phony obj/bar/bar.stamp\n"
+      "build baz$:baz: phony obj/baz/baz.stamp\n";
   const char expected_root_target[] =
       "build all: phony $\n"
-      "    phony/foo/bar $\n"
-      "    phony/bar/bar $\n"
-      "    phony/baz/baz\n";
+      "    obj/foo/bar.stamp $\n"
+      "    obj/bar/bar.stamp $\n"
+      "    obj/baz/baz.stamp\n";
   const char expected_default[] = "default all\n";
   std::string out_str = ninja_out.str();
 #define EXPECT_SNIPPET(expected)                       \
diff --git a/src/gn/ninja_bundle_data_target_writer.cc b/src/gn/ninja_bundle_data_target_writer.cc
index 2281978..0e3bcb0 100644
--- a/src/gn/ninja_bundle_data_target_writer.cc
+++ b/src/gn/ninja_bundle_data_target_writer.cc
@@ -21,15 +21,13 @@
         OutputFile(settings_->build_settings(), source_file));
   }
 
-  std::vector<OutputFile> input_deps = WriteInputDepsPhonyAndGetDep(
-      std::vector<const Target*>(), /*num_output_uses=*/1);
+  std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), /*num_stamp_uses=*/1);
   output_files.insert(output_files.end(), input_deps.begin(), input_deps.end());
 
   std::vector<OutputFile> order_only_deps;
-  for (const auto& pair : target_->data_deps()) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      order_only_deps.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& pair : target_->data_deps())
+    order_only_deps.push_back(pair.ptr->dependency_output_file());
 
-  WritePhonyForTarget(output_files, order_only_deps);
+  WriteStampForTarget(output_files, order_only_deps);
 }
diff --git a/src/gn/ninja_bundle_data_target_writer_unittest.cc b/src/gn/ninja_bundle_data_target_writer_unittest.cc
index ebf0f89..06b5eb2 100644
--- a/src/gn/ninja_bundle_data_target_writer_unittest.cc
+++ b/src/gn/ninja_bundle_data_target_writer_unittest.cc
@@ -42,7 +42,7 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/foo/data: phony "
+      "build obj/foo/data.stamp: stamp "
       "../../foo/input1.txt "
       "../../foo/input2.txt "
       "../../foo/Foo.xcassets/Contents.json "
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index f0d43a3..17f9c08 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -132,10 +132,10 @@
 
   WriteCompilerVars(module_dep_info);
 
-  size_t num_output_uses = target_->sources().size();
+  size_t num_stamp_uses = target_->sources().size();
 
-  std::vector<OutputFile> input_deps =
-      WriteInputsPhonyAndGetDep(num_output_uses);
+  std::vector<OutputFile> input_deps = WriteInputsStampAndGetDep(
+      num_stamp_uses);
 
   // The input dependencies will be an order-only dependency. This will cause
   // Ninja to make sure the inputs are up to date before compiling this source,
@@ -164,11 +164,11 @@
   // The order only deps are referenced by each source file compile,
   // but also by PCH compiles.  The latter are annoying to count, so omit
   // them here.  This means that binary targets with a single source file
-  // that also use PCH files won't have a phony target even though having
+  // that also use PCH files won't have a stamp file even though having
   // one would make output ninja file size a bit lower. That's ok, binary
   // targets with a single source are rare.
-  std::vector<OutputFile> order_only_deps = WriteInputDepsPhonyAndGetDep(
-      std::vector<const Target*>(), num_output_uses);
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
 
   // For GCC builds, the .gch files are not object files, but still need to be
   // added as explicit dependencies below. The .gch output files are placed in
@@ -207,7 +207,7 @@
     return;
 
   if (target_->output_type() == Target::SOURCE_SET) {
-    WriteSourceSetPhony(obj_files);
+    WriteSourceSetStamp(obj_files);
 #ifndef NDEBUG
     // Verify that the function that separately computes a source set's object
     // files match the object files just computed.
@@ -667,11 +667,8 @@
     swift_order_only_deps.Append(order_only_deps.begin(),
                                  order_only_deps.end());
 
-    for (const Target* swiftmodule : target_->swift_values().modules()) {
-      CHECK(swiftmodule->dependency_output_file_or_phony());
-      swift_order_only_deps.push_back(
-          *swiftmodule->dependency_output_file_or_phony());
-    }
+    for (const Target* swiftmodule : target_->swift_values().modules())
+      swift_order_only_deps.push_back(swiftmodule->dependency_output_file());
 
     WriteCompilerBuildLine(target_->sources(), input_deps,
                            swift_order_only_deps.vector(), tool->name(),
@@ -722,12 +719,11 @@
         cur->output_type() == Target::RUST_PROC_MACRO)
       continue;
 
-    if (cur->dependency_output_file_or_phony() &&
-        (cur->dependency_output_file_or_phony()->value() !=
-         cur->link_output_file().value())) {
+    if (cur->dependency_output_file().value() !=
+        cur->link_output_file().value()) {
       // This is a shared library with separate link and deps files. Save for
       // later.
-      implicit_deps.push_back(*cur->dependency_output_file_or_phony());
+      implicit_deps.push_back(cur->dependency_output_file());
       solibs.push_back(cur->link_output_file());
     } else {
       // Normal case, just link to this target.
@@ -758,13 +754,12 @@
   }
 
   // If any target creates a framework bundle, then treat it as an implicit
-  // dependency via the phony target. This is a pessimisation as it is not
+  // dependency via the .stamp file. This is a pessimisation as it is not
   // always necessary to relink the current target if one of the framework
   // is regenerated, but it ensure that if one of the framework API changes,
   // any dependent target will relink it (see crbug.com/1037607).
   for (const Target* dep : classified_deps.framework_deps) {
-    if (dep->dependency_output_file_or_phony())
-      implicit_deps.push_back(*dep->dependency_output_file_or_phony());
+    implicit_deps.push_back(dep->dependency_output_file());
   }
 
   // The input dependency is only needed if there are no object files, as the
@@ -779,9 +774,8 @@
     for (const auto* dep :
          target_->rust_values().transitive_libs().GetOrdered()) {
       if (dep->output_type() == Target::RUST_LIBRARY) {
-        CHECK(dep->dependency_output_file());
-        transitive_rustlibs.push_back(*dep->dependency_output_file());
-        implicit_deps.push_back(*dep->dependency_output_file());
+        transitive_rustlibs.push_back(dep->dependency_output_file());
+        implicit_deps.push_back(dep->dependency_output_file());
       }
     }
   }
@@ -813,11 +807,11 @@
   // this target.
   //
   // The action dependencies are not strictly necessary in this case. They
-  // should also have been collected via the input deps phony alias that each
-  // source file has for an order-only dependency, and since this target depends
-  // on the sources, there is already an implicit order-only dependency.
-  // However, it's extra work to separate these out and there's no disadvantage
-  // to listing them again.
+  // should also have been collected via the input deps stamp that each source
+  // file has for an order-only dependency, and since this target depends on
+  // the sources, there is already an implicit order-only dependency. However,
+  // it's extra work to separate these out and there's no disadvantage to
+  // listing them again.
   WriteOrderOnlyDependencies(classified_deps.non_linkable_deps);
 
   // End of the link "build" line.
@@ -879,11 +873,8 @@
 
     // Non-linkable targets.
     for (auto* non_linkable_dep : non_linkable_deps) {
-      if (non_linkable_dep->dependency_output_file_or_phony()) {
-        out_ << " ";
-        path_output_.WriteFile(
-            out_, *non_linkable_dep->dependency_output_file_or_phony());
-      }
+      out_ << " ";
+      path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
     }
   }
 }
diff --git a/src/gn/ninja_c_binary_target_writer.h b/src/gn/ninja_c_binary_target_writer.h
index ae0b09d..bbc71c5 100644
--- a/src/gn/ninja_c_binary_target_writer.h
+++ b/src/gn/ninja_c_binary_target_writer.h
@@ -40,7 +40,7 @@
   // non-object files (for instance, .gch files from a GCC toolchain, are
   // appended to |other_files|).
   //
-  // input_deps is the phony target collecting the dependencies required before
+  // input_deps is the stamp file collecting the dependencies required before
   // compiling this target. It will be empty if there are no input deps.
   void WritePCHCommands(const std::vector<OutputFile>& input_deps,
                         const std::vector<OutputFile>& order_only_deps,
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 540e755..99961da 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -55,7 +55,7 @@
         "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
         "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.input1.o "
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
     std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
@@ -86,7 +86,7 @@
         // order.
         "build ./libshlib.so: solink obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
-        "|| phony/foo/bar\n"
+        "|| obj/foo/bar.stamp\n"
         "  ldflags =\n"
         "  libs =\n"
         "  frameworks =\n"
@@ -119,7 +119,7 @@
         "\n"
         // There are no sources so there are no params to alink. (In practice
         // this will probably fail in the archive tool.)
-        "build obj/foo/libstlib.a: alink || phony/foo/bar\n"
+        "build obj/foo/libstlib.a: alink || obj/foo/bar.stamp\n"
         "  arflags =\n"
         "  output_extension = \n"
         "  output_dir = \n";
@@ -147,7 +147,7 @@
         // order.
         "build obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
-        "|| phony/foo/bar\n"
+        "|| obj/foo/bar.stamp\n"
         "  arflags =\n"
         "  output_extension = \n"
         "  output_dir = \n";
@@ -329,15 +329,15 @@
       "target_output_name = libshlib\n"
       "\n"
       "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
-      " || phony/foo/action\n"
+      " || obj/foo/action.stamp\n"
       "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
-      " || phony/foo/action\n"
+      " || obj/foo/action.stamp\n"
       "\n"
       "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
       // The order-only dependency here is stricly unnecessary since the
       // sources list this as an order-only dep. See discussion in the code
       // that writes this.
-      "obj/foo/libshlib.input2.o || phony/foo/action\n"
+      "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
       "  frameworks =\n"
@@ -392,12 +392,12 @@
       "target_output_name = gen_obj\n"
       "\n"
       "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
-      " || phony/foo/generate\n"
+      " || obj/foo/generate.stamp\n"
       "\n"
-      "build phony/foo/gen_obj: phony obj/out/Debug/gen_obj.generated.o"
+      "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
       // The order-only dependency here is strictly unnecessary since the
       // sources list this as an order-only dep.
-      " || phony/foo/generate\n";
+      " || obj/foo/generate.stamp\n";
 
   std::string obj_str = obj_out.str();
   EXPECT_EQ(obj_expected, obj_str);
@@ -429,8 +429,8 @@
       "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
       // The order-only dependency here is strictly unnecessary since
       // obj/out/Debug/gen_obj.generated.o has dependency to
-      // phony/foo/gen_obj
-      " || phony/foo/gen_obj\n"
+      // obj/foo/gen_obj.stamp
+      " || obj/foo/gen_obj.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
       "  frameworks =\n"
@@ -537,8 +537,6 @@
   Target framework(setup.settings(), Label(SourceDir("//bar"), "framework"));
   framework.set_output_type(Target::CREATE_BUNDLE);
   framework.bundle_data().product_type() = "com.apple.product-type.framework";
-  framework.bundle_data().set_partial_info_plist(
-      SourceFile("//out/Debug/bar/framework/framework_partial_info.plist"));
   framework.public_configs().push_back(LabelConfigPair(&framework_config));
   framework.SetToolchain(setup.toolchain());
   framework.visibility().SetPublic();
@@ -565,7 +563,7 @@
       "target_output_name = libshlib\n"
       "\n"
       "\n"
-      "build ./libshlib.so: solink | phony/bar/framework\n"
+      "build ./libshlib.so: solink | obj/bar/framework.stamp\n"
       "  ldflags = -F.\n"
       "  libs =\n"
       "  frameworks = -framework System -framework Bar "
@@ -651,7 +649,7 @@
   NinjaCBinaryTargetWriter inter_writer(&inter, inter_out);
   inter_writer.Run();
 
-  // The intermediate source set will be a phony target that depends on the
+  // The intermediate source set will be a stamp file that depends on the
   // object files, and will have an order-only dependency on its data dep and
   // data file.
   const char inter_expected[] =
@@ -665,7 +663,7 @@
       "\n"
       "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
       "\n"
-      "build phony/foo/inter: phony obj/foo/inter.inter.o || "
+      "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
       "./data_target\n";
   EXPECT_EQ(inter_expected, inter_out.str());
 
@@ -684,7 +682,7 @@
 
   // The final output depends on both object files (one from the final target,
   // one from the source set) and has an order-only dependency on the source
-  // set's phony target and the final target's data file. The source set phony
+  // set's stamp file and the final target's data file. The source set stamp
   // dependency will create an implicit order-only dependency on the data
   // target.
   const char final_expected[] =
@@ -699,7 +697,7 @@
       "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
       "\n"
       "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
-      "phony/foo/inter\n"
+      "obj/foo/inter.stamp\n"
       "  ldflags =\n"
       "  libs =\n"
       "  frameworks =\n"
@@ -888,8 +886,8 @@
         "build withpch/obj/foo/no_pch_target.input2.o: "
         "withpch_cc ../../foo/input2.c\n"
         "\n"
-        "build withpch/phony/foo/no_pch_target: "
-        "phony withpch/obj/foo/no_pch_target.input1.o "
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
         "withpch/obj/foo/no_pch_target.input2.o\n";
     EXPECT_EQ(no_pch_expected, out.str());
   }
@@ -942,7 +940,7 @@
         // Explicit dependency on the PCH build step.
         "withpch/obj/build/pch_target.precompile.c.o\n"
         "\n"
-        "build withpch/phony/foo/pch_target: phony "
+        "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
         "withpch/obj/foo/pch_target.input1.o "
         "withpch/obj/foo/pch_target.input2.o "
         // The precompiled object files were added to the outputs.
@@ -1022,8 +1020,8 @@
         "build withpch/obj/foo/no_pch_target.input2.o: "
         "withpch_cc ../../foo/input2.c\n"
         "\n"
-        "build withpch/phony/foo/no_pch_target: "
-        "phony withpch/obj/foo/no_pch_target.input1.o "
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
         "withpch/obj/foo/no_pch_target.input2.o\n";
     EXPECT_EQ(no_pch_expected, out.str());
   }
@@ -1074,8 +1072,8 @@
         // Explicit dependency on the PCH build step.
         "withpch/obj/build/pch_target.precompile.h-c.gch\n"
         "\n"
-        "build withpch/phony/foo/pch_target: "
-        "phony withpch/obj/foo/pch_target.input1.o "
+        "build withpch/obj/foo/pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/pch_target.input1.o "
         "withpch/obj/foo/pch_target.input2.o\n";
     EXPECT_EQ(pch_gcc_expected, out.str());
   }
@@ -1140,7 +1138,7 @@
         "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
         " | ../../foo/input.data\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.input1.o "
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o\n";
 
     EXPECT_EQ(expected, out.str());
@@ -1204,14 +1202,14 @@
         "target_out_dir = obj/foo\n"
         "target_output_name = bar\n"
         "\n"
-        "build phony/foo/bar.inputs: phony"
+        "build obj/foo/bar.inputs.stamp: stamp"
         " ../../foo/input1.data ../../foo/input2.data\n"
         "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
-        " | phony/foo/bar.inputs\n"
+        " | obj/foo/bar.inputs.stamp\n"
         "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
-        " | phony/foo/bar.inputs\n"
+        " | obj/foo/bar.inputs.stamp\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.input1.o "
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o\n";
 
     EXPECT_EQ(expected, out.str());
@@ -1254,14 +1252,14 @@
         "target_out_dir = obj/foo\n"
         "target_output_name = bar\n"
         "\n"
-        "build phony/foo/bar.inputs: phony"
+        "build obj/foo/bar.inputs.stamp: stamp"
         " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
         "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
-        " | phony/foo/bar.inputs\n"
+        " | obj/foo/bar.inputs.stamp\n"
         "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
-        " | phony/foo/bar.inputs\n"
+        " | obj/foo/bar.inputs.stamp\n"
         "\n"
-        "build phony/foo/bar: phony obj/foo/bar.input1.o "
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
         "obj/foo/bar.input2.o\n";
 
     EXPECT_EQ(expected, out.str());
@@ -1573,7 +1571,7 @@
         "\n"
         "build obj/foo/file1.o obj/foo/file2.o: stamp obj/foo/Foo.swiftmodule\n"
         "\n"
-        "build phony/foo/foo: phony"
+        "build obj/foo/foo.stamp: stamp"
         " obj/foo/file1.o obj/foo/file2.o\n";
 
     const std::string out_str = out.str();
@@ -1606,13 +1604,13 @@
         "target_output_name = bar\n"
         "\n"
         "build obj/bar/Bar.swiftmodule: swift ../../bar/bar.swift"
-        " || phony/foo/foo\n"
+        " || obj/foo/foo.stamp\n"
         "\n"
         "build obj/bar/bar.o: stamp obj/bar/Bar.swiftmodule"
-        " || phony/foo/foo\n"
+        " || obj/foo/foo.stamp\n"
         "\n"
-        "build phony/bar/bar: phony obj/bar/bar.o "
-        "|| phony/foo/foo\n";
+        "build obj/bar/bar.stamp: stamp obj/bar/bar.o "
+        "|| obj/foo/foo.stamp\n";
 
     const std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
@@ -1652,13 +1650,13 @@
         "target_output_name = bar\n"
         "\n"
         "build obj/bar/Bar.swiftmodule: swift ../../bar/bar.swift"
-        " || phony/foo/foo\n"
+        " || obj/foo/foo.stamp\n"
         "\n"
         "build obj/bar/bar.o: stamp obj/bar/Bar.swiftmodule"
-        " || phony/foo/foo\n"
+        " || obj/foo/foo.stamp\n"
         "\n"
-        "build phony/bar/bar: phony obj/bar/bar.o "
-        "|| phony/bar/group phony/foo/foo\n";
+        "build obj/bar/bar.stamp: stamp obj/bar/bar.o "
+        "|| obj/bar/group.stamp obj/foo/foo.stamp\n";
 
     const std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
@@ -1687,7 +1685,7 @@
         "\n"
         "build ./bar: link obj/foo/file1.o obj/foo/file2.o "
         "| obj/foo/Foo.swiftmodule "
-        "|| phony/foo/foo\n"
+        "|| obj/foo/foo.stamp\n"
         "  ldflags =\n"
         "  libs =\n"
         "  frameworks =\n"
diff --git a/src/gn/ninja_copy_target_writer.cc b/src/gn/ninja_copy_target_writer.cc
index 646ec44..9299223 100644
--- a/src/gn/ninja_copy_target_writer.cc
+++ b/src/gn/ninja_copy_target_writer.cc
@@ -33,13 +33,28 @@
     return;
   }
 
-  // General target-related substitutions needed by the copy tool.
-  WriteSharedVars(copy_tool->substitution_bits());
+  const Tool* stamp_tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolStamp);
+  if (!stamp_tool) {
+    g_scheduler->FailWithError(Err(
+        nullptr, "Copy tool not defined",
+        "The toolchain " +
+            target_->toolchain()->label().GetUserVisibleName(false) +
+            "\n used by target " + target_->label().GetUserVisibleName(false) +
+            "\n doesn't define a \"stamp\" tool."));
+    return;
+  }
+
+  // Figure out the substitutions used by the copy and stamp tools.
+  SubstitutionBits required_bits = copy_tool->substitution_bits();
+  required_bits.MergeFrom(stamp_tool->substitution_bits());
+
+  // General target-related substitutions needed by both tools.
+  WriteSharedVars(required_bits);
 
   std::vector<OutputFile> output_files;
   WriteCopyRules(&output_files);
   out_ << std::endl;
-  WritePhonyForTarget(output_files, std::vector<OutputFile>());
+  WriteStampForTarget(output_files, std::vector<OutputFile>());
 }
 
 void NinjaCopyTargetWriter::WriteCopyRules(
@@ -54,15 +69,13 @@
   std::string tool_name = GetNinjaRulePrefixForToolchain(settings_) +
                           GeneralTool::kGeneralToolCopy;
 
-  size_t num_output_uses = target_->sources().size();
-  std::vector<OutputFile> input_deps = WriteInputDepsPhonyAndGetDep(
-      std::vector<const Target*>(), num_output_uses);
+  size_t num_stamp_uses = target_->sources().size();
+  std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
 
   std::vector<OutputFile> data_outs;
-  for (const auto& dep : target_->data_deps()) {
-    if (dep.ptr->dependency_output_file_or_phony())
-      data_outs.push_back(*dep.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& dep : target_->data_deps())
+    data_outs.push_back(dep.ptr->dependency_output_file());
 
   // Note that we don't write implicit deps for copy steps. "copy" only
   // depends on the output files themselves, rather than having includes
diff --git a/src/gn/ninja_copy_target_writer_unittest.cc b/src/gn/ninja_copy_target_writer_unittest.cc
index 1b5077a..f641ffa 100644
--- a/src/gn/ninja_copy_target_writer_unittest.cc
+++ b/src/gn/ninja_copy_target_writer_unittest.cc
@@ -35,7 +35,7 @@
       "build input1.out: copy ../../foo/input1.txt\n"
       "build input2.out: copy ../../foo/input2.txt\n"
       "\n"
-      "build phony/foo/bar: phony input1.out input2.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected_linux, out_str);
 }
@@ -63,7 +63,7 @@
   const char expected_linux[] =
       "build output.out: copy ../../foo/input1.txt\n"
       "\n"
-      "build phony/foo/bar: phony output.out\n";
+      "build obj/foo/bar.stamp: stamp output.out\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected_linux, out_str);
 }
@@ -88,7 +88,7 @@
   const char expected_linux[] =
       "build input1.out: copy ../../foo/input1.txt || ../../foo/script.py\n"
       "\n"
-      "build phony/foo/bar: phony input1.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected_linux, out_str);
 }
@@ -118,9 +118,9 @@
   writer.Run();
 
   const char expected_linux[] =
-      "build input1.out: copy ../../foo/input1.txt || phony/foo/datadep\n"
+      "build input1.out: copy ../../foo/input1.txt || obj/foo/datadep.stamp\n"
       "\n"
-      "build phony/foo/bar: phony input1.out\n";
+      "build obj/foo/bar.stamp: stamp input1.out\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected_linux, out_str);
 }
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc
index 002c042..9f000f3 100644
--- a/src/gn/ninja_create_bundle_target_writer.cc
+++ b/src/gn/ninja_create_bundle_target_writer.cc
@@ -77,11 +77,11 @@
   if (!EnsureAllToolsAvailable(target_))
     return;
 
-  // Output users are CopyBundleData, CompileAssetsCatalog, CodeSigning and
-  // PhonyForTarget.
-  size_t num_output_uses = 4;
-  std::vector<OutputFile> order_only_deps = WriteInputDepsPhonyAndGetDep(
-      std::vector<const Target*>(), num_output_uses);
+  // Stamp users are CopyBundleData, CompileAssetsCatalog, CodeSigning and
+  // StampForTarget.
+  size_t num_stamp_uses = 4;
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
 
   std::string code_signing_rule_name = WriteCodeSigningRuleDefinition();
 
@@ -90,17 +90,9 @@
   WriteCompileAssetsCatalogStep(order_only_deps, &output_files);
   WriteCodeSigningStep(code_signing_rule_name, order_only_deps, &output_files);
 
-  for (const auto& pair : target_->data_deps()) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      order_only_deps.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
-
-  // If the target does not have a phony target to write, then we have nothing
-  // left to do.
-  if (!target_->dependency_output_file_or_phony())
-    return;
-
-  WritePhonyForTarget(output_files, order_only_deps);
+  for (const auto& pair : target_->data_deps())
+    order_only_deps.push_back(pair.ptr->dependency_output_file());
+  WriteStampForTarget(output_files, order_only_deps);
 
   // Write a phony target for the outer bundle directory. This allows other
   // targets to treat the entire bundle as a single unit, even though it is
@@ -110,8 +102,7 @@
       out_,
       OutputFile(settings_->build_settings(),
                  target_->bundle_data().GetBundleRootDirOutput(settings_)));
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony << " ";
-  out_ << target_->dependency_output_file_or_phony()->value();
+  out_ << ": phony " << target_->dependency_output_file().value();
   out_ << std::endl;
 }
 
@@ -227,7 +218,7 @@
     return;
   }
 
-  OutputFile input_dep = WriteCompileAssetsCatalogInputDepsPhony(
+  OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp(
       target_->bundle_data().assets_catalog_deps());
   DCHECK(!input_dep.value().empty());
 
@@ -287,32 +278,28 @@
 }
 
 OutputFile
-NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsPhony(
+NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp(
     const std::vector<const Target*>& dependencies) {
   DCHECK(!dependencies.empty());
-  if (dependencies.size() == 1) {
-    return dependencies[0]->dependency_output_file_or_phony()
-               ? *dependencies[0]->dependency_output_file_or_phony()
-               : OutputFile{};
-  }
+  if (dependencies.size() == 1)
+    return dependencies[0]->dependency_output_file();
 
-  OutputFile xcassets_input_phony =
-      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-  xcassets_input_phony.value().append(target_->label().name());
-  xcassets_input_phony.value().append(".xcassets.inputdeps");
+  OutputFile xcassets_input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  xcassets_input_stamp_file.value().append(target_->label().name());
+  xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
 
   out_ << "build ";
-  path_output_.WriteFile(out_, xcassets_input_phony);
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony;
+  path_output_.WriteFile(out_, xcassets_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
 
   for (const Target* target : dependencies) {
-    if (target->dependency_output_file_or_phony()) {
-      out_ << " ";
-      path_output_.WriteFile(out_, *target->dependency_output_file_or_phony());
-    }
+    out_ << " ";
+    path_output_.WriteFile(out_, target->dependency_output_file());
   }
   out_ << std::endl;
-  return xcassets_input_phony;
+  return xcassets_input_stamp_file;
 }
 
 void NinjaCreateBundleTargetWriter::WriteCodeSigningStep(
@@ -322,9 +309,9 @@
   if (code_signing_rule_name.empty())
     return;
 
-  OutputFile code_signing_input_phony =
-      WriteCodeSigningInputDepsPhony(order_only_deps, output_files);
-  DCHECK(!code_signing_input_phony.value().empty());
+  OutputFile code_signing_input_stamp_file =
+      WriteCodeSigningInputDepsStamp(order_only_deps, output_files);
+  DCHECK(!code_signing_input_stamp_file.value().empty());
 
   out_ << "build";
   std::vector<OutputFile> code_signing_output_files;
@@ -334,17 +321,17 @@
   path_output_.WriteFiles(out_, code_signing_output_files);
 
   // Since the code signature step depends on all the files from the bundle,
-  // the create_bundle phony can just depends on the output of the signature
+  // the create_bundle stamp can just depends on the output of the signature
   // script (dependencies are transitive).
   *output_files = std::move(code_signing_output_files);
 
   out_ << ": " << code_signing_rule_name;
   out_ << " | ";
-  path_output_.WriteFile(out_, code_signing_input_phony);
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
   out_ << std::endl;
 }
 
-OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsPhony(
+OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsStamp(
     const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* output_files) {
   std::vector<SourceFile> code_signing_input_files;
@@ -363,14 +350,15 @@
   if (code_signing_input_files.size() == 1 && order_only_deps.empty())
     return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
 
-  OutputFile code_signing_input_phony =
-      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-  code_signing_input_phony.value().append(target_->label().name());
-  code_signing_input_phony.value().append(".codesigning.inputdeps");
+  OutputFile code_signing_input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  code_signing_input_stamp_file.value().append(target_->label().name());
+  code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp");
 
   out_ << "build ";
-  path_output_.WriteFile(out_, code_signing_input_phony);
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony;
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
 
   for (const SourceFile& source : code_signing_input_files) {
     out_ << " ";
@@ -381,5 +369,5 @@
     path_output_.WriteFiles(out_, order_only_deps);
   }
   out_ << std::endl;
-  return code_signing_input_phony;
+  return code_signing_input_stamp_file;
 }
diff --git a/src/gn/ninja_create_bundle_target_writer.h b/src/gn/ninja_create_bundle_target_writer.h
index 189a524..6d4e224 100644
--- a/src/gn/ninja_create_bundle_target_writer.h
+++ b/src/gn/ninja_create_bundle_target_writer.h
@@ -46,9 +46,9 @@
       const std::vector<OutputFile>& order_only_deps,
       std::vector<OutputFile>* output_files);
 
-  // Writes the phony target for the assets catalog compilation input
+  // Writes the stamp file for the assets catalog compilation input
   // dependencies.
-  OutputFile WriteCompileAssetsCatalogInputDepsPhony(
+  OutputFile WriteCompileAssetsCatalogInputDepsStamp(
       const std::vector<const Target*>& dependencies);
 
   // Writes the code signing step (if a script is defined).
@@ -60,8 +60,8 @@
                             const std::vector<OutputFile>& order_only_deps,
                             std::vector<OutputFile>* output_files);
 
-  // Writes the phony target for the code signing input dependencies.
-  OutputFile WriteCodeSigningInputDepsPhony(
+  // Writes the stamp file for the code signing input dependencies.
+  OutputFile WriteCodeSigningInputDepsStamp(
       const std::vector<OutputFile>& order_only_deps,
       std::vector<OutputFile>* output_files);
 
diff --git a/src/gn/ninja_create_bundle_target_writer_unittest.cc b/src/gn/ninja_create_bundle_target_writer_unittest.cc
index 77598c8..d2c5c24 100644
--- a/src/gn/ninja_create_bundle_target_writer_unittest.cc
+++ b/src/gn/ninja_create_bundle_target_writer_unittest.cc
@@ -75,17 +75,17 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/baz/bar.inputdeps: phony phony/foo/bar "
-      "phony/foo/data\n"
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
       "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
-      "../../foo/input1.txt || phony/baz/bar.inputdeps\n"
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
       "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
-      "../../foo/input2.txt || phony/baz/bar.inputdeps\n"
-      "build phony/baz/bar: phony "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
       "bar.bundle/Contents/Resources/input1.txt "
       "bar.bundle/Contents/Resources/input2.txt"
-      " || phony/baz/bar.inputdeps\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      " || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -124,17 +124,17 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/baz/bar.inputdeps: phony phony/foo/bar "
-      "phony/foo/data\n"
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
       "build gen/bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
-      "../../foo/input1.txt || phony/baz/bar.inputdeps\n"
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
       "build gen/bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
-      "../../foo/input2.txt || phony/baz/bar.inputdeps\n"
-      "build phony/baz/bar: phony "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
       "gen/bar.bundle/Contents/Resources/input1.txt "
       "gen/bar.bundle/Contents/Resources/input2.txt || "
-      "phony/baz/bar.inputdeps\n"
-      "build gen/bar.bundle: phony phony/baz/bar\n";
+      "obj/baz/bar.inputdeps.stamp\n"
+      "build gen/bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -165,10 +165,10 @@
   writer.Run();
 
   const char expected[] =
-      "build baz/bar/bar_partial_info.plist: stamp || phony/foo/bar\n"
-      "build phony/baz/bar: phony "
-      "baz/bar/bar_partial_info.plist || phony/foo/bar\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      "build baz/bar/bar_partial_info.plist: stamp || obj/foo/bar.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+      "baz/bar/bar_partial_info.plist || obj/foo/bar.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -225,17 +225,17 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/baz/bar.inputdeps: phony phony/foo/bar "
-      "phony/foo/data\n"
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
       "build bar.bundle/Contents/Resources/Assets.car: compile_xcassets "
-      "../../foo/Foo.xcassets | phony/foo/data || "
-      "phony/baz/bar.inputdeps\n"
+      "../../foo/Foo.xcassets | obj/foo/data.stamp || "
+      "obj/baz/bar.inputdeps.stamp\n"
       "  product_type = com.apple.product-type\n"
       "  xcasset_compiler_flags = --app-icon foo\n"
-      "build phony/baz/bar: phony "
+      "build obj/baz/bar.stamp: stamp "
       "bar.bundle/Contents/Resources/Assets.car || "
-      "phony/baz/bar.inputdeps\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      "obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -253,8 +253,6 @@
   SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
   create_bundle.set_output_type(Target::CREATE_BUNDLE);
   create_bundle.SetToolchain(setup.toolchain());
-  create_bundle.bundle_data().set_partial_info_plist(
-      SourceFile("//out/Debug/baz/bar/bar_partial_info.plist"));
   ASSERT_TRUE(create_bundle.OnResolved(&err));
 
   std::ostringstream out;
@@ -262,11 +260,10 @@
   writer.Run();
 
   const char expected[] =
-      "build baz/bar/bar_partial_info.plist: stamp\n"
-      "build phony/baz/bar: phony baz/bar/bar_partial_info.plist\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      "build obj/baz/bar.stamp: stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str) << out_str;
+  EXPECT_EQ(expected, out_str);
 }
 
 // Tests complex target with multiple bundle_data sources, including
@@ -379,32 +376,32 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/baz/bar.inputdeps: phony phony/biz/assets "
-      "phony/foo/assets phony/foo/bar phony/foo/data "
-      "phony/qux/info_plist phony/quz/assets\n"
+      "build obj/baz/bar.inputdeps.stamp: stamp obj/biz/assets.stamp "
+      "obj/foo/assets.stamp obj/foo/bar.stamp obj/foo/data.stamp "
+      "obj/qux/info_plist.stamp obj/quz/assets.stamp\n"
       "build bar.bundle/Contents/Info.plist: copy_bundle_data "
-      "../../qux/qux-Info.plist || phony/baz/bar.inputdeps\n"
+      "../../qux/qux-Info.plist || obj/baz/bar.inputdeps.stamp\n"
       "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
-      "../../foo/input1.txt || phony/baz/bar.inputdeps\n"
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
       "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
-      "../../foo/input2.txt || phony/baz/bar.inputdeps\n"
-      "build phony/baz/bar.xcassets.inputdeps: phony "
-      "phony/foo/assets "
-      "phony/quz/assets phony/biz/assets\n"
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.xcassets.inputdeps.stamp: stamp "
+      "obj/foo/assets.stamp "
+      "obj/quz/assets.stamp obj/biz/assets.stamp\n"
       "build bar.bundle/Contents/Resources/Assets.car | "
       "baz/bar/bar_partial_info.plist: compile_xcassets "
       "../../foo/Foo.xcassets ../../quz/Quz.xcassets "
-      "../../biz/Biz.xcassets | phony/baz/bar.xcassets.inputdeps || "
-      "phony/baz/bar.inputdeps\n"
+      "../../biz/Biz.xcassets | obj/baz/bar.xcassets.inputdeps.stamp || "
+      "obj/baz/bar.inputdeps.stamp\n"
       "  product_type = com.apple.product-type\n"
       "  partial_info_plist = baz/bar/bar_partial_info.plist\n"
-      "build phony/baz/bar: phony "
+      "build obj/baz/bar.stamp: stamp "
       "bar.bundle/Contents/Info.plist "
       "bar.bundle/Contents/Resources/input1.txt "
       "bar.bundle/Contents/Resources/input2.txt "
       "bar.bundle/Contents/Resources/Assets.car "
-      "baz/bar/bar_partial_info.plist || phony/baz/bar.inputdeps\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      "baz/bar/bar_partial_info.plist || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
@@ -461,30 +458,30 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/baz/bar.inputdeps: phony ./quz phony/foo/bar "
-      "phony/foo/data\n"
+      "build obj/baz/bar.inputdeps.stamp: stamp ./quz obj/foo/bar.stamp "
+      "obj/foo/data.stamp\n"
       "rule __baz_bar___toolchain_default__code_signing_rule\n"
       "  command =  ../../build/codesign.py -b=quz bar.bundle\n"
       "  description = CODE SIGNING //baz:bar(//toolchain:default)\n"
       "  restat = 1\n"
       "\n"
       "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
-      "../../foo/input1.txt || phony/baz/bar.inputdeps\n"
+      "../../foo/input1.txt || obj/baz/bar.inputdeps.stamp\n"
       "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
-      "../../foo/input2.txt || phony/baz/bar.inputdeps\n"
-      "build phony/baz/bar.codesigning.inputdeps: phony "
+      "../../foo/input2.txt || obj/baz/bar.inputdeps.stamp\n"
+      "build obj/baz/bar.codesigning.inputdeps.stamp: stamp "
       "../../build/codesign.py "
       "quz "
       "bar.bundle/Contents/Resources/input1.txt "
       "bar.bundle/Contents/Resources/input2.txt || "
-      "phony/baz/bar.inputdeps\n"
+      "obj/baz/bar.inputdeps.stamp\n"
       "build bar.bundle/Contents/quz bar.bundle/_CodeSignature/CodeResources: "
       "__baz_bar___toolchain_default__code_signing_rule "
-      "| phony/baz/bar.codesigning.inputdeps\n"
-      "build phony/baz/bar: phony "
+      "| obj/baz/bar.codesigning.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
       "bar.bundle/Contents/quz "
-      "bar.bundle/_CodeSignature/CodeResources || phony/baz/bar.inputdeps\n"
-      "build bar.bundle: phony phony/baz/bar\n";
+      "bar.bundle/_CodeSignature/CodeResources || obj/baz/bar.inputdeps.stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
diff --git a/src/gn/ninja_generated_file_target_writer.cc b/src/gn/ninja_generated_file_target_writer.cc
index f4508a5..6b0db1b 100644
--- a/src/gn/ninja_generated_file_target_writer.cc
+++ b/src/gn/ninja_generated_file_target_writer.cc
@@ -26,23 +26,19 @@
   // Write the file.
   GenerateFile();
 
-  // A generated_file target should generate a phony target with dependencies
+  // A generated_file target should generate a stamp file with dependencies
   // on each of the deps and data_deps in the target. The actual collection is
   // done at gen time, and so ninja doesn't need to know about it.
   std::vector<OutputFile> output_files;
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      output_files.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
+    output_files.push_back(pair.ptr->dependency_output_file());
 
   std::vector<OutputFile> data_output_files;
   const LabelTargetVector& data_deps = target_->data_deps();
-  for (const auto& pair : data_deps) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      data_output_files.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& pair : data_deps)
+    data_output_files.push_back(pair.ptr->dependency_output_file());
 
-  WritePhonyForTarget(output_files, data_output_files);
+  WriteStampForTarget(output_files, data_output_files);
 }
 
 void NinjaGeneratedFileTargetWriter::GenerateFile() {
diff --git a/src/gn/ninja_generated_file_target_writer_unittest.cc b/src/gn/ninja_generated_file_target_writer_unittest.cc
index c7eab68..e88ad49 100644
--- a/src/gn/ninja_generated_file_target_writer_unittest.cc
+++ b/src/gn/ninja_generated_file_target_writer_unittest.cc
@@ -54,7 +54,7 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/foo/bar: phony phony/foo/dep phony/foo/dep2 || "
-      "phony/foo/datadep\n";
+      "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
+      "obj/foo/datadep.stamp\n";
   EXPECT_EQ(expected, out.str());
 }
diff --git a/src/gn/ninja_group_target_writer.cc b/src/gn/ninja_group_target_writer.cc
index 5ba4ba6..b518977 100644
--- a/src/gn/ninja_group_target_writer.cc
+++ b/src/gn/ninja_group_target_writer.cc
@@ -17,20 +17,16 @@
 NinjaGroupTargetWriter::~NinjaGroupTargetWriter() = default;
 
 void NinjaGroupTargetWriter::Run() {
-  // A group rule just generates a phony target with dependencies on each of
+  // A group rule just generates a stamp file with dependencies on each of
   // the deps and data_deps in the group.
   std::vector<OutputFile> output_files;
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      output_files.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
+    output_files.push_back(pair.ptr->dependency_output_file());
 
   std::vector<OutputFile> data_output_files;
   const LabelTargetVector& data_deps = target_->data_deps();
-  for (const auto& pair : data_deps) {
-    if (pair.ptr->dependency_output_file_or_phony())
-      data_output_files.push_back(*pair.ptr->dependency_output_file_or_phony());
-  }
+  for (const auto& pair : data_deps)
+    data_output_files.push_back(pair.ptr->dependency_output_file());
 
-  WritePhonyForTarget(output_files, data_output_files);
+  WriteStampForTarget(output_files, data_output_files);
 }
diff --git a/src/gn/ninja_group_target_writer_unittest.cc b/src/gn/ninja_group_target_writer_unittest.cc
index 2408992..2280bb0 100644
--- a/src/gn/ninja_group_target_writer_unittest.cc
+++ b/src/gn/ninja_group_target_writer_unittest.cc
@@ -45,7 +45,7 @@
   writer.Run();
 
   const char expected[] =
-      "build phony/foo/bar: phony phony/foo/dep phony/foo/dep2 || "
-      "phony/foo/datadep\n";
+      "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
+      "obj/foo/datadep.stamp\n";
   EXPECT_EQ(expected, out.str());
 }
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 66e2804..59960a1 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -110,10 +110,10 @@
 void NinjaRustBinaryTargetWriter::Run() {
   DCHECK(target_->output_type() != Target::SOURCE_SET);
 
-  size_t num_output_uses = target_->sources().size();
+  size_t num_stamp_uses = target_->sources().size();
 
-  std::vector<OutputFile> input_deps =
-      WriteInputsPhonyAndGetDep(num_output_uses);
+  std::vector<OutputFile> input_deps = WriteInputsStampAndGetDep(
+      num_stamp_uses);
 
   WriteCompilerVars();
 
@@ -124,8 +124,8 @@
   // Ninja to make sure the inputs are up to date before compiling this source,
   // but changes in the inputs deps won't cause the file to be recompiled. See
   // the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation.
-  std::vector<OutputFile> order_only_deps = WriteInputDepsPhonyAndGetDep(
-      std::vector<const Target*>(), num_output_uses);
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
   std::copy(input_deps.begin(), input_deps.end(),
             std::back_inserter(order_only_deps));
 
@@ -144,19 +144,14 @@
                      classified_deps.extra_object_files.begin(),
                      classified_deps.extra_object_files.end());
   for (const auto* framework_dep : classified_deps.framework_deps) {
-    CHECK(framework_dep->dependency_output_file());
-    order_only_deps.push_back(*framework_dep->dependency_output_file());
+    order_only_deps.push_back(framework_dep->dependency_output_file());
   }
   for (const auto* non_linkable_dep : classified_deps.non_linkable_deps) {
-    if (non_linkable_dep->dependency_output_file_or_phony()) {
-      if (non_linkable_dep->source_types_used().RustSourceUsed() &&
-          non_linkable_dep->output_type() != Target::SOURCE_SET) {
-        rustdeps.push_back(
-            *non_linkable_dep->dependency_output_file_or_phony());
-      }
-      order_only_deps.push_back(
-          *non_linkable_dep->dependency_output_file_or_phony());
+    if (non_linkable_dep->source_types_used().RustSourceUsed() &&
+        non_linkable_dep->output_type() != Target::SOURCE_SET) {
+      rustdeps.push_back(non_linkable_dep->dependency_output_file());
     }
+    order_only_deps.push_back(non_linkable_dep->dependency_output_file());
   }
   for (const auto* linkable_dep : classified_deps.linkable_deps) {
     if (linkable_dep->source_types_used().RustSourceUsed()) {
@@ -164,8 +159,7 @@
     } else {
       nonrustdeps.push_back(linkable_dep->link_output_file());
     }
-    CHECK(linkable_dep->dependency_output_file());
-    implicit_deps.push_back(*linkable_dep->dependency_output_file());
+    implicit_deps.push_back(linkable_dep->dependency_output_file());
   }
 
   // Rust libraries specified by paths.
@@ -184,8 +178,7 @@
   for (const auto* dep :
        target_->rust_values().transitive_libs().GetOrdered()) {
     if (dep->source_types_used().RustSourceUsed()) {
-      CHECK(dep->dependency_output_file());
-      transitive_rustlibs.push_back(*dep->dependency_output_file());
+      transitive_rustlibs.push_back(dep->dependency_output_file());
     }
   }
 
@@ -256,7 +249,6 @@
   for (const Target* target : deps) {
     if (target->output_type() == Target::RUST_LIBRARY ||
         target->output_type() == Target::RUST_PROC_MACRO) {
-      CHECK(target->dependency_output_file());
       out_ << " --extern ";
       const auto& renamed_dep =
           target_->rust_values().aliased_deps().find(target->label());
@@ -265,7 +257,7 @@
       } else {
         out_ << std::string(target->rust_values().crate_name()) << "=";
       }
-      path_output_.WriteFile(out_, *target->dependency_output_file());
+      path_output_.WriteFile(out_, target->dependency_output_file());
     }
   }
 
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index d2446ac..34d87f0 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -244,7 +244,7 @@
         "\n"
         "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | "
         "../../bar/mylib.rs ../../bar/lib.rs obj/bar/libmymacro.so || "
-        "phony/baz/group\n"
+        "obj/baz/group.stamp\n"
         "  externs = --extern mymacro=obj/bar/libmymacro.so\n"
         "  rustdeps = -Ldependency=obj/bar\n"
         "  sources = ../../bar/mylib.rs ../../bar/lib.rs\n";
@@ -436,7 +436,7 @@
         "../../foo/main.rs obj/baz/sourceset.csourceset.o "
         "obj/bar/libmylib.rlib "
         "obj/foo/libstatic.a ./libshared.so ./libshared_with_toc.so.TOC "
-        "|| phony/baz/sourceset\n"
+        "|| obj/baz/sourceset.stamp\n"
         "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
         "  rustdeps = -Ldependency=obj/bar -Lnative=obj/baz -Lnative=obj/foo "
         "-Lnative=. -Clink-arg=obj/baz/sourceset.csourceset.o -lstatic "
@@ -748,7 +748,7 @@
         "target_output_name = bar\n"
         "\n"
         "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
-        "../../foo/main.rs obj/bar/libmylib.rlib || phony/baz/group\n"
+        "../../foo/main.rs obj/bar/libmylib.rlib || obj/baz/group.stamp\n"
         "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
         "  rustdeps = -Ldependency=obj/bar\n"
         "  sources = ../../foo/source.rs ../../foo/main.rs\n";
@@ -828,7 +828,7 @@
     writer.Run();
 
     const char expected[] =
-        "build phony/foo/bar.inputs: phony ../../foo/config.json ../../foo/template.h\n"
+        "build obj/foo/bar.inputs.stamp: stamp ../../foo/config.json ../../foo/template.h\n"
         "crate_name = foo_bar\n"
         "crate_type = bin\n"
         "output_extension = \n"
@@ -841,7 +841,7 @@
         "\n"
         "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs "
         "../../foo/main.rs ../../foo/config.json ../../foo/template.h "
-        "|| phony/foo/bar.inputs\n"
+        "|| obj/foo/bar.inputs.stamp\n"
         "  externs =\n"
         "  rustdeps =\n"
         "  sources = ../../foo/source.rs ../../foo/main.rs "
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index 802852c..b6bbb9f 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -8,7 +8,6 @@
 
 #include "base/files/file_util.h"
 #include "base/strings/string_util.h"
-#include "gn/builtin_tool.h"
 #include "gn/config_values_extractors.h"
 #include "gn/err.h"
 #include "gn/escape.h"
@@ -188,16 +187,16 @@
     out_ << std::endl;
 }
 
-std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsPhonyAndGetDep(
+std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
     const std::vector<const Target*>& extra_hard_deps,
-    size_t num_output_uses) const {
+    size_t num_stamp_uses) const {
   CHECK(target_->toolchain()) << "Toolchain not set on target "
                               << target_->label().GetUserVisibleName(true);
 
   // ----------
   // Collect all input files that are input deps of this target. Knowing the
   // number before writing allows us to either skip writing the input deps
-  // phony or optimize it. Use pointers to avoid copies here.
+  // stamp or optimize it. Use pointers to avoid copies here.
   std::vector<const SourceFile*> input_deps_sources;
   input_deps_sources.reserve(32);
 
@@ -246,7 +245,7 @@
   // Toolchain dependencies. These must be resolved before doing anything.
   // This just writes all toolchain deps for simplicity. If we find that
   // toolchains often have more than one dependency, we could consider writing
-  // a toolchain-specific phony target and only include the phony here.
+  // a toolchain-specific stamp file and only include the stamp here.
   // Note that these are usually empty/small.
   const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
   for (const auto& toolchain_dep : toolchain_deps) {
@@ -263,16 +262,14 @@
     return std::vector<OutputFile>();  // No input dependencies.
 
   // If we're only generating one input dependency, return it directly instead
-  // of writing a phony target for it.
+  // of writing a stamp file for it.
   if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
     return std::vector<OutputFile>{
         OutputFile(settings_->build_settings(), *input_deps_sources[0])};
   if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) {
-    const std::optional<OutputFile>& dep =
-        input_deps_targets[0]->dependency_output_file_or_phony();
-    if (!dep)
-      return std::vector<OutputFile>();
-    return std::vector<OutputFile>{*dep};
+    const OutputFile& dep = input_deps_targets[0]->dependency_output_file();
+    DCHECK(!dep.value().empty());
+    return std::vector<OutputFile>{dep};
   }
 
   std::vector<OutputFile> outs;
@@ -286,49 +283,48 @@
       input_deps_targets.begin(), input_deps_targets.end(),
       [](const Target* a, const Target* b) { return a->label() < b->label(); });
   for (auto* dep : input_deps_targets) {
-    if (dep->dependency_output_file_or_phony())
-      outs.push_back(*dep->dependency_output_file_or_phony());
+    DCHECK(!dep->dependency_output_file().value().empty());
+    outs.push_back(dep->dependency_output_file());
   }
 
-  // If there are multiple inputs, but the phony target would be referenced only
+  // If there are multiple inputs, but the stamp file would be referenced only
   // once, don't write it but depend on the inputs directly.
-  if (num_output_uses == 1u)
+  if (num_stamp_uses == 1u)
     return outs;
 
-  // Make a phony target. We don't need to worry about an empty phony target, as
-  // we would return early if there were no inputs.
-  CHECK(!outs.empty());
-  OutputFile input_phony_file =
-      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-  input_phony_file.value().append(target_->label().name());
-  input_phony_file.value().append(".inputdeps");
+  // Make a stamp file.
+  OutputFile input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  input_stamp_file.value().append(target_->label().name());
+  input_stamp_file.value().append(".inputdeps.stamp");
 
   out_ << "build ";
-  path_output_.WriteFile(out_, input_phony_file);
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony;
+  path_output_.WriteFile(out_, input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
   path_output_.WriteFiles(out_, outs);
 
   out_ << "\n";
-  return std::vector<OutputFile>{input_phony_file};
+  return std::vector<OutputFile>{input_stamp_file};
 }
 
-void NinjaTargetWriter::WritePhonyForTarget(
+void NinjaTargetWriter::WriteStampForTarget(
     const std::vector<OutputFile>& files,
     const std::vector<OutputFile>& order_only_deps) {
-  // If there's no phony, then we should not have any inputs and it is okay to
-  // omit the build rule.
-  if (!target_->dependency_output_phony()) {
-    CHECK(files.empty());
-    CHECK(order_only_deps.empty());
-    return;
-  }
-  const OutputFile& phony_target = *target_->dependency_output_phony();
-  CHECK(!phony_target.value().empty());
+  const OutputFile& stamp_file = target_->dependency_output_file();
+
+  // First validate that the target's dependency is a stamp file. Otherwise,
+  // we shouldn't have gotten here!
+  CHECK(base::EndsWith(stamp_file.value(), ".stamp",
+                       base::CompareCase::INSENSITIVE_ASCII))
+      << "Output should end in \".stamp\" for stamp file output. Instead got: "
+      << "\"" << stamp_file.value() << "\"";
 
   out_ << "build ";
-  path_output_.WriteFile(out_, phony_target);
+  path_output_.WriteFile(out_, stamp_file);
 
-  out_ << ": " << BuiltinTool::kBuiltinToolPhony;
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
   path_output_.WriteFiles(out_, files);
 
   if (!order_only_deps.empty()) {
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h
index c9c8841..f4c9eae 100644
--- a/src/gn/ninja_target_writer.h
+++ b/src/gn/ninja_target_writer.h
@@ -40,20 +40,21 @@
   // identified by the given bits will be written.
   void WriteSharedVars(const SubstitutionBits& bits);
 
-  // Writes to the output stream a phony rule for input dependencies, and
+  // Writes to the output stream a stamp rule for input dependencies, and
   // returns the file to be appended to source rules that encodes the
   // order-only dependencies for the current target.
-  // If num_output_uses is small, this might return all input dependencies
-  // directly, without writing a phony rule.
+  // If num_stamp_uses is small, this might return all input dependencies
+  // directly, without writing a stamp file.
   // If there are no implicit dependencies and no extra target dependencies
   // are passed in, this returns an empty vector.
-  std::vector<OutputFile> WriteInputDepsPhonyAndGetDep(
+  std::vector<OutputFile> WriteInputDepsStampAndGetDep(
       const std::vector<const Target*>& extra_hard_deps,
-      size_t num_output_uses) const;
+      size_t num_stamp_uses) const;
 
-  // Writes to the output file a final phony rule for the target that aliases
-  // the given list of files.
-  void WritePhonyForTarget(const std::vector<OutputFile>& deps,
+  // Writes to the output file a final stamp rule for the target that stamps
+  // the given list of files. This function assumes the stamp is for the target
+  // as a whole so the stamp file is set as the target's dependency output.
+  void WriteStampForTarget(const std::vector<OutputFile>& deps,
                            const std::vector<OutputFile>& order_only_deps);
 
   const Settings* settings_;  // Non-owning.
diff --git a/src/gn/ninja_target_writer_unittest.cc b/src/gn/ninja_target_writer_unittest.cc
index 300dbdd..1b19159 100644
--- a/src/gn/ninja_target_writer_unittest.cc
+++ b/src/gn/ninja_target_writer_unittest.cc
@@ -22,17 +22,17 @@
   void Run() override {}
 
   // Make this public so the test can call it.
-  std::vector<OutputFile> WriteInputDepsPhonyAndGetDep(
+  std::vector<OutputFile> WriteInputDepsStampAndGetDep(
       const std::vector<const Target*>& extra_hard_deps,
-      size_t num_output_uses) {
-    return NinjaTargetWriter::WriteInputDepsPhonyAndGetDep(extra_hard_deps,
-                                                           num_output_uses);
+      size_t num_stamp_uses) {
+    return NinjaTargetWriter::WriteInputDepsStampAndGetDep(extra_hard_deps,
+                                                           num_stamp_uses);
   }
 };
 
 }  // namespace
 
-TEST(NinjaTargetWriter, WriteInputDepsPhonyAndGetDep) {
+TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) {
   TestWithScope setup;
   Err err;
 
@@ -72,7 +72,7 @@
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream);
     std::vector<OutputFile> dep =
-        writer.WriteInputDepsPhonyAndGetDep(std::vector<const Target*>(), 10u);
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
     // Since there is only one dependency, it should just be returned and
     // nothing written to the stream.
@@ -86,12 +86,12 @@
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
     std::vector<OutputFile> dep =
-        writer.WriteInputDepsPhonyAndGetDep(std::vector<const Target*>(), 10u);
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
-    // Since there is only one dependency, a phony target will be returned
+    // Since there is only one dependency, a stamp file will be returned
     // directly without writing any additional rules.
     ASSERT_EQ(1u, dep.size());
-    EXPECT_EQ("phony/foo/base", dep[0].value());
+    EXPECT_EQ("obj/foo/base.stamp", dep[0].value());
   }
 
   {
@@ -107,7 +107,7 @@
         "build: __foo_action___rule | ../../foo/script.py"
         " ../../foo/action_source.txt ./target\n"
         "\n"
-        "build phony/foo/action: phony\n",
+        "build obj/foo/action.stamp: stamp\n",
         stream.str());
   }
 
@@ -117,19 +117,19 @@
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&action, setup.toolchain(), stream);
     std::vector<OutputFile> dep =
-        writer.WriteInputDepsPhonyAndGetDep(std::vector<const Target*>(), 10u);
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
     ASSERT_EQ(1u, dep.size());
-    EXPECT_EQ("phony/foo/action.inputdeps", dep[0].value());
+    EXPECT_EQ("obj/foo/action.inputdeps.stamp", dep[0].value());
     EXPECT_EQ(
-        "build phony/foo/action.inputdeps: phony ../../foo/script.py "
+        "build obj/foo/action.inputdeps.stamp: stamp ../../foo/script.py "
         "../../foo/action_source.txt ./target\n",
         stream.str());
   }
 }
 
-// Tests WriteInputDepsPhonyAndGetDep when toolchain deps are present.
-TEST(NinjaTargetWriter, WriteInputDepsPhonyAndGetDepWithToolchainDeps) {
+// Tests WriteInputDepsStampAndGetDep when toolchain deps are present.
+TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDepWithToolchainDeps) {
   TestWithScope setup;
   Err err;
 
@@ -153,11 +153,11 @@
   std::ostringstream stream;
   TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
   std::vector<OutputFile> dep =
-      writer.WriteInputDepsPhonyAndGetDep(std::vector<const Target*>(), 10u);
+      writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
-  // Since there is more than one dependency, a phony target will be returned
-  // and the rule for the phony target will be written to the stream.
+  // Since there is more than one dependency, a stamp file will be returned
+  // and the rule for the stamp file will be written to the stream.
   ASSERT_EQ(1u, dep.size());
-  EXPECT_EQ("phony/foo/setup", dep[0].value());
+  EXPECT_EQ("obj/foo/setup.stamp", dep[0].value());
   EXPECT_EQ("", stream.str());
 }
diff --git a/src/gn/runtime_deps.cc b/src/gn/runtime_deps.cc
index f04857f..3b6d683 100644
--- a/src/gn/runtime_deps.cc
+++ b/src/gn/runtime_deps.cc
@@ -176,7 +176,7 @@
       return false;
     }
 
-    std::optional<OutputFile> output_file;
+    OutputFile output_file;
     const char extension[] = ".runtime_deps";
     if (target->output_type() == Target::SHARED_LIBRARY ||
         target->output_type() == Target::LOADABLE_MODULE) {
@@ -185,20 +185,11 @@
       CHECK(!target->computed_outputs().empty());
       output_file =
           OutputFile(target->computed_outputs()[0].value() + extension);
-    } else if (target->dependency_output_file()) {
-      output_file =
-          OutputFile(target->dependency_output_file()->value() + extension);
     } else {
-      // If there is no dependency_output_file, this target's dependency output
-      // is either a phony alias or was elided entirely (due to lack of real
-      // inputs). In either case, there is no file to add an additional
-      // extension to, so we should compute our own name in the OBJ BuildDir.
-      output_file = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ);
-      output_file->value().append(target->GetComputedOutputName());
-      output_file->value().append(extension);
+      output_file =
+          OutputFile(target->dependency_output_file().value() + extension);
     }
-    if (output_file)
-      files_to_write->push_back(std::make_pair(*output_file, target));
+    files_to_write->push_back(std::make_pair(output_file, target));
   }
   return true;
 }
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
index b94bc47..0ad3a6b 100644
--- a/src/gn/switches.cc
+++ b/src/gn/switches.cc
@@ -229,12 +229,9 @@
   an output file "bar.so", GN will create a file "bar.so.runtime_deps" in the
   build directory.
 
-  For targets that don't generate an output file (such as source set, action,
-  copy or group), the runtime deps file will be in the output directory where an
-  output file would have been located. For example, the source_set target
-  "//foo:bar" would result in a runtime dependency file being written to
-  "<output_dir>/obj/foo/bar.runtime_deps". This is probably not useful; the
-  use-case for this feature is generally executable targets.
+  If a source set, action, copy, or group is listed, the runtime deps file will
+  correspond to the .stamp file corresponding to that target. This is probably
+  not useful; the use-case for this feature is generally executable targets.
 
   The runtime dependency file will list one file per line, with no escaping.
   The files will be relative to the root_build_dir. The first line of the file
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 6b28107..b8a8d05 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -5,7 +5,6 @@
 #include "gn/target.h"
 
 #include <stddef.h>
-#include <algorithm>
 
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
@@ -517,7 +516,8 @@
     if (!bundle_data().GetOutputsAsSourceFiles(settings(), this, outputs, err))
       return false;
   } else if (IsBinary() && output_type() != Target::SOURCE_SET) {
-    // Binary target with normal outputs (source sets have phony targets).
+    // Binary target with normal outputs (source sets have stamp outputs like
+    // groups).
     DCHECK(IsBinary()) << static_cast<int>(output_type());
     if (!build_complete) {
       // Can't access the toolchain for a target before the build is complete.
@@ -537,20 +537,15 @@
           output_file.AsSourceFile(settings()->build_settings()));
     }
   } else {
-    // Everything else (like a group or bundle_data) has a phony output. The
-    // dependency output phony should have computed what this is. This won't be
+    // Everything else (like a group or bundle_data) has a stamp output. The
+    // dependency output file should have computed what this is. This won't be
     // valid unless the build is complete.
     if (!build_complete) {
       *err = Err(loc_for_error, kBuildIncompleteMsg);
       return false;
     }
-
-    // The dependency output might be empty if there is no output file or a
-    // phony alias for a set of inputs.
-    if (dependency_output_file_or_phony()) {
-      outputs->push_back(dependency_output_file_or_phony()->AsSourceFile(
-          settings()->build_settings()));
-    }
+    outputs->push_back(
+        dependency_output_file().AsSourceFile(settings()->build_settings()));
   }
   return true;
 }
@@ -781,65 +776,25 @@
   bundle_data_.OnTargetResolved(this);
 }
 
-bool Target::HasRealInputs() const {
-  // This check is only necessary if this target will result in a phony target.
-  // Phony targets with no real inputs are treated as always dirty.
-
-  // Actions always have at least one input file: the script used to execute
-  // the action. As such, they will never have an input-less phony target. We
-  // check this first to elide the common checks.
-  if (output_type() == ACTION || output_type() == ACTION_FOREACH) {
-    return true;
-  }
-
-  // If any of this target's dependencies is non-phony target or a phony target
-  // with real inputs, then this target should be considered to have inputs.
-  for (const auto& pair : GetDeps(DEPS_ALL)) {
-    if (pair.ptr->dependency_output_file_or_phony()) {
-      return true;
-    }
-  }
-
-  if (output_type() == BUNDLE_DATA) {
-    return !sources().empty();
-  }
-  if (output_type() == CREATE_BUNDLE) {
-    // CREATE_BUNDLE targets pick up most of their inputs in the form of
-    // dependencies on bundle_data targets, which were checked above when
-    // looping through GetDeps. This code handles the remaining possible
-    // CREATE_BUNDLE inputs.
-    return !bundle_data().assets_catalog_sources().empty() ||
-           !bundle_data().partial_info_plist().is_null() ||
-           !bundle_data().code_signing_script().is_null();
-  }
-
-  // If any of this target's sources will result in output files, then this
-  // target should be considered to have real inputs.
-  std::vector<OutputFile> tool_outputs;
-  return std::any_of(
-      sources().begin(), sources().end(), [&, this](const auto& source) {
-        const char* tool_name = Tool::kToolNone;
-        return GetOutputFilesForSource(source, &tool_name, &tool_outputs);
-      });
-}
-
 bool Target::FillOutputFiles(Err* err) {
   const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
   bool check_tool_outputs = false;
   switch (output_type_) {
+    case GROUP:
+    case BUNDLE_DATA:
+    case CREATE_BUNDLE:
+    case SOURCE_SET:
+    case COPY_FILES:
     case ACTION:
     case ACTION_FOREACH:
-    case BUNDLE_DATA:
-    case COPY_FILES:
-    case CREATE_BUNDLE:
-    case GENERATED_FILE:
-    case GROUP:
-    case SOURCE_SET: {
-      if (HasRealInputs()) {
-        dependency_output_phony_ =
-            GetBuildDirForTargetAsOutputFile(this, BuildDirType::PHONY);
-        dependency_output_phony_->value().append(GetComputedOutputName());
-      }
+    case GENERATED_FILE: {
+      // These don't get linked to and use stamps which should be the first
+      // entry in the outputs. These stamps are named
+      // "<target_out_dir>/<targetname>.stamp".
+      dependency_output_file_ =
+          GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
+      dependency_output_file_.value().append(GetComputedOutputName());
+      dependency_output_file_.value().append(".stamp");
       break;
     }
     case EXECUTABLE:
@@ -854,7 +809,7 @@
 
       if (tool->runtime_outputs().list().empty()) {
         // Default to the first output for the runtime output.
-        runtime_outputs_.push_back(*dependency_output_file_);
+        runtime_outputs_.push_back(dependency_output_file_);
       } else {
         SubstitutionWriter::ApplyListToLinkerAsOutputFile(
             this, tool, tool->runtime_outputs(), &runtime_outputs_);
@@ -866,10 +821,9 @@
       // first output.
       CHECK(tool->outputs().list().size() >= 1);
       check_tool_outputs = true;
-      dependency_output_file_ =
+      link_output_file_ = dependency_output_file_ =
           SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
               this, tool, tool->outputs().list()[0]);
-      link_output_file_ = *dependency_output_file_;
       break;
     case RUST_PROC_MACRO:
     case SHARED_LIBRARY:
@@ -878,10 +832,9 @@
       if (const CTool* ctool = tool->AsC()) {
         if (ctool->link_output().empty() && ctool->depend_output().empty()) {
           // Default behavior, use the first output file for both.
-          dependency_output_file_ =
+          link_output_file_ = dependency_output_file_ =
               SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
                   this, tool, tool->outputs().list()[0]);
-          link_output_file_ = *dependency_output_file_;
         } else {
           // Use the tool-specified ones.
           if (!ctool->link_output().empty()) {
@@ -904,10 +857,9 @@
         }
       } else if (const RustTool* rstool = tool->AsRust()) {
         // Default behavior, use the first output file for both.
-        dependency_output_file_ =
+        link_output_file_ = dependency_output_file_ =
             SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
                 this, tool, tool->outputs().list()[0]);
-        link_output_file_ = *dependency_output_file_;
       }
       break;
     case UNKNOWN:
diff --git a/src/gn/target.h b/src/gn/target.h
index 1d26301..dbcfa69 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -320,7 +320,7 @@
   // action or a copy step, and the output library or executable file(s) from
   // binary targets.
   //
-  // It will NOT include phony targets or object files.
+  // It will NOT include stamp files and object files.
   const std::vector<OutputFile>& computed_outputs() const {
     return computed_outputs_;
   }
@@ -333,32 +333,15 @@
   // a dependency on this one. It could be the same as the link output file
   // (this will be the case for static libraries). For shared libraries it
   // could be the same or different than the link output file, depending on the
-  // system.
-  //
-  // The dependency output phony is only set when the target does not have an
-  // output file and is using a phony alias to represent it. The exception to
-  // this is for phony targets without any real inputs. Ninja treats empty phony
-  // targets as always dirty, so no other targets should depend on that target.
-  // In that scenario, both dependency_output_phony or dependency_output_file
-  // will be std::nullopt.
-  //
-  // Callers that do not care whether the dependency is represented by a file or
-  // a phony should use dependency_output_file_or_phony().
+  // system. For actions this will be the stamp file.
   //
   // These are only known once the target is resolved and will be empty before
   // that. This is a cache of the files to prevent every target that depends on
   // a given library from recomputing the same pattern.
   const OutputFile& link_output_file() const { return link_output_file_; }
-  const std::optional<OutputFile>& dependency_output_file() const {
+  const OutputFile& dependency_output_file() const {
     return dependency_output_file_;
   }
-  const std::optional<OutputFile>& dependency_output_phony() const {
-    return dependency_output_phony_;
-  }
-  const std::optional<OutputFile>& dependency_output_file_or_phony() const {
-    return dependency_output_file_ ? dependency_output_file_
-                                   : dependency_output_phony_;
-  }
 
   // The subset of computed_outputs that are considered runtime outputs.
   const std::vector<OutputFile>& runtime_outputs() const {
@@ -383,11 +366,6 @@
   // The |loc_for_error| is used to blame a location for any errors produced. It
   // can be empty if there is no range (like this is being called based on the
   // command-line.
-  //
-  // It is possible for |outputs| to be returned empty without an error being
-  // reported. This can occur when the output type will result in a phony alias
-  // target (like a source_set) that is omitted from build files when they have
-  // no real inputs.
   bool GetOutputsAsSourceFiles(const LocationRange& loc_for_error,
                                bool build_complete,
                                std::vector<SourceFile>* outputs,
@@ -424,11 +402,6 @@
   void PullRecursiveHardDeps();
   void PullRecursiveBundleData();
 
-  // Checks to see whether this target or any of its dependencies have real
-  // inputs. If not, this target should be omitted as a dependency. This check
-  // only applies to targets that will result in a phony rule.
-  bool HasRealInputs() const;
-
   // Fills the link and dependency output files when a target is resolved.
   bool FillOutputFiles(Err* err);
 
@@ -516,8 +489,7 @@
   // Output files. Empty until the target is resolved.
   std::vector<OutputFile> computed_outputs_;
   OutputFile link_output_file_;
-  std::optional<OutputFile> dependency_output_file_;
-  std::optional<OutputFile> dependency_output_phony_;
+  OutputFile dependency_output_file_;
   std::vector<OutputFile> runtime_outputs_;
 
   Metadata metadata_;
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index 2496696..792c530 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -726,8 +726,7 @@
   ASSERT_TRUE(target.OnResolved(&err));
 
   EXPECT_EQ("./liba.so", target.link_output_file().value());
-  ASSERT_TRUE(target.dependency_output_file());
-  EXPECT_EQ("./liba.so.TOC", target.dependency_output_file()->value());
+  EXPECT_EQ("./liba.so.TOC", target.dependency_output_file().value());
 
   ASSERT_EQ(1u, target.runtime_outputs().size());
   EXPECT_EQ("./liba.so", target.runtime_outputs()[0].value());
@@ -773,8 +772,7 @@
   ASSERT_TRUE(target.OnResolved(&err));
 
   EXPECT_EQ("./a.dll.lib", target.link_output_file().value());
-  ASSERT_TRUE(target.dependency_output_file());
-  EXPECT_EQ("./a.dll.lib", target.dependency_output_file()->value());
+  EXPECT_EQ("./a.dll.lib", target.dependency_output_file().value());
 
   ASSERT_EQ(2u, target.runtime_outputs().size());
   EXPECT_EQ("./a.dll", target.runtime_outputs()[0].value());
@@ -804,7 +802,6 @@
 
   Target target(setup.settings(), Label(SourceDir("//a/"), "a"));
   target.set_output_type(Target::SOURCE_SET);
-  target.sources().push_back(SourceFile("//a/source_file1.cc"));
   target.SetToolchain(&toolchain);
   Err err;
   ASSERT_TRUE(target.OnResolved(&err));
@@ -821,12 +818,12 @@
   EXPECT_EQ("input.cc.o", output[0].value()) << output[0].value();
 
   // Test GetOutputsAsSourceFiles(). Since this is a source set it should give a
-  // phony target.
+  // stamp file.
   std::vector<SourceFile> computed_outputs;
   EXPECT_TRUE(target.GetOutputsAsSourceFiles(LocationRange(), true,
                                              &computed_outputs, &err));
   ASSERT_EQ(1u, computed_outputs.size());
-  EXPECT_EQ("//out/Debug/phony/a/a", computed_outputs[0].value());
+  EXPECT_EQ("//out/Debug/obj/a/a.stamp", computed_outputs[0].value());
 }
 
 // Tests Target::GetOutputFilesForSource for action_foreach targets (these, like
diff --git a/src/gn/tool.cc b/src/gn/tool.cc
index 9297ce4..5e4186d 100644
--- a/src/gn/tool.cc
+++ b/src/gn/tool.cc
@@ -4,7 +4,6 @@
 
 #include "gn/tool.h"
 
-#include "gn/builtin_tool.h"
 #include "gn/c_tool.h"
 #include "gn/general_tool.h"
 #include "gn/rust_tool.h"
@@ -53,13 +52,6 @@
   return nullptr;
 }
 
-BuiltinTool* Tool::AsBuiltin() {
-  return nullptr;
-}
-const BuiltinTool* Tool::AsBuiltin() const {
-  return nullptr;
-}
-
 bool Tool::IsPatternInOutputList(const SubstitutionList& output_list,
                                  const SubstitutionPattern& pattern) const {
   for (const auto& cur : output_list.list()) {
@@ -350,7 +342,7 @@
 
 // static
 const char* Tool::GetToolTypeForTargetFinalOutput(const Target* target) {
-  // The contents of this list might be suprising (i.e. phony tool for copy
+  // The contents of this list might be suprising (i.e. stamp tool for copy
   // rules). See the header for why.
   // TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
   if (target->source_types_used().RustSourceUsed()) {
@@ -389,6 +381,8 @@
     }
   }
   switch (target->output_type()) {
+    case Target::GROUP:
+      return GeneralTool::kGeneralToolStamp;
     case Target::EXECUTABLE:
       return CTool::kCToolLink;
     case Target::SHARED_LIBRARY:
@@ -397,15 +391,15 @@
       return CTool::kCToolSolinkModule;
     case Target::STATIC_LIBRARY:
       return CTool::kCToolAlink;
+    case Target::SOURCE_SET:
+      return GeneralTool::kGeneralToolStamp;
     case Target::ACTION:
     case Target::ACTION_FOREACH:
     case Target::BUNDLE_DATA:
-    case Target::COPY_FILES:
     case Target::CREATE_BUNDLE:
+    case Target::COPY_FILES:
     case Target::GENERATED_FILE:
-    case Target::GROUP:
-    case Target::SOURCE_SET:
-      return BuiltinTool::kBuiltinToolPhony;
+      return GeneralTool::kGeneralToolStamp;
     default:
       NOTREACHED();
       return kToolNone;
diff --git a/src/gn/tool.h b/src/gn/tool.h
index 5e824c9..e64935f 100644
--- a/src/gn/tool.h
+++ b/src/gn/tool.h
@@ -24,7 +24,6 @@
 class CTool;
 class GeneralTool;
 class RustTool;
-class BuiltinTool;
 
 // To add a new Tool category, create a subclass implementing SetComplete()
 // Add a new category to ToolCategories
@@ -64,8 +63,6 @@
   virtual const GeneralTool* AsGeneral() const;
   virtual RustTool* AsRust();
   virtual const RustTool* AsRust() const;
-  virtual BuiltinTool* AsBuiltin();
-  virtual const BuiltinTool* AsBuiltin() const;
 
   // Basic information ---------------------------------------------------------
 
diff --git a/src/gn/toolchain.cc b/src/gn/toolchain.cc
index a00aec4..bfad81d 100644
--- a/src/gn/toolchain.cc
+++ b/src/gn/toolchain.cc
@@ -9,15 +9,13 @@
 #include <utility>
 
 #include "base/logging.h"
-#include "gn/builtin_tool.h"
 #include "gn/target.h"
 #include "gn/value.h"
 
 Toolchain::Toolchain(const Settings* settings,
                      const Label& label,
                      const SourceFileSet& build_dependency_files)
-    : Item(settings, label, build_dependency_files),
-      phony_tool_(BuiltinTool::kBuiltinToolPhony) {}
+    : Item(settings, label, build_dependency_files) {}
 
 Toolchain::~Toolchain() = default;
 
@@ -31,9 +29,6 @@
 
 Tool* Toolchain::GetTool(const char* name) {
   DCHECK(name != Tool::kToolNone);
-  if (name == BuiltinTool::kBuiltinToolPhony) {
-    return &phony_tool_;
-  }
   auto pair = tools_.find(name);
   if (pair != tools_.end()) {
     return pair->second.get();
@@ -43,9 +38,6 @@
 
 const Tool* Toolchain::GetTool(const char* name) const {
   DCHECK(name != Tool::kToolNone);
-  if (name == BuiltinTool::kBuiltinToolPhony) {
-    return &phony_tool_;
-  }
   auto pair = tools_.find(name);
   if (pair != tools_.end()) {
     return pair->second.get();
@@ -95,20 +87,6 @@
   return nullptr;
 }
 
-BuiltinTool* Toolchain::GetToolAsBuiltin(const char* name) {
-  if (Tool* tool = GetTool(name)) {
-    return tool->AsBuiltin();
-  }
-  return nullptr;
-}
-
-const BuiltinTool* Toolchain::GetToolAsBuiltin(const char* name) const {
-  if (const Tool* tool = GetTool(name)) {
-    return tool->AsBuiltin();
-  }
-  return nullptr;
-}
-
 void Toolchain::SetTool(std::unique_ptr<Tool> t) {
   DCHECK(t->name() != Tool::kToolNone);
   DCHECK(tools_.find(t->name()) == tools_.end());
@@ -142,11 +120,6 @@
   return GetToolAsRust(Tool::GetToolTypeForSourceType(type));
 }
 
-const BuiltinTool* Toolchain::GetToolForSourceTypeAsBuiltin(
-    SourceFile::Type type) const {
-  return GetToolAsBuiltin(Tool::GetToolTypeForSourceType(type));
-}
-
 const Tool* Toolchain::GetToolForTargetFinalOutput(const Target* target) const {
   return GetTool(Tool::GetToolTypeForTargetFinalOutput(target));
 }
@@ -165,8 +138,3 @@
     const Target* target) const {
   return GetToolAsRust(Tool::GetToolTypeForTargetFinalOutput(target));
 }
-
-const BuiltinTool* Toolchain::GetToolForTargetFinalOutputAsBuiltin(
-    const Target* target) const {
-  return GetToolAsBuiltin(Tool::GetToolTypeForTargetFinalOutput(target));
-}
diff --git a/src/gn/toolchain.h b/src/gn/toolchain.h
index e4215ea..eb5a60c 100644
--- a/src/gn/toolchain.h
+++ b/src/gn/toolchain.h
@@ -9,7 +9,6 @@
 #include <string_view>
 
 #include "base/logging.h"
-#include "gn/builtin_tool.h"
 #include "gn/item.h"
 #include "gn/label_ptr.h"
 #include "gn/scope.h"
@@ -63,8 +62,6 @@
   const CTool* GetToolAsC(const char* name) const;
   RustTool* GetToolAsRust(const char* name);
   const RustTool* GetToolAsRust(const char* name) const;
-  BuiltinTool* GetToolAsBuiltin(const char* name);
-  const BuiltinTool* GetToolAsBuiltin(const char* name) const;
 
   // Set a tool. When all tools are configured, you should call
   // ToolchainSetupComplete().
@@ -96,19 +93,16 @@
   const CTool* GetToolForSourceTypeAsC(SourceFile::Type type) const;
   const GeneralTool* GetToolForSourceTypeAsGeneral(SourceFile::Type type) const;
   const RustTool* GetToolForSourceTypeAsRust(SourceFile::Type type) const;
-  const BuiltinTool* GetToolForSourceTypeAsBuiltin(SourceFile::Type type) const;
 
   // Returns the tool that produces the final output for the given target type.
   // This isn't necessarily the tool you would expect. For copy target, this
-  // will return the phony tool instead since the final output of a copy
-  // target is a phony alias to the set of copies done so there is one output.
+  // will return the stamp tool instead since the final output of a copy
+  // target is to stamp the set of copies done so there is one output.
   const Tool* GetToolForTargetFinalOutput(const Target* target) const;
   const CTool* GetToolForTargetFinalOutputAsC(const Target* target) const;
   const GeneralTool* GetToolForTargetFinalOutputAsGeneral(
       const Target* target) const;
   const RustTool* GetToolForTargetFinalOutputAsRust(const Target* target) const;
-  const BuiltinTool* GetToolForTargetFinalOutputAsBuiltin(
-      const Target* target) const;
 
   const SubstitutionBits& substitution_bits() const {
     DCHECK(setup_complete_);
@@ -120,7 +114,6 @@
   }
 
  private:
-  BuiltinTool phony_tool_;
   std::map<const char*, std::unique_ptr<Tool>> tools_;
 
   bool setup_complete_ = false;
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc
index 6acff14..6ee56e8 100644
--- a/src/gn/visual_studio_writer.cc
+++ b/src/gn/visual_studio_writer.cc
@@ -521,14 +521,14 @@
 
   project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros"));
 
-  auto [ninja_target, ninja_target_is_phony] = GetNinjaTarget(target);
+  std::string ninja_target = GetNinjaTarget(target);
 
   {
     std::unique_ptr<XmlElementWriter> properties =
         project.SubElement("PropertyGroup");
     properties->SubElement("OutDir")->Text("$(SolutionDir)");
     properties->SubElement("TargetName")->Text("$(ProjectName)");
-    if (target->output_type() != Target::GROUP && !ninja_target_is_phony) {
+    if (target->output_type() != Target::GROUP) {
       properties->SubElement("TargetPath")->Text("$(OutDir)\\" + ninja_target);
     }
   }
@@ -904,20 +904,13 @@
   }
 }
 
-std::pair<std::string, bool> VisualStudioWriter::GetNinjaTarget(const Target* target) {
+std::string VisualStudioWriter::GetNinjaTarget(const Target* target) {
   std::ostringstream ninja_target_out;
-  bool is_phony = false;
-  OutputFile output_file;
-  if (target->dependency_output_file()) {
-    output_file = *target->dependency_output_file();
-  } else if (target->dependency_output_phony()) {
-    output_file = *target->dependency_output_phony();
-    is_phony = true;
-  }
-
-  ninja_path_output_.WriteFile(ninja_target_out, output_file);
+  DCHECK(!target->dependency_output_file().value().empty());
+  ninja_path_output_.WriteFile(ninja_target_out,
+                               target->dependency_output_file());
   std::string s = ninja_target_out.str();
   if (s.compare(0, 2, "./") == 0)
     s = s.substr(2);
-  return std::make_pair(s, is_phony);
+  return s;
 }
diff --git a/src/gn/visual_studio_writer.h b/src/gn/visual_studio_writer.h
index 55ae705..e4957a1 100644
--- a/src/gn/visual_studio_writer.h
+++ b/src/gn/visual_studio_writer.h
@@ -127,8 +127,7 @@
   // and updates |root_folder_dir_|. Also sets |parent_folder| for |projects_|.
   void ResolveSolutionFolders();
 
-  // Returns the ninja target string and whether the target is phony.
-  std::pair<std::string, bool> GetNinjaTarget(const Target* target);
+  std::string GetNinjaTarget(const Target* target);
 
   const BuildSettings* build_settings_;