Add NinjaTargetWriter::resolved()

Allow NinjaTargetWriter instances to use a shared
ResolvedTargetData instance to compute values over
transitive dependency trees.

The instance can be set with SetResolvedTargetData(),
or will otherwise be created on demand when the
resolved() method is called.

Bug: 331
Change-Id: Ie464bfe3ab5f645e70bc20e4c315797f17336ad9
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15321
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index b960cc0..e14954d 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -43,11 +43,13 @@
 void NinjaBinaryTargetWriter::Run() {
   if (target_->source_types_used().RustSourceUsed()) {
     NinjaRustBinaryTargetWriter writer(target_, out_);
+    writer.SetResolvedTargetData(GetResolvedTargetData());
     writer.Run();
     return;
   }
 
   NinjaCBinaryTargetWriter writer(target_, out_);
+  writer.SetResolvedTargetData(GetResolvedTargetData());
   writer.Run();
 }
 
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index bd07f72..416d62e 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -40,10 +40,30 @@
                    settings_->build_settings()->root_path_utf8(),
                    ESCAPE_NINJA) {}
 
+void NinjaTargetWriter::SetResolvedTargetData(ResolvedTargetData* resolved) {
+  if (resolved) {
+    resolved_owned_.reset();
+    resolved_ptr_ = resolved;
+  }
+}
+
+ResolvedTargetData* NinjaTargetWriter::GetResolvedTargetData() {
+  return const_cast<ResolvedTargetData*>(&resolved());
+}
+
+const ResolvedTargetData& NinjaTargetWriter::resolved() const {
+  if (!resolved_ptr_) {
+    resolved_owned_ = std::make_unique<ResolvedTargetData>();
+    resolved_ptr_ = resolved_owned_.get();
+  }
+  return *resolved_ptr_;
+}
+
 NinjaTargetWriter::~NinjaTargetWriter() = default;
 
 // static
-std::string NinjaTargetWriter::RunAndWriteFile(const Target* target) {
+std::string NinjaTargetWriter::RunAndWriteFile(const Target* target,
+                                               ResolvedTargetData* resolved) {
   const Settings* settings = target->settings();
 
   ScopedTrace trace(TraceItem::TRACE_FILE_WRITE_NINJA,
@@ -76,26 +96,33 @@
   bool needs_file_write = false;
   if (target->output_type() == Target::BUNDLE_DATA) {
     NinjaBundleDataTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->output_type() == Target::CREATE_BUNDLE) {
     NinjaCreateBundleTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->output_type() == Target::COPY_FILES) {
     NinjaCopyTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->output_type() == Target::ACTION ||
              target->output_type() == Target::ACTION_FOREACH) {
     NinjaActionTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->output_type() == Target::GROUP) {
     NinjaGroupTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->output_type() == Target::GENERATED_FILE) {
     NinjaGeneratedFileTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else if (target->IsBinary()) {
     needs_file_write = true;
     NinjaBinaryTargetWriter writer(target, rules);
+    writer.SetResolvedTargetData(resolved);
     writer.Run();
   } else {
     CHECK(0) << "Output type of target not handled.";
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h
index 3d38a14..74378da 100644
--- a/src/gn/ninja_target_writer.h
+++ b/src/gn/ninja_target_writer.h
@@ -8,6 +8,7 @@
 #include <iosfwd>
 
 #include "gn/path_output.h"
+#include "gn/resolved_target_data.h"
 #include "gn/substitution_type.h"
 
 class OutputFile;
@@ -22,6 +23,17 @@
   NinjaTargetWriter(const Target* target, std::ostream& out);
   virtual ~NinjaTargetWriter();
 
+  // Returns a ResolvedTargetData that can be used to retrieve information
+  // from targets. The instance can be set through SetResolvedTargetData()
+  // or may be created on demand otherwise (which is useful to keep unit-tests
+  // simple).
+  const ResolvedTargetData& resolved() const;
+
+  // Sets the ResolvedTargetData instance to return for future resolved()
+  // calls. Does not transfer ownership, and allows several NinjaTargetWriter
+  // instances to share the same cached information.
+  void SetResolvedTargetData(ResolvedTargetData* resolved);
+
   // Returns the build line to be written to the toolchain build file.
   //
   // Some targets have their rules written to separate files, and some can have
@@ -29,11 +41,15 @@
   // function will return the rules as a string. For the separate file case,
   // the separate ninja file will be written and the return string will be the
   // subninja command to load that file.
-  static std::string RunAndWriteFile(const Target* target);
+  static std::string RunAndWriteFile(const Target* target,
+                                     ResolvedTargetData* resolved = nullptr);
 
   virtual void Run() = 0;
 
  protected:
+  // Returns a writable pointer to resolved(). Only used internally.
+  ResolvedTargetData* GetResolvedTargetData();
+
   // Writes out the substitution values that are shared between the different
   // types of tools (target gen dir, target label, etc.). Only the substitutions
   // identified by the given bits will be written.
@@ -78,6 +94,12 @@
   std::ostream& out_;
   PathOutput path_output_;
 
+  // The ResolvedTargetData instance can be set through SetResolvedTargetData()
+  // or it will be created lazily when resolved() is called, hence the need
+  // for 'mutable' here.
+  mutable ResolvedTargetData* resolved_ptr_ = nullptr;
+  mutable std::unique_ptr<ResolvedTargetData> resolved_owned_;
+
  private:
   void WriteCopyRules();
   void WriteEscapedSubstitution(const Substitution* type);
diff --git a/src/gn/ninja_target_writer_unittest.cc b/src/gn/ninja_target_writer_unittest.cc
index 7a04e3c..682de45 100644
--- a/src/gn/ninja_target_writer_unittest.cc
+++ b/src/gn/ninja_target_writer_unittest.cc
@@ -32,6 +32,48 @@
 
 }  // namespace
 
+TEST(NinjaTargetWriter, ResolvedCreatedOnDemand) {
+  TestWithScope setup;
+  Err err;
+
+  // Make a base target that's a hard dep (action).
+  Target base_target(setup.settings(), Label(SourceDir("//foo/"), "base"));
+  base_target.set_output_type(Target::ACTION);
+  base_target.visibility().SetPublic();
+  base_target.SetToolchain(setup.toolchain());
+  base_target.action_values().set_script(SourceFile("//foo/script.py"));
+  ASSERT_TRUE(base_target.OnResolved(&err));
+
+  std::ostringstream stream;
+  TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream);
+
+  const auto* resolved_ptr = &writer.resolved();
+  ASSERT_TRUE(resolved_ptr);
+
+  // Same address should be returned on second call.
+  EXPECT_EQ(resolved_ptr, &writer.resolved());
+}
+
+TEST(NinjaTargetWriter, ResolvedSetExplicitly) {
+  TestWithScope setup;
+  Err err;
+
+  // Make a base target that's a hard dep (action).
+  Target base_target(setup.settings(), Label(SourceDir("//foo/"), "base"));
+  base_target.set_output_type(Target::ACTION);
+  base_target.visibility().SetPublic();
+  base_target.SetToolchain(setup.toolchain());
+  base_target.action_values().set_script(SourceFile("//foo/script.py"));
+  ASSERT_TRUE(base_target.OnResolved(&err));
+
+  ResolvedTargetData resolved;
+  std::ostringstream stream;
+  TestingNinjaTargetWriter writer(&base_target, setup.toolchain(), stream);
+  writer.SetResolvedTargetData(&resolved);
+
+  EXPECT_EQ(&resolved, &writer.resolved());
+}
+
 TEST(NinjaTargetWriter, WriteInputDepsStampAndGetDep) {
   TestWithScope setup;
   Err err;
diff --git a/src/gn/swift_values.cc b/src/gn/swift_values.cc
index 3cfd377..85a49ad 100644
--- a/src/gn/swift_values.cc
+++ b/src/gn/swift_values.cc
@@ -54,7 +54,7 @@
 
 // static
 bool SwiftValues::FillModuleOutputFile(Target* target, Err* err) {
-  if (!target->IsBinary() || !target->source_types_used().SwiftSourceUsed())
+  if (!target->builds_swift_module())
     return true;
 
   const Tool* tool =
diff --git a/src/gn/target.h b/src/gn/target.h
index c965d1b..fbdc6c8 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -317,8 +317,7 @@
 
   // Return true if this targets builds a SwiftModule
   bool builds_swift_module() const {
-    return IsBinary() && has_swift_values() &&
-           source_types_used().SwiftSourceUsed();
+    return IsBinary() && source_types_used().SwiftSourceUsed();
   }
 
   RustValues& rust_values();