Add ResolvedTargetData::GetLinkedLibraries()

This CL removes Target::all_libs() and Target::all_lib_dirs()
and move their computation to the ResolvedTargetData class,
where the values will be generated on demand and returned by
the GetLinkedLibraries() and GetLinkedLibraryDirs() methods
respectively.

Bug: 331
Change-Id: I4ce91fca247ac4bbe8a51666f0f58fa55bfe925b
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15323
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 444a5e0..f82c079 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -14,6 +14,7 @@
 #include "gn/desc_builder.h"
 #include "gn/input_file.h"
 #include "gn/parse_tree.h"
+#include "gn/resolved_target_data.h"
 #include "gn/runtime_deps.h"
 #include "gn/rust_variables.h"
 #include "gn/scope.h"
@@ -582,10 +583,12 @@
     // currently implement a blame feature for this since the bottom-up
     // inheritance makes this difficult.
 
+    ResolvedTargetData resolved;
+
     // Libs can be part of any target and get recursively pushed up the chain,
     // so display them regardless of target type.
     if (what(variables::kLibs)) {
-      const UniqueVector<LibFile>& all_libs = target_->all_libs();
+      const auto& all_libs = resolved.GetLinkedLibraries(target_);
       if (!all_libs.empty()) {
         auto libs = std::make_unique<base::ListValue>();
         for (size_t i = 0; i < all_libs.size(); i++)
@@ -595,7 +598,7 @@
     }
 
     if (what(variables::kLibDirs)) {
-      const UniqueVector<SourceDir>& all_lib_dirs = target_->all_lib_dirs();
+      const auto& all_lib_dirs = resolved.GetLinkedLibraryDirs(target_);
       if (!all_lib_dirs.empty()) {
         auto lib_dirs = std::make_unique<base::ListValue>();
         for (size_t i = 0; i < all_lib_dirs.size(); i++)
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index b9ecc21..d9d403f 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -327,7 +327,7 @@
     const Tool* tool) {
   // Write library search paths that have been recursively pushed
   // through the dependency tree.
-  const UniqueVector<SourceDir>& all_lib_dirs = target_->all_lib_dirs();
+  const auto& all_lib_dirs = resolved().GetLinkedLibraryDirs(target_);
   if (!all_lib_dirs.empty()) {
     // Since we're passing these on the command line to the linker and not
     // to Ninja, we need to do shell escaping.
@@ -380,7 +380,7 @@
       ESCAPE_NINJA_COMMAND);
   EscapeOptions lib_escape_opts;
   lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
-  const UniqueVector<LibFile>& all_libs = target_->all_libs();
+  const auto& all_libs = resolved().GetLinkedLibraries(target_);
   for (size_t i = 0; i < all_libs.size(); i++) {
     const LibFile& lib_file = all_libs[i];
     const std::string& lib_value = lib_file.value();
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index c0f73b8..f74d208 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -642,7 +642,7 @@
   }
 
   // Libraries specified by paths.
-  for (const auto& lib : target_->all_libs()) {
+  for (const auto& lib : resolved().GetLinkedLibraries(target_)) {
     if (lib.is_source_file()) {
       implicit_deps.push_back(
           OutputFile(settings_->build_settings(), lib.source_file()));
diff --git a/src/gn/resolved_target_data.cc b/src/gn/resolved_target_data.cc
index 742a4cc..722858d 100644
--- a/src/gn/resolved_target_data.cc
+++ b/src/gn/resolved_target_data.cc
@@ -4,6 +4,8 @@
 
 #include "gn/resolved_target_data.h"
 
+#include "gn/config_values_extractors.h"
+
 ResolvedTargetData::TargetInfo* ResolvedTargetData::GetTargetInfo(
     const Target* target) const {
   auto ret = targets_.PushBackWithIndex(target);
@@ -12,3 +14,25 @@
   }
   return infos_[ret.second].get();
 }
+
+void ResolvedTargetData::ComputeLibInfo(TargetInfo* info) const {
+  UniqueVector<SourceDir> all_lib_dirs;
+  UniqueVector<LibFile> all_libs;
+
+  for (ConfigValuesIterator iter(info->target); !iter.done(); iter.Next()) {
+    const ConfigValues& cur = iter.cur();
+    all_lib_dirs.Append(cur.lib_dirs());
+    all_libs.Append(cur.libs());
+  }
+  for (const Target* dep : info->deps.linked_deps()) {
+    if (!dep->IsFinal() || dep->output_type() == Target::STATIC_LIBRARY) {
+      const TargetInfo* dep_info = GetTargetLibInfo(dep);
+      all_lib_dirs.Append(dep_info->lib_dirs);
+      all_libs.Append(dep_info->libs);
+    }
+  }
+
+  info->lib_dirs = all_lib_dirs.release();
+  info->libs = all_libs.release();
+  info->has_lib_info = true;
+}
diff --git a/src/gn/resolved_target_data.h b/src/gn/resolved_target_data.h
index 510d6f6..13d7797 100644
--- a/src/gn/resolved_target_data.h
+++ b/src/gn/resolved_target_data.h
@@ -60,6 +60,21 @@
     return GetTargetDeps(target).linked_deps();
   }
 
+  // The list of all library directory search path to add to the final link
+  // command of linkable binary. For example, if this returns ['dir1', 'dir2']
+  // a command for a C++ linker would typically use `-Ldir1 -Ldir2`.
+  const std::vector<SourceDir>& GetLinkedLibraryDirs(
+      const Target* target) const {
+    return GetTargetLibInfo(target)->lib_dirs;
+  }
+
+  // The list of all library files to add to the final link command of linkable
+  // binaries. For example, if this returns ['foo', '/path/to/bar'], the command
+  // for a C++ linker would typically use '-lfoo /path/to/bar'.
+  const std::vector<LibFile>& GetLinkedLibraries(const Target* target) const {
+    return GetTargetLibInfo(target)->libs;
+  }
+
  private:
   // The information associated with a given Target pointer.
   struct TargetInfo {
@@ -73,12 +88,32 @@
 
     const Target* target = nullptr;
     ResolvedTargetDeps deps;
+
+    bool has_lib_info = false;
+
+    // Only valid if |has_lib_info| is true.
+    std::vector<SourceDir> lib_dirs;
+    std::vector<LibFile> libs;
   };
 
   // Retrieve TargetInfo value associated with |target|. Create
   // a new empty instance on demand if none is already available.
   TargetInfo* GetTargetInfo(const Target* target) const;
 
+  const TargetInfo* GetTargetLibInfo(const Target* target) const {
+    TargetInfo* info = GetTargetInfo(target);
+    if (!info->has_lib_info) {
+      ComputeLibInfo(info);
+      DCHECK(info->has_lib_info);
+    }
+    return info;
+  }
+
+  // Compute the portion of TargetInfo guarded by one of the |has_xxx|
+  // booleans. This performs recursive and expensive computations and
+  // should only be called once per TargetInfo instance.
+  void ComputeLibInfo(TargetInfo* info) const;
+
   // A { Target* -> TargetInfo } map that will create entries
   // on demand (hence the mutable qualifier). Implemented with a
   // UniqueVector<> and a parallel vector of unique TargetInfo
diff --git a/src/gn/resolved_target_data_unittest.cc b/src/gn/resolved_target_data_unittest.cc
index 03cd28b..440c604 100644
--- a/src/gn/resolved_target_data_unittest.cc
+++ b/src/gn/resolved_target_data_unittest.cc
@@ -51,3 +51,61 @@
   EXPECT_EQ(b_deps.public_deps().size(), 0u);
   EXPECT_EQ(b_deps.data_deps().size(), 0u);
 }
+
+// Tests that lib[_dir]s are inherited across deps boundaries for static
+// libraries but not executables.
+TEST(ResolvedTargetDataTest, LibInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  ResolvedTargetData resolved;
+
+  const LibFile lib("foo");
+  const SourceDir libdir("/foo_dir/");
+
+  // Leaf target with ldflags set.
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
+  z.config_values().libs().push_back(lib);
+  z.config_values().lib_dirs().push_back(libdir);
+  ASSERT_TRUE(z.OnResolved(&err));
+
+  // All lib[_dir]s should be set when target is resolved.
+  const auto& all_libs = resolved.GetLinkedLibraries(&z);
+  ASSERT_EQ(1u, all_libs.size());
+  EXPECT_EQ(lib, all_libs[0]);
+
+  const auto& all_lib_dirs = resolved.GetLinkedLibraryDirs(&z);
+  ASSERT_EQ(1u, all_lib_dirs.size());
+  EXPECT_EQ(libdir, all_lib_dirs[0]);
+
+  // Shared library target should inherit the libs from the static library
+  // and its own. Its own flag should be before the inherited one.
+  const LibFile second_lib("bar");
+  const SourceDir second_libdir("/bar_dir/");
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
+  shared.config_values().libs().push_back(second_lib);
+  shared.config_values().lib_dirs().push_back(second_libdir);
+  shared.private_deps().push_back(LabelTargetPair(&z));
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  const auto& all_libs2 = resolved.GetLinkedLibraries(&shared);
+  ASSERT_EQ(2u, all_libs2.size());
+  EXPECT_EQ(second_lib, all_libs2[0]);
+  EXPECT_EQ(lib, all_libs2[1]);
+
+  const auto& all_lib_dirs2 = resolved.GetLinkedLibraryDirs(&shared);
+  ASSERT_EQ(2u, all_lib_dirs2.size());
+  EXPECT_EQ(second_libdir, all_lib_dirs2[0]);
+  EXPECT_EQ(libdir, all_lib_dirs2[1]);
+
+  // Executable target shouldn't get either by depending on shared.
+  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
+  exec.private_deps().push_back(LabelTargetPair(&shared));
+  ASSERT_TRUE(exec.OnResolved(&err));
+
+  const auto& all_libs3 = resolved.GetLinkedLibraries(&exec);
+  EXPECT_EQ(0u, all_libs3.size());
+
+  const auto& all_lib_dirs3 = resolved.GetLinkedLibraryDirs(&exec);
+  EXPECT_EQ(0u, all_lib_dirs3.size());
+}
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 2cebcf7..4d8cc56 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -493,9 +493,6 @@
   // order (local ones first, then the dependency's).
   for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
     const ConfigValues& cur = iter.cur();
-    all_lib_dirs_.Append(cur.lib_dirs().begin(), cur.lib_dirs().end());
-    all_libs_.Append(cur.libs().begin(), cur.libs().end());
-
     all_framework_dirs_.Append(cur.framework_dirs().begin(),
                                cur.framework_dirs().end());
     all_frameworks_.Append(cur.frameworks().begin(), cur.frameworks().end());
@@ -862,9 +859,6 @@
 
   // Library settings are always inherited across static library boundaries.
   if (!dep->IsFinal() || dep->output_type() == STATIC_LIBRARY) {
-    all_lib_dirs_.Append(dep->all_lib_dirs());
-    all_libs_.Append(dep->all_libs());
-
     all_framework_dirs_.Append(dep->all_framework_dirs());
     all_frameworks_.Append(dep->all_frameworks());
     all_weak_frameworks_.Append(dep->all_weak_frameworks());
diff --git a/src/gn/target.h b/src/gn/target.h
index fbdc6c8..c6f93c4 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -332,9 +332,6 @@
     return rust_transitive_inheritable_libs_;
   }
 
-  const UniqueVector<SourceDir>& all_lib_dirs() const { return all_lib_dirs_; }
-  const UniqueVector<LibFile>& all_libs() const { return all_libs_; }
-
   const UniqueVector<SourceDir>& all_framework_dirs() const {
     return all_framework_dirs_;
   }
@@ -504,11 +501,6 @@
   // that need to be linked.
   InheritedLibraries inherited_libraries_;
 
-  // These libs and dirs are inherited from statically linked deps and all
-  // configs applying to this target.
-  UniqueVector<SourceDir> all_lib_dirs_;
-  UniqueVector<LibFile> all_libs_;
-
   // These frameworks and dirs are inherited from statically linked deps and
   // all configs applying to this target.
   UniqueVector<SourceDir> all_framework_dirs_;
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index 005335d..7a3ca6e 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "gn/build_settings.h"
 #include "gn/config.h"
+#include "gn/resolved_target_data.h"
 #include "gn/scheduler.h"
 #include "gn/settings.h"
 #include "gn/test_with_scheduler.h"
@@ -36,52 +37,6 @@
 
 using TargetTest = TestWithScheduler;
 
-// Tests that lib[_dir]s are inherited across deps boundaries for static
-// libraries but not executables.
-TEST_F(TargetTest, LibInheritance) {
-  TestWithScope setup;
-  Err err;
-
-  const LibFile lib("foo");
-  const SourceDir libdir("/foo_dir/");
-
-  // Leaf target with ldflags set.
-  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
-  z.config_values().libs().push_back(lib);
-  z.config_values().lib_dirs().push_back(libdir);
-  ASSERT_TRUE(z.OnResolved(&err));
-
-  // All lib[_dir]s should be set when target is resolved.
-  ASSERT_EQ(1u, z.all_libs().size());
-  EXPECT_EQ(lib, z.all_libs()[0]);
-  ASSERT_EQ(1u, z.all_lib_dirs().size());
-  EXPECT_EQ(libdir, z.all_lib_dirs()[0]);
-
-  // Shared library target should inherit the libs from the static library
-  // and its own. Its own flag should be before the inherited one.
-  const LibFile second_lib("bar");
-  const SourceDir second_libdir("/bar_dir/");
-  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
-  shared.config_values().libs().push_back(second_lib);
-  shared.config_values().lib_dirs().push_back(second_libdir);
-  shared.private_deps().push_back(LabelTargetPair(&z));
-  ASSERT_TRUE(shared.OnResolved(&err));
-
-  ASSERT_EQ(2u, shared.all_libs().size());
-  EXPECT_EQ(second_lib, shared.all_libs()[0]);
-  EXPECT_EQ(lib, shared.all_libs()[1]);
-  ASSERT_EQ(2u, shared.all_lib_dirs().size());
-  EXPECT_EQ(second_libdir, shared.all_lib_dirs()[0]);
-  EXPECT_EQ(libdir, shared.all_lib_dirs()[1]);
-
-  // Executable target shouldn't get either by depending on shared.
-  TestTarget exec(setup, "//foo:exec", Target::EXECUTABLE);
-  exec.private_deps().push_back(LabelTargetPair(&shared));
-  ASSERT_TRUE(exec.OnResolved(&err));
-  EXPECT_EQ(0u, exec.all_libs().size());
-  EXPECT_EQ(0u, exec.all_lib_dirs().size());
-}
-
 // Tests that framework[_dir]s are inherited across deps boundaries for static
 // libraries but not executables.
 TEST_F(TargetTest, FrameworkInheritance) {
@@ -366,10 +321,14 @@
   EXPECT_EQ(&b, a_inherited[0]);
 
   // A should inherit the libs and lib_dirs from the C.
-  ASSERT_EQ(1u, a.all_libs().size());
-  EXPECT_EQ(lib, a.all_libs()[0]);
-  ASSERT_EQ(1u, a.all_lib_dirs().size());
-  EXPECT_EQ(lib_dir, a.all_lib_dirs()[0]);
+  ResolvedTargetData resolved;
+  const auto& a_all_libs = resolved.GetLinkedLibraries(&a);
+  ASSERT_EQ(1u, a_all_libs.size());
+  EXPECT_EQ(lib, a_all_libs[0]);
+
+  const auto& a_all_lib_dirs = resolved.GetLinkedLibraryDirs(&a);
+  ASSERT_EQ(1u, a_all_lib_dirs.size());
+  EXPECT_EQ(lib_dir, a_all_lib_dirs[0]);
 }
 
 TEST_F(TargetTest, InheritCompleteStaticLibStaticLibDeps) {
@@ -684,8 +643,10 @@
 
   // Libs have special handling, check that they were forwarded from the
   // public config to all_libs.
-  ASSERT_EQ(1u, dep_on_pub.all_libs().size());
-  ASSERT_EQ(lib_name, dep_on_pub.all_libs()[0]);
+  ResolvedTargetData resolved;
+  const auto& dep_on_pub_all_libs = resolved.GetLinkedLibraries(&dep_on_pub);
+  ASSERT_EQ(1u, dep_on_pub_all_libs.size());
+  ASSERT_EQ(lib_name, dep_on_pub_all_libs[0]);
 
   // This target has a private dependency on dest for forwards configs.
   TestTarget forward(setup, "//a:f", Target::SOURCE_SET);