Allow .o files for GN generated inputs.

GN checks inputs and sources for files that are in the build directory, and
requires those files be generated by a dependency of the target.

Previously, only the final outputs of a target were considered. But NaCl wants
to compile some CRT code and take one of the intermediate object files as
inputs to a subsequent action. The current code was not aware of the generated
object files.

This patch will check for generated object files when the normal checks for
generated inputs fails.

BUG=579629

Review URL: https://codereview.chromium.org/1607423002

Cr-Original-Commit-Position: refs/heads/master@{#370539}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 65e89854f57ac34a51e4c60ac663ce21110e6944
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 593d769..fa8849a 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -92,49 +92,6 @@
   PathOutput& path_output_;
 };
 
-// Computes the set of output files resulting from compiling the given source
-// file. If the file can be compiled and the tool exists, fills the outputs in
-// and writes the tool type to computed_tool_type. If the file is not
-// compilable, returns false.
-//
-// The target that the source belongs to is passed as an argument. In the case
-// of linking to source sets, this can be different than the target this class
-// is currently writing.
-//
-// The function can succeed with a "NONE" tool type for object files which are
-// just passed to the output. The output will always be overwritten, not
-// appended to.
-bool GetOutputFilesForSource(const Target* target,
-                             const SourceFile& source,
-                             Toolchain::ToolType* computed_tool_type,
-                             std::vector<OutputFile>* outputs) {
-  outputs->clear();
-  *computed_tool_type = Toolchain::TYPE_NONE;
-
-  SourceFileType file_type = GetSourceFileType(source);
-  if (file_type == SOURCE_UNKNOWN)
-    return false;
-  if (file_type == SOURCE_O) {
-    // Object files just get passed to the output and not compiled.
-    outputs->push_back(
-        OutputFile(target->settings()->build_settings(), source));
-    return true;
-  }
-
-  *computed_tool_type =
-      target->toolchain()->GetToolTypeForSourceType(file_type);
-  if (*computed_tool_type == Toolchain::TYPE_NONE)
-    return false;  // No tool for this file (it's a header file or something).
-  const Tool* tool = target->toolchain()->GetTool(*computed_tool_type);
-  if (!tool)
-    return false;  // Tool does not apply for this toolchain.file.
-
-  // Figure out what output(s) this compiler produces.
-  SubstitutionWriter::ApplyListToCompilerAsOutputFile(
-      target, source, tool->outputs(), outputs);
-  return !outputs->empty();
-}
-
 // Returns the language-specific suffix for precompiled header files.
 const char* GetPCHLangSuffixForToolType(Toolchain::ToolType type) {
   switch (type) {
@@ -256,7 +213,7 @@
   // the tool if there are more than one.
   for (const auto& source : source_set->sources()) {
     Toolchain::ToolType tool_type = Toolchain::TYPE_NONE;
-    if (GetOutputFilesForSource(source_set, source, &tool_type, &tool_outputs))
+    if (source_set->GetOutputFilesForSource(source, &tool_type, &tool_outputs))
       obj_files->push_back(tool_outputs[0]);
 
     used_types.Set(GetSourceFileType(source));
@@ -662,7 +619,7 @@
     // Clear the vector but maintain the max capacity to prevent reallocations.
     deps.resize(0);
     Toolchain::ToolType tool_type = Toolchain::TYPE_NONE;
-    if (!GetOutputFilesForSource(target_, source, &tool_type, &tool_outputs)) {
+    if (!target_->GetOutputFilesForSource(source, &tool_type, &tool_outputs)) {
       if (GetSourceFileType(source) == SOURCE_DEF)
         other_files->push_back(source);
       continue;  // No output for this source.
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index d3902c5..6c5d12b 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -67,11 +67,7 @@
   } else if (target->output_type() == Target::GROUP) {
     NinjaGroupTargetWriter writer(target, file);
     writer.Run();
-  } else if (target->output_type() == Target::EXECUTABLE ||
-             target->output_type() == Target::LOADABLE_MODULE ||
-             target->output_type() == Target::STATIC_LIBRARY ||
-             target->output_type() == Target::SHARED_LIBRARY ||
-             target->output_type() == Target::SOURCE_SET) {
+  } else if (target->IsBinary()) {
     NinjaBinaryTargetWriter writer(target, file);
     writer.Run();
   } else {
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 49a22fc..905ee4f 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include <algorithm>
+
 #include "base/bind.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -13,7 +15,10 @@
 #include "tools/gn/deps_iterator.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/scheduler.h"
+#include "tools/gn/source_file_type.h"
 #include "tools/gn/substitution_writer.h"
+#include "tools/gn/tool.h"
+#include "tools/gn/toolchain.h"
 #include "tools/gn/trace.h"
 
 namespace {
@@ -69,9 +74,15 @@
 //
 // Pass a pointer to an empty set for the first invocation. This will be used
 // to avoid duplicate checking.
+//
+// Checking of object files is optional because it is much slower. This allows
+// us to check targets for normal outputs, and then as a second pass check
+// object files (since we know it will be an error otherwise). This allows
+// us to avoid computing all object file names in the common case.
 bool EnsureFileIsGeneratedByDependency(const Target* target,
                                        const OutputFile& file,
                                        bool check_private_deps,
+                                       bool consider_object_files,
                                        std::set<const Target*>* seen_targets) {
   if (seen_targets->find(target) != seen_targets->end())
     return false;  // Already checked this one and it's not found.
@@ -85,11 +96,24 @@
       return true;
   }
 
+  // Check binary target intermediate files if requested.
+  if (consider_object_files && target->IsBinary()) {
+    std::vector<OutputFile> source_outputs;
+    for (const SourceFile& source : target->sources()) {
+      Toolchain::ToolType tool_type;
+      if (!target->GetOutputFilesForSource(source, &tool_type, &source_outputs))
+        continue;
+      if (std::find(source_outputs.begin(), source_outputs.end(), file) !=
+          source_outputs.end())
+        return true;
+    }
+  }
+
   // Check all public dependencies (don't do data ones since those are
   // runtime-only).
   for (const auto& pair : target->public_deps()) {
     if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
-                                          seen_targets))
+                                          consider_object_files, seen_targets))
       return true;  // Found a path.
   }
 
@@ -97,6 +121,7 @@
   if (check_private_deps) {
     for (const auto& pair : target->private_deps()) {
       if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                            consider_object_files,
                                             seen_targets))
         return true;  // Found a path.
     }
@@ -215,6 +240,14 @@
   return true;
 }
 
+bool Target::IsBinary() const {
+  return output_type_ == EXECUTABLE ||
+         output_type_ == SHARED_LIBRARY ||
+         output_type_ == LOADABLE_MODULE ||
+         output_type_ == STATIC_LIBRARY ||
+         output_type_ == SOURCE_SET;
+}
+
 bool Target::IsLinkable() const {
   return output_type_ == STATIC_LIBRARY || output_type_ == SHARED_LIBRARY;
 }
@@ -287,6 +320,34 @@
   return false;
 }
 
+bool Target::GetOutputFilesForSource(const SourceFile& source,
+                                     Toolchain::ToolType* computed_tool_type,
+                                     std::vector<OutputFile>* outputs) const {
+  outputs->clear();
+  *computed_tool_type = Toolchain::TYPE_NONE;
+
+  SourceFileType file_type = GetSourceFileType(source);
+  if (file_type == SOURCE_UNKNOWN)
+    return false;
+  if (file_type == SOURCE_O) {
+    // Object files just get passed to the output and not compiled.
+    outputs->push_back(OutputFile(settings()->build_settings(), source));
+    return true;
+  }
+
+  *computed_tool_type = toolchain_->GetToolTypeForSourceType(file_type);
+  if (*computed_tool_type == Toolchain::TYPE_NONE)
+    return false;  // No tool for this file (it's a header file or something).
+  const Tool* tool = toolchain_->GetTool(*computed_tool_type);
+  if (!tool)
+    return false;  // Tool does not apply for this toolchain.file.
+
+  // Figure out what output(s) this compiler produces.
+  SubstitutionWriter::ApplyListToCompilerAsOutputFile(
+      this, source, tool->outputs(), outputs);
+  return !outputs->empty();
+}
+
 void Target::PullDependentTargetConfigsFrom(const Target* dep) {
   MergeAllDependentConfigsFrom(dep, &configs_, &all_dependent_configs_);
   MergePublicConfigsFrom(dep, &configs_);
@@ -568,6 +629,13 @@
   // can be filtered out of this list.
   OutputFile out_file(settings()->build_settings(), source);
   std::set<const Target*> seen_targets;
-  if (!EnsureFileIsGeneratedByDependency(this, out_file, true, &seen_targets))
-    g_scheduler->AddUnknownGeneratedInput(this, source);
+  if (!EnsureFileIsGeneratedByDependency(this, out_file, true, false,
+                                         &seen_targets)) {
+    // Check object files (much slower and very rare) only if the "normal"
+    // output check failed.
+    seen_targets.clear();
+    if (!EnsureFileIsGeneratedByDependency(this, out_file, true, true,
+                                           &seen_targets))
+      g_scheduler->AddUnknownGeneratedInput(this, source);
+  }
 }
diff --git a/tools/gn/target.h b/tools/gn/target.h
index eff4d15..cbdab35 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -21,6 +21,7 @@
 #include "tools/gn/ordered_set.h"
 #include "tools/gn/output_file.h"
 #include "tools/gn/source_file.h"
+#include "tools/gn/toolchain.h"
 #include "tools/gn/unique_vector.h"
 
 class DepsIteratorRange;
@@ -66,6 +67,10 @@
   OutputType output_type() const { return output_type_; }
   void set_output_type(OutputType t) { output_type_ = t; }
 
+  // True for targets that compile source code (all types of libaries and
+  // executables).
+  bool IsBinary() const;
+
   // Can be linked into other targets.
   bool IsLinkable() const;
 
@@ -245,6 +250,18 @@
     return dependency_output_file_;
   }
 
+  // Computes the set of output files resulting from compiling the given source
+  // file. If the file can be compiled and the tool exists, fills the outputs
+  // in and writes the tool type to computed_tool_type. If the file is not
+  // compilable, returns false.
+  //
+  // The function can succeed with a "NONE" tool type for object files which
+  // are just passed to the output. The output will always be overwritten, not
+  // appended to.
+  bool GetOutputFilesForSource(const SourceFile& source,
+                               Toolchain::ToolType* computed_tool_type,
+                               std::vector<OutputFile>* outputs) const;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(Target, ResolvePrecompiledHeaders);
 
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 4d9b1da..c59f44c 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -587,6 +587,31 @@
   EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
 }
 
+// Tests that intermediate object files generated by binary targets are also
+// considered generated for the purposes of input checking. Above, we tested
+// the failure cases for generated inputs, so here only test .o files that are
+// present.
+TEST(Target, ObjectGeneratedInputs) {
+  Scheduler scheduler;
+  TestWithScope setup;
+  Err err;
+
+  // This target compiles the source.
+  SourceFile source_file("//source.cc");
+  TestTarget source_generator(setup, "//:source_target", Target::SOURCE_SET);
+  source_generator.sources().push_back(source_file);
+  EXPECT_TRUE(source_generator.OnResolved(&err));
+
+  // This is the object file that the test toolchain generates for the source.
+  SourceFile object_file("//out/Debug/obj/source_target.source.o");
+
+  TestTarget final_target(setup, "//:final", Target::ACTION);
+  final_target.inputs().push_back(object_file);
+  EXPECT_TRUE(final_target.OnResolved(&err));
+
+  AssertSchedulerHasOneUnknownFileMatching(&final_target, object_file);
+}
+
 TEST(Target, ResolvePrecompiledHeaders) {
   TestWithScope setup;
   Err err;