gn: Write no stamp files for action inputs.

Also omit input stamp files that are referenced only once in
other places. Stamp files are apparently somewhat expensive
to create on Windows (crbug.com/787903)

Removes 1817 stamp files from the main toolchain.ninja file in
my chrome/linux build, and reduces toolchain.ninja size by 3MB.

No intended behavior change.

Bug: 810978

Change-Id: I33ed07bc473b245e4c27414012681f238980e9fa
Reviewed-on: https://chromium-review.googlesource.com/912536
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Nico Weber <thakis@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#536132}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: cf54d20bfce0f557b4d6d5cea5d46ce1c2fc7909
diff --git a/tools/gn/ninja_action_target_writer.cc b/tools/gn/ninja_action_target_writer.cc
index bc7b69d..263cf23 100644
--- a/tools/gn/ninja_action_target_writer.cc
+++ b/tools/gn/ninja_action_target_writer.cc
@@ -38,13 +38,13 @@
   for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
     extra_hard_deps.push_back(pair.ptr);
 
-  // For ACTIONs this is a bit inefficient since it creates an input dep
-  // stamp file even though we're only going to use it once. It would save a
-  // build step to skip this and write the order-only deps directly on the
-  // build rule. This should probably be handled by WriteInputDepsStampAndGetDep
-  // automatically if we supply a count of sources (so it can optimize based on
-  // how many times things would be duplicated).
-  OutputFile input_dep = WriteInputDepsStampAndGetDep(extra_hard_deps);
+  // For ACTIONs, the input deps appear only once in the generated ninja
+  // file, so WriteInputDepsStampAndGetDep() won't create a stamp file
+  // and the action will just depend on all the input deps directly.
+  size_t num_stamp_uses =
+      target_->output_type() == Target::ACTION ? 1u : target_->sources().size();
+  std::vector<OutputFile> input_deps =
+      WriteInputDepsStampAndGetDep(extra_hard_deps, num_stamp_uses);
   out_ << std::endl;
 
   // Collects all output files for writing below.
@@ -52,7 +52,7 @@
 
   if (target_->output_type() == Target::ACTION_FOREACH) {
     // Write separate build lines for each input source file.
-    WriteSourceRules(custom_rule_name, input_dep, &output_files);
+    WriteSourceRules(custom_rule_name, input_deps, &output_files);
   } else {
     DCHECK(target_->output_type() == Target::ACTION);
 
@@ -64,11 +64,11 @@
     path_output_.WriteFiles(out_, output_files);
 
     out_ << ": " << custom_rule_name;
-    if (!input_dep.value().empty()) {
+    if (!input_deps.empty()) {
       // As in WriteSourceRules, we want to force this target to rebuild any
       // time any of its dependencies change.
-      out_ << " | ";
-      path_output_.WriteFile(out_, input_dep);
+      out_ << " |";
+      path_output_.WriteFiles(out_, input_deps);
     }
     out_ << std::endl;
     if (target_->action_values().has_depfile()) {
@@ -88,6 +88,8 @@
   // 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 stamp file.
   std::vector<OutputFile> data_outs;
   for (const auto& dep : target_->data_deps())
     data_outs.push_back(dep.ptr->dependency_output_file());
@@ -158,7 +160,7 @@
 
 void NinjaActionTargetWriter::WriteSourceRules(
     const std::string& custom_rule_name,
-    const OutputFile& input_dep,
+    const std::vector<OutputFile>& input_deps,
     std::vector<OutputFile>* output_files) {
   EscapeOptions args_escape_options;
   args_escape_options.mode = ESCAPE_NINJA_COMMAND;
@@ -173,13 +175,13 @@
 
     out_ << ": " << custom_rule_name << " ";
     path_output_.WriteFile(out_, sources[i]);
-    if (!input_dep.value().empty()) {
+    if (!input_deps.empty()) {
       // Using "|" for the dependencies forces all implicit dependencies to be
       // fully up to date before running the action, and will re-run this
       // action if any input dependencies change. This is important because
       // this action may consume the outputs of previous steps.
-      out_ << " | ";
-      path_output_.WriteFile(out_, input_dep);
+      out_ << " |";
+      path_output_.WriteFiles(out_, input_deps);
     }
     out_ << std::endl;
 
diff --git a/tools/gn/ninja_action_target_writer.h b/tools/gn/ninja_action_target_writer.h
index 71c1239..d0010f9 100644
--- a/tools/gn/ninja_action_target_writer.h
+++ b/tools/gn/ninja_action_target_writer.h
@@ -39,10 +39,9 @@
   // Writes the rules for compiling each source, writing all output files
   // to the given vector.
   //
-  // input_dep is a file expressing the depencies common to all build steps.
-  // It will be a stamp file if there is more than one.
+  // input_deps are the dependencies common to all build steps.
   void WriteSourceRules(const std::string& custom_rule_name,
-                        const OutputFile& input_dep,
+                        const std::vector<OutputFile>& input_deps,
                         std::vector<OutputFile>* output_files);
 
   // Writes the output files generated by the output template for the given
diff --git a/tools/gn/ninja_action_target_writer_unittest.cc b/tools/gn/ninja_action_target_writer_unittest.cc
index 7f0d911..53f96c3 100644
--- a/tools/gn/ninja_action_target_writer_unittest.cc
+++ b/tools/gn/ninja_action_target_writer_unittest.cc
@@ -60,17 +60,16 @@
   NinjaActionTargetWriter writer(&target, out);
   writer.Run();
 
-  const char expected[] =
-      "rule __foo_bar___rule\n"
-      "  command = /usr/bin/python ../../foo/script.py\n"
-      "  description = ACTION //foo:bar()\n"
-      "  restat = 1\n"
-      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-          "../../foo/included.txt\n"
-      "\n"
-      "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
-      "\n"
-      "build obj/foo/bar.stamp: stamp foo.out\n";
+  const char* expected = 1 /* skip initial newline */ + R"(
+rule __foo_bar___rule
+  command = /usr/bin/python ../../foo/script.py
+  description = ACTION //foo:bar()
+  restat = 1
+
+build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
+
+build obj/foo/bar.stamp: stamp foo.out
+)";
   EXPECT_EQ(expected, out.str());
 }
 
@@ -105,18 +104,17 @@
   NinjaActionTargetWriter writer(&target, out);
   writer.Run();
 
-  const char expected[] =
-      "rule __foo_bar___rule\n"
-      "  command = /usr/bin/python ../../foo/script.py\n"
-      "  description = ACTION //foo:bar()\n"
-      "  restat = 1\n"
-      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-          "../../foo/included.txt\n"
-      "\n"
-      "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
-      "  pool = foo_pool\n"
-      "\n"
-      "build obj/foo/bar.stamp: stamp foo.out\n";
+  const char* expected = 1 /* skip initial newline */ + R"(
+rule __foo_bar___rule
+  command = /usr/bin/python ../../foo/script.py
+  description = ACTION //foo:bar()
+  restat = 1
+
+build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
+  pool = foo_pool
+
+build obj/foo/bar.stamp: stamp foo.out
+)";
   EXPECT_EQ(expected, out.str());
 }
 
@@ -152,10 +150,9 @@
       "  command = /usr/bin/python ../../foo/script.py\n"
       "  description = ACTION //foo:bar()\n"
       "  restat = 1\n"
-      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-          "../../foo/included.txt ../../foo/source.txt\n"
       "\n"
-      "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
+      "build foo.out: __foo_bar___rule | ../../foo/script.py "
+      "../../foo/included.txt ../../foo/source.txt\n"
       "\n"
       "build obj/foo/bar.stamp: stamp foo.out\n";
   EXPECT_EQ(expected_linux, out.str());
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 14a630c..0e2bab0 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -293,16 +293,24 @@
   // this case it's sufficient to ensure that the upstream dependencies are
   // built first. This is exactly what Ninja's order-only dependencies
   // expresses.
-  OutputFile order_only_dep =
-      WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+  //
+  // 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 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.
+  size_t num_stamp_uses = target_->sources().size();
+  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
   // |pch_other_files|. This is to prevent linking against them.
   std::vector<OutputFile> pch_obj_files;
   std::vector<OutputFile> pch_other_files;
-  WritePCHCommands(used_types, input_dep, order_only_dep,
-                   &pch_obj_files, &pch_other_files);
+  WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
+                   &pch_other_files);
   std::vector<OutputFile>* pch_files = !pch_obj_files.empty() ?
       &pch_obj_files : &pch_other_files;
 
@@ -320,7 +328,8 @@
   //    object file list.
   std::vector<OutputFile> obj_files;
   std::vector<SourceFile> other_files;
-  WriteSources(*pch_files, input_dep, order_only_dep, &obj_files, &other_files);
+  WriteSources(*pch_files, input_dep, order_only_deps, &obj_files,
+               &other_files);
 
   // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
   obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
@@ -499,7 +508,7 @@
 void NinjaBinaryTargetWriter::WritePCHCommands(
     const SourceFileTypeSet& used_types,
     const OutputFile& input_dep,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* object_files,
     std::vector<OutputFile>* other_files) {
   if (!target_->config_values().has_precompiled_headers())
@@ -509,29 +518,26 @@
   if (tool_c &&
       tool_c->precompiled_header_type() != Tool::PCH_NONE &&
       used_types.Get(SOURCE_C)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_C,
-                    Toolchain::TYPE_CC,
-                    tool_c->precompiled_header_type(),
-                    input_dep, order_only_dep, object_files, other_files);
+    WritePCHCommand(SUBSTITUTION_CFLAGS_C, Toolchain::TYPE_CC,
+                    tool_c->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
   }
   const Tool* tool_cxx = target_->toolchain()->GetTool(Toolchain::TYPE_CXX);
   if (tool_cxx &&
       tool_cxx->precompiled_header_type() != Tool::PCH_NONE &&
       used_types.Get(SOURCE_CPP)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_CC,
-                    Toolchain::TYPE_CXX,
-                    tool_cxx->precompiled_header_type(),
-                    input_dep, order_only_dep, object_files, other_files);
+    WritePCHCommand(SUBSTITUTION_CFLAGS_CC, Toolchain::TYPE_CXX,
+                    tool_cxx->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
   }
 
   const Tool* tool_objc = target_->toolchain()->GetTool(Toolchain::TYPE_OBJC);
   if (tool_objc &&
       tool_objc->precompiled_header_type() == Tool::PCH_GCC &&
       used_types.Get(SOURCE_M)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC,
-                    Toolchain::TYPE_OBJC,
-                    tool_objc->precompiled_header_type(),
-                    input_dep, order_only_dep, object_files, other_files);
+    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, Toolchain::TYPE_OBJC,
+                    tool_objc->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
   }
 
   const Tool* tool_objcxx =
@@ -539,10 +545,9 @@
   if (tool_objcxx &&
       tool_objcxx->precompiled_header_type() == Tool::PCH_GCC &&
       used_types.Get(SOURCE_MM)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC,
-                    Toolchain::TYPE_OBJCXX,
-                    tool_objcxx->precompiled_header_type(),
-                    input_dep, order_only_dep, object_files, other_files);
+    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, Toolchain::TYPE_OBJCXX,
+                    tool_objcxx->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
   }
 }
 
@@ -551,16 +556,16 @@
     Toolchain::ToolType tool_type,
     Tool::PrecompiledHeaderType header_type,
     const OutputFile& input_dep,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* object_files,
     std::vector<OutputFile>* other_files) {
   switch (header_type) {
     case Tool::PCH_MSVC:
-      WriteWindowsPCHCommand(flag_type, tool_type, input_dep, order_only_dep,
+      WriteWindowsPCHCommand(flag_type, tool_type, input_dep, order_only_deps,
                              object_files);
       break;
     case Tool::PCH_GCC:
-      WriteGCCPCHCommand(flag_type, tool_type, input_dep, order_only_dep,
+      WriteGCCPCHCommand(flag_type, tool_type, input_dep, order_only_deps,
                          other_files);
       break;
     case Tool::PCH_NONE:
@@ -573,7 +578,7 @@
     SubstitutionType flag_type,
     Toolchain::ToolType tool_type,
     const OutputFile& input_dep,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* gch_files) {
   // Compute the pch output file (it will be language-specific).
   std::vector<OutputFile> outputs;
@@ -589,7 +594,7 @@
 
   // Build line to compile the file.
   WriteCompilerBuildLine(target_->config_values().precompiled_source(),
-                         extra_deps, order_only_dep, tool_type, outputs);
+                         extra_deps, order_only_deps, tool_type, outputs);
 
   // This build line needs a custom language-specific flags value. Rule-specific
   // variables are just indented underneath the rule line.
@@ -625,7 +630,7 @@
     SubstitutionType flag_type,
     Toolchain::ToolType tool_type,
     const OutputFile& input_dep,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* object_files) {
   // Compute the pch output file (it will be language-specific).
   std::vector<OutputFile> outputs;
@@ -641,7 +646,7 @@
 
   // Build line to compile the file.
   WriteCompilerBuildLine(target_->config_values().precompiled_source(),
-                         extra_deps, order_only_dep, tool_type, outputs);
+                         extra_deps, order_only_deps, tool_type, outputs);
 
   // This build line needs a custom language-specific flags value. Rule-specific
   // variables are just indented underneath the rule line.
@@ -660,7 +665,7 @@
 void NinjaBinaryTargetWriter::WriteSources(
     const std::vector<OutputFile>& pch_deps,
     const OutputFile& input_dep,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     std::vector<OutputFile>* object_files,
     std::vector<SourceFile>* other_files) {
   object_files->reserve(object_files->size() + target_->sources().size());
@@ -709,7 +714,7 @@
           }
         }
       }
-      WriteCompilerBuildLine(source, deps, order_only_dep, tool_type,
+      WriteCompilerBuildLine(source, deps, order_only_deps, tool_type,
                              tool_outputs);
     }
 
@@ -723,7 +728,7 @@
 void NinjaBinaryTargetWriter::WriteCompilerBuildLine(
     const SourceFile& source,
     const std::vector<OutputFile>& extra_deps,
-    const OutputFile& order_only_dep,
+    const std::vector<OutputFile>& order_only_deps,
     Toolchain::ToolType tool_type,
     const std::vector<OutputFile>& outputs) {
   out_ << "build";
@@ -735,15 +740,12 @@
 
   if (!extra_deps.empty()) {
     out_ << " |";
-    for (const OutputFile& dep : extra_deps) {
-      out_ << " ";
-      path_output_.WriteFile(out_, dep);
-    }
+    path_output_.WriteFiles(out_, extra_deps);
   }
 
-  if (!order_only_dep.value().empty()) {
-    out_ << " || ";
-    path_output_.WriteFile(out_, order_only_dep);
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
   }
   out_ << std::endl;
 }
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index 0188720..93e5b2d 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -61,7 +61,7 @@
   // compiling this target. It will be empty if there are no input deps.
   void WritePCHCommands(const SourceFileTypeSet& used_types,
                         const OutputFile& input_dep,
-                        const OutputFile& order_only_dep,
+                        const std::vector<OutputFile>& order_only_deps,
                         std::vector<OutputFile>* object_files,
                         std::vector<OutputFile>* other_files);
 
@@ -70,39 +70,39 @@
                        Toolchain::ToolType tool_type,
                        Tool::PrecompiledHeaderType header_type,
                        const OutputFile& input_dep,
-                       const OutputFile& order_only_dep,
+                       const std::vector<OutputFile>& order_only_deps,
                        std::vector<OutputFile>* object_files,
                        std::vector<OutputFile>* other_files);
 
   void WriteGCCPCHCommand(SubstitutionType flag_type,
                           Toolchain::ToolType tool_type,
                           const OutputFile& input_dep,
-                          const OutputFile& order_only_dep,
+                          const std::vector<OutputFile>& order_only_deps,
                           std::vector<OutputFile>* gch_files);
 
   void WriteWindowsPCHCommand(SubstitutionType flag_type,
                               Toolchain::ToolType tool_type,
                               const OutputFile& input_dep,
-                              const OutputFile& order_only_dep,
+                              const std::vector<OutputFile>& order_only_deps,
                               std::vector<OutputFile>* object_files);
 
   // pch_deps are additional dependencies to run before the rule. They are
   // expected to abide by the naming conventions specified by GetPCHOutputFiles.
   //
-  // order_only_dep is the name of the stamp file that covers the dependencies
-  // that must be run before doing any compiles.
+  // order_only_dep are the dependencies that must be run before doing any
+  // compiles.
   //
   // The files produced by the compiler will be added to two output vectors.
   void WriteSources(const std::vector<OutputFile>& pch_deps,
                     const OutputFile& input_dep,
-                    const OutputFile& order_only_dep,
+                    const std::vector<OutputFile>& order_only_deps,
                     std::vector<OutputFile>* object_files,
                     std::vector<SourceFile>* other_files);
 
   // Writes a build line.
   void WriteCompilerBuildLine(const SourceFile& source,
                               const std::vector<OutputFile>& extra_deps,
-                              const OutputFile& order_only_dep,
+                              const std::vector<OutputFile>& order_only_deps,
                               Toolchain::ToolType tool_type,
                               const std::vector<OutputFile>& outputs);
 
diff --git a/tools/gn/ninja_bundle_data_target_writer.cc b/tools/gn/ninja_bundle_data_target_writer.cc
index 88540bb..ec3cf74 100644
--- a/tools/gn/ninja_bundle_data_target_writer.cc
+++ b/tools/gn/ninja_bundle_data_target_writer.cc
@@ -21,10 +21,9 @@
         OutputFile(settings_->build_settings(), source_file));
   }
 
-  std::vector<const Target*> extra_hard_deps;
-  OutputFile input_dep = WriteInputDepsStampAndGetDep(extra_hard_deps);
-  if (!input_dep.value().empty())
-    output_files.push_back(input_dep);
+  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())
diff --git a/tools/gn/ninja_copy_target_writer.cc b/tools/gn/ninja_copy_target_writer.cc
index 462dbe3..194e2aa 100644
--- a/tools/gn/ninja_copy_target_writer.cc
+++ b/tools/gn/ninja_copy_target_writer.cc
@@ -70,8 +70,9 @@
       GetNinjaRulePrefixForToolchain(settings_) +
       Toolchain::ToolTypeToName(Toolchain::TYPE_COPY);
 
-  OutputFile input_dep =
-      WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+  size_t num_stamp_uses = target_->sources().size();
+  std::vector<OutputFile> input_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
 
   // Note that we don't write implicit deps for copy steps. "copy" only
   // depends on the output files themselves, rather than having includes
@@ -109,9 +110,9 @@
     path_output_.WriteFile(out_, output_file);
     out_ << ": " << tool_name << " ";
     path_output_.WriteFile(out_, input_file);
-    if (!input_dep.value().empty()) {
-      out_ << " || ";
-      path_output_.WriteFile(out_, input_dep);
+    if (!input_deps.empty()) {
+      out_ << " ||";
+      path_output_.WriteFiles(out_, input_deps);
     }
     out_ << std::endl;
   }
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 77af7a1..c8d1b84 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -179,8 +179,9 @@
     out_ << std::endl;
 }
 
-OutputFile NinjaTargetWriter::WriteInputDepsStampAndGetDep(
-    const std::vector<const Target*>& extra_hard_deps) const {
+std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
+    const std::vector<const Target*>& extra_hard_deps,
+    size_t num_stamp_uses) const {
   CHECK(target_->toolchain())
       << "Toolchain not set on target "
       << target_->label().GetUserVisibleName(true);
@@ -251,18 +252,39 @@
   // Write the outputs.
 
   if (input_deps_sources.size() + input_deps_targets.size() == 0)
-    return OutputFile();  // No input dependencies.
+    return std::vector<OutputFile>();  // No input dependencies.
 
   // If we're only generating one input dependency, return it directly instead
   // of writing a stamp file for it.
   if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
-    return OutputFile(settings_->build_settings(), *input_deps_sources[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 OutputFile& dep = input_deps_targets[0]->dependency_output_file();
     DCHECK(!dep.value().empty());
-    return dep;
+    return std::vector<OutputFile>{dep};
   }
 
+  std::vector<OutputFile> outs;
+  // File input deps.
+  for (const SourceFile* source : input_deps_sources)
+    outs.push_back(OutputFile(settings_->build_settings(), *source));
+  // Target input deps. Sort by label so the output is deterministic (otherwise
+  // some of the targets will have gone through std::sets which will have
+  // sorted them by pointer).
+  std::sort(
+      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) {
+    DCHECK(!dep->dependency_output_file().value().empty());
+    outs.push_back(dep->dependency_output_file());
+  }
+
+  // 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_stamp_uses == 1u)
+    return outs;
+
   // Make a stamp file.
   OutputFile input_stamp_file =
       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
@@ -274,27 +296,10 @@
   out_ << ": "
        << GetNinjaRulePrefixForToolchain(settings_)
        << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP);
-
-  // File input deps.
-  for (const SourceFile* source : input_deps_sources) {
-    out_ << " ";
-    path_output_.WriteFile(out_, *source);
-  }
-
-  // Target input deps. Sort by label so the output is deterministic (otherwise
-  // some of the targets will have gone through std::sets which will have
-  // sorted them by pointer).
-  std::sort(
-      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) {
-    DCHECK(!dep->dependency_output_file().value().empty());
-    out_ << " ";
-    path_output_.WriteFile(out_, dep->dependency_output_file());
-  }
+  path_output_.WriteFiles(out_, outs);
 
   out_ << "\n";
-  return input_stamp_file;
+  return std::vector<OutputFile>{input_stamp_file};
 }
 
 void NinjaTargetWriter::WriteStampForTarget(
diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h
index 5a3af18..dc9a885 100644
--- a/tools/gn/ninja_target_writer.h
+++ b/tools/gn/ninja_target_writer.h
@@ -42,11 +42,14 @@
 
   // 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. The returned OutputFile
-  // will be empty if there are no implicit dependencies and no extra target
-  // dependencies passed in.
-  OutputFile WriteInputDepsStampAndGetDep(
-      const std::vector<const Target*>& extra_hard_deps) const;
+  // order-only dependencies for the current target.
+  // 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> WriteInputDepsStampAndGetDep(
+      const std::vector<const Target*>& extra_hard_deps,
+      size_t num_stamp_uses) const;
 
   // 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
diff --git a/tools/gn/ninja_target_writer_unittest.cc b/tools/gn/ninja_target_writer_unittest.cc
index 0ffda3b..68a59c8 100644
--- a/tools/gn/ninja_target_writer_unittest.cc
+++ b/tools/gn/ninja_target_writer_unittest.cc
@@ -22,9 +22,11 @@
   void Run() override {}
 
   // Make this public so the test can call it.
-  OutputFile WriteInputDepsStampAndGetDep(
-      const std::vector<const Target*>& extra_hard_deps) {
-    return NinjaTargetWriter::WriteInputDepsStampAndGetDep(extra_hard_deps);
+  std::vector<OutputFile> WriteInputDepsStampAndGetDep(
+      const std::vector<const Target*>& extra_hard_deps,
+      size_t num_stamp_uses) {
+    return NinjaTargetWriter::WriteInputDepsStampAndGetDep(extra_hard_deps,
+                                                           num_stamp_uses);
   }
 };
 
@@ -69,12 +71,13 @@
   {
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream);
-    OutputFile dep =
-        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
     // Since there is only one dependency, it should just be returned and
     // nothing written to the stream.
-    EXPECT_EQ("../../foo/script.py", dep.value());
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("../../foo/script.py", dep[0].value());
     EXPECT_EQ("", stream.str());
   }
 
@@ -82,12 +85,13 @@
   {
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
-    OutputFile dep =
-        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
     // Since there is only one dependency, a stamp file will be returned
     // directly without writing any additional rules.
-    EXPECT_EQ("obj/foo/base.stamp", dep.value());
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("obj/foo/base.stamp", dep[0].value());
   }
 
   // Input deps for action which should depend on the base since its a hard dep
@@ -95,10 +99,11 @@
   {
     std::ostringstream stream;
     TestingNinjaTargetWriter writer(&action, setup.toolchain(), stream);
-    OutputFile dep =
-        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+    std::vector<OutputFile> dep =
+        writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
-    EXPECT_EQ("obj/foo/action.inputdeps.stamp", dep.value());
+    ASSERT_EQ(1u, dep.size());
+    EXPECT_EQ("obj/foo/action.inputdeps.stamp", dep[0].value());
     EXPECT_EQ("build obj/foo/action.inputdeps.stamp: stamp ../../foo/script.py "
                   "../../foo/action_source.txt obj/foo/base.stamp\n",
               stream.str());
@@ -129,11 +134,12 @@
 
   std::ostringstream stream;
   TestingNinjaTargetWriter writer(&target, setup.toolchain(), stream);
-  OutputFile dep =
-      writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>());
+  std::vector<OutputFile> dep =
+      writer.WriteInputDepsStampAndGetDep(std::vector<const Target*>(), 10u);
 
   // 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.
-  EXPECT_EQ("obj/foo/setup.stamp", dep.value());
+  ASSERT_EQ(1u, dep.size());
+  EXPECT_EQ("obj/foo/setup.stamp", dep[0].value());
   EXPECT_EQ("", stream.str());
 }