Add ResolvedTargetData class

This class re-implements some of the expensive recursive computations
over the build graph that are currently implemented in
Target::OnResolved(). This will allow, in a future CL, to move these
computations out of the graph builder, that runs exclusively on the
main thread, and into parallel worked pool threads.

Another benefit is that the computations are performed top-down
(i.e. by recursing from dependents to dependencies), which limits
them to only the targets that actually need them. By contrast, the
current code propagates values from dependencies to dependents
unconditionally, resulting in un-necessary work and data associated
for _every_ target in the graph.

To avoid duplicate work, each ResolvedTargetData call does record or
reuses intermediate target-specific values. This will be leveraged,
in yet another future CL, by setting up and using one
ResolvedTargetData instance per worker pool thread.

Future CLs will update the Ninja target writers to use instances of
this class, and remove the Target methods that return the same results.

+ Add TargetPublicPair to model a compact (target_ptr, is_public_flag)
  pair, and TargetPublicPairListBuilder to generate lists of such
  instances efficiently.

+ Add ResolvedTargetDeps which provides a compact iterator over
  different sets of target dependencies, since profiling shows
  that using a DepsIterator instance is much slower during
  graph-traversal heavy operations.

Both classes are used internally by ResolvedTargetData.

Bug: None
Change-Id: I83f2e3aed83e0ac1b96bb887e7f3f3f9a01a7156
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13624
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: David Turner <digit@google.com>
diff --git a/build/gen.py b/build/gen.py
index 4c75502..3ec0560 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -673,6 +673,7 @@
         'src/gn/pattern.cc',
         'src/gn/pool.cc',
         'src/gn/qt_creator_writer.cc',
+        'src/gn/resolved_target_data.cc',
         'src/gn/runtime_deps.cc',
         'src/gn/rust_substitution_type.cc',
         'src/gn/rust_tool.cc',
@@ -792,6 +793,8 @@
         'src/gn/path_output_unittest.cc',
         'src/gn/pattern_unittest.cc',
         'src/gn/pointer_set_unittest.cc',
+        'src/gn/resolved_target_data_unittest.cc',
+        'src/gn/resolved_target_deps_unittest.cc',
         'src/gn/runtime_deps_unittest.cc',
         'src/gn/scope_per_file_provider_unittest.cc',
         'src/gn/scope_unittest.cc',
@@ -805,6 +808,7 @@
         'src/gn/substitution_writer_unittest.cc',
         'src/gn/tagged_pointer_unittest.cc',
         'src/gn/target_unittest.cc',
+        'src/gn/target_public_pair_unittest.cc',
         'src/gn/template_unittest.cc',
         'src/gn/test_with_scheduler.cc',
         'src/gn/test_with_scope.cc',
diff --git a/src/gn/resolved_target_data.cc b/src/gn/resolved_target_data.cc
new file mode 100644
index 0000000..93064f9
--- /dev/null
+++ b/src/gn/resolved_target_data.cc
@@ -0,0 +1,461 @@
+// Copyright 2022 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 "gn/resolved_target_data.h"
+
+#include "gn/config_values_extractors.h"
+#include "gn/resolved_target_deps.h"
+
+using LibInfo = ResolvedTargetData::LibInfo;
+using FrameworkInfo = ResolvedTargetData::FrameworkInfo;
+
+namespace {
+
+struct TargetInfo {
+  TargetInfo() = default;
+
+  TargetInfo(const Target* target)
+      : target(target),
+        deps(target->public_deps(),
+             target->private_deps(),
+             target->data_deps()) {}
+
+  const Target* target = nullptr;
+  ResolvedTargetDeps deps;
+
+  bool has_lib_info = false;
+  bool has_framework_info = false;
+  bool has_hard_deps = false;
+  bool has_inherited_libs = false;
+  bool has_rust_libs = false;
+
+  // Only valid if |has_lib_info|.
+  ImmutableVector<SourceDir> lib_dirs;
+  ImmutableVector<LibFile> libs;
+
+  // Only valid if |has_framework_info|.
+  ImmutableVector<SourceDir> framework_dirs;
+  ImmutableVector<std::string> frameworks;
+  ImmutableVector<std::string> weak_frameworks;
+
+  // Only valid if |has_hard_deps|.
+  ImmutableVector<const Target*> hard_deps;
+
+  // Only valid if |has_inherited_libs|.
+  ImmutableVector<TargetPublicPair> inherited_libs;
+
+  // Only valid if |has_rust_libs|.
+  ImmutableVector<TargetPublicPair> rust_inherited_libs;
+  ImmutableVector<TargetPublicPair> rust_inheritable_libs;
+};
+
+}  // namespace
+
+// Implementation class for ResolvedTargetData.
+class ResolvedTargetData::Impl {
+ public:
+  LibInfo GetLibInfo(const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetLibInfo(target);
+    DCHECK(info->has_lib_info);
+    return LibInfo{
+        info->lib_dirs,
+        info->libs,
+    };
+  }
+
+  ImmutableVectorView<SourceDir> all_lib_dirs(const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetLibInfo(target);
+    DCHECK(info->has_lib_info);
+    return info->lib_dirs;
+  }
+
+  ImmutableVectorView<LibFile> all_libs(const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetLibInfo(target);
+    DCHECK(info->has_lib_info);
+    return info->libs;
+  }
+
+  FrameworkInfo GetFrameworkInfo(const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetFrameworkInfo(target);
+    DCHECK(info->has_framework_info);
+    return FrameworkInfo{
+        info->framework_dirs,
+        info->frameworks,
+        info->weak_frameworks,
+    };
+  }
+
+  ImmutableVectorView<SourceDir> all_framework_dirs(
+      const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetFrameworkInfo(target);
+    DCHECK(info->has_framework_info);
+    return info->framework_dirs;
+  }
+
+  ImmutableVectorView<std::string> all_frameworks(const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetFrameworkInfo(target);
+    DCHECK(info->has_framework_info);
+    return info->frameworks;
+  }
+
+  ImmutableVectorView<std::string> all_weak_frameworks(
+      const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetFrameworkInfo(target);
+    DCHECK(info->has_framework_info);
+    return info->weak_frameworks;
+  }
+
+  TargetSet recursive_hard_deps(const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    DCHECK(info->has_hard_deps);
+    if (!info->has_hard_deps)
+      ComputeHardDeps(info);
+
+    return TargetSet(info->hard_deps.begin(), info->hard_deps.end());
+  }
+
+  ImmutableVectorView<TargetPublicPair> inherited_libraries(
+      const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetInheritedLibs(target);
+    DCHECK(info->has_inherited_libs);
+    return info->inherited_libs;
+  }
+
+  ImmutableVectorView<TargetPublicPair> rust_transitive_inherited_libs(
+      const Target* target) const {
+    const TargetInfo* info = GetRecursiveTargetRustLibs(target);
+    DCHECK(info->has_rust_libs);
+    return info->rust_inherited_libs;
+  }
+
+ private:
+  const TargetInfo* GetRecursiveTargetLibInfo(const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    if (!info->has_lib_info)
+      ComputeLibInfo(info);
+    return info;
+  }
+
+  void 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 = GetRecursiveTargetLibInfo(dep);
+        all_lib_dirs.Append(dep_info->lib_dirs);
+        all_libs.Append(dep_info->libs);
+      }
+    }
+
+    info->lib_dirs = ImmutableVector<SourceDir>(all_lib_dirs.release());
+    info->libs = ImmutableVector<LibFile>(all_libs.release());
+    info->has_lib_info = true;
+  }
+
+  const TargetInfo* GetRecursiveTargetFrameworkInfo(
+      const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    if (!info->has_framework_info)
+      ComputeFrameworkInfo(info);
+    return info;
+  }
+
+  void ComputeFrameworkInfo(TargetInfo* info) const {
+    UniqueVector<SourceDir> all_framework_dirs;
+    UniqueVector<std::string> all_frameworks;
+    UniqueVector<std::string> all_weak_frameworks;
+
+    for (ConfigValuesIterator iter(info->target); !iter.done(); iter.Next()) {
+      const ConfigValues& cur = iter.cur();
+      all_framework_dirs.Append(cur.framework_dirs());
+      all_frameworks.Append(cur.frameworks());
+      all_weak_frameworks.Append(cur.weak_frameworks());
+    }
+    for (const Target* dep : info->deps.linked_deps()) {
+      if (!dep->IsFinal() || dep->output_type() == Target::STATIC_LIBRARY) {
+        const TargetInfo* dep_info = GetRecursiveTargetLibInfo(dep);
+        all_framework_dirs.Append(dep_info->framework_dirs);
+        all_frameworks.Append(dep_info->frameworks);
+        all_weak_frameworks.Append(dep_info->weak_frameworks);
+      }
+    }
+
+    info->framework_dirs = ImmutableVector<SourceDir>(all_framework_dirs);
+    info->frameworks = ImmutableVector<std::string>(all_frameworks);
+    info->weak_frameworks = ImmutableVector<std::string>(all_weak_frameworks);
+    info->has_framework_info = true;
+  }
+
+  const TargetInfo* GetRecursiveTargetHardDeps(const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    if (!info->has_hard_deps)
+      ComputeHardDeps(info);
+    return info;
+  }
+
+  void ComputeHardDeps(TargetInfo* info) const {
+    TargetSet all_hard_deps;
+    for (const Target* dep : info->deps.linked_deps()) {
+      // Direct hard dependencies
+      if (info->target->hard_dep() || dep->hard_dep()) {
+        all_hard_deps.insert(dep);
+        continue;
+      }
+      // If |dep| is binary target and |dep| has no public header,
+      // |this| target does not need to have |dep|'s hard_deps as its
+      // hard_deps to start compiles earlier. Unless the target compiles a
+      // Swift module (since they also generate a header that can be used
+      // by the current target).
+      if (dep->IsBinary() && !dep->all_headers_public() &&
+          dep->public_headers().empty() && !dep->builds_swift_module()) {
+        continue;
+      }
+
+      // Recursive hard dependencies of all dependencies.
+      const TargetInfo* dep_info = GetRecursiveTargetHardDeps(dep);
+      all_hard_deps.insert(dep_info->hard_deps.begin(),
+                           dep_info->hard_deps.end());
+    }
+    info->hard_deps = ImmutableVector<const Target*>(all_hard_deps);
+    info->has_hard_deps = true;
+  }
+
+  const TargetInfo* GetRecursiveTargetInheritedLibs(
+      const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    if (!info->has_inherited_libs)
+      ComputeInheritedLibs(info);
+    return info;
+  }
+
+  void ComputeInheritedLibs(TargetInfo* info) const {
+    TargetPublicPairListBuilder inherited_libraries;
+
+    ComputeInheritedLibsFor(info->deps.public_deps(), true,
+                            &inherited_libraries);
+    ComputeInheritedLibsFor(info->deps.private_deps(), false,
+                            &inherited_libraries);
+
+    info->has_inherited_libs = true;
+    info->inherited_libs = inherited_libraries.Build();
+  }
+
+  void ComputeInheritedLibsFor(
+      base::span<const Target*> deps,
+      bool is_public,
+      TargetPublicPairListBuilder* inherited_libraries) const {
+    for (const Target* dep : deps) {
+      // Direct dependent libraries.
+      if (dep->output_type() == Target::STATIC_LIBRARY ||
+          dep->output_type() == Target::SHARED_LIBRARY ||
+          dep->output_type() == Target::RUST_LIBRARY ||
+          dep->output_type() == Target::SOURCE_SET ||
+          (dep->output_type() == Target::CREATE_BUNDLE &&
+           dep->bundle_data().is_framework())) {
+        inherited_libraries->Append(dep, is_public);
+      }
+      if (dep->output_type() == Target::SHARED_LIBRARY) {
+        // Shared library dependendencies are inherited across public shared
+        // library boundaries.
+        //
+        // In this case:
+        //   EXE -> INTERMEDIATE_SHLIB --[public]--> FINAL_SHLIB
+        // The EXE will also link to to FINAL_SHLIB. The public dependency means
+        // that the EXE can use the headers in FINAL_SHLIB so the FINAL_SHLIB
+        // will need to appear on EXE's link line.
+        //
+        // However, if the dependency is private:
+        //   EXE -> INTERMEDIATE_SHLIB --[private]--> FINAL_SHLIB
+        // the dependency will not be propagated because INTERMEDIATE_SHLIB is
+        // not granting permission to call functions from FINAL_SHLIB. If EXE
+        // wants to use functions (and link to) FINAL_SHLIB, it will need to do
+        // so explicitly.
+        //
+        // Static libraries and source sets aren't inherited across shared
+        // library boundaries because they will be linked into the shared
+        // library. Rust dylib deps are handled above and transitive deps are
+        // resolved by the compiler.
+        const TargetInfo* dep_info = GetRecursiveTargetInheritedLibs(dep);
+        for (const auto& pair : dep_info->inherited_libs) {
+          if (pair.target()->output_type() == Target::SHARED_LIBRARY &&
+              pair.is_public()) {
+            inherited_libraries->Append(pair.target(), is_public);
+          }
+        }
+      } else if (!dep->IsFinal()) {
+        // The current target isn't linked, so propagate linked deps and
+        // libraries up the dependency tree.
+        const TargetInfo* dep_info = GetRecursiveTargetInheritedLibs(dep);
+        for (const auto& pair : dep_info->inherited_libs) {
+          // Proc macros are not linked into targets that depend on them, so do
+          // not get inherited; they are consumed by the Rust compiler and only
+          // need to be specified in --extern.
+          if (pair.target()->output_type() != Target::RUST_PROC_MACRO)
+            inherited_libraries->Append(pair.target(),
+                                        is_public && pair.is_public());
+        }
+      } else if (dep->complete_static_lib()) {
+        // Inherit only final targets through _complete_ static libraries.
+        //
+        // Inherited final libraries aren't linked into complete static
+        // libraries. They are forwarded here so that targets that depend on
+        // complete static libraries can link them in. Conversely, since
+        // complete static libraries link in non-final targets they shouldn't be
+        // inherited.
+        const TargetInfo* dep_info = GetRecursiveTargetInheritedLibs(dep);
+        for (const auto& pair : dep_info->inherited_libs) {
+          if (pair.target()->IsFinal())
+            inherited_libraries->Append(pair.target(),
+                                        is_public && pair.is_public());
+        }
+      }
+    }
+  }
+
+  const TargetInfo* GetRecursiveTargetRustLibs(const Target* target) const {
+    TargetInfo* info = GetInfo(target);
+    if (!info->has_rust_libs)
+      ComputeRustLibs(info);
+    return info;
+  }
+
+  struct RustLibsBuilder {
+    TargetPublicPairListBuilder inherited;
+    TargetPublicPairListBuilder inheritable;
+  };
+
+  void ComputeRustLibs(TargetInfo* info) const {
+    RustLibsBuilder rust_libs;
+
+    ComputeRustLibsFor(info->deps.public_deps(), true, &rust_libs);
+    ComputeRustLibsFor(info->deps.private_deps(), false, &rust_libs);
+
+    info->has_rust_libs = true;
+    info->rust_inherited_libs = rust_libs.inherited.Build();
+    info->rust_inheritable_libs = rust_libs.inheritable.Build();
+  }
+
+  void ComputeRustLibsFor(base::span<const Target*> deps,
+                          bool is_public,
+                          RustLibsBuilder* rust_libs) const {
+    for (const Target* dep : deps) {
+      // Collect Rust libraries that are accessible from the current target, or
+      // transitively part of the current target.
+      if (dep->output_type() == Target::STATIC_LIBRARY ||
+          dep->output_type() == Target::SHARED_LIBRARY ||
+          dep->output_type() == Target::SOURCE_SET ||
+          dep->output_type() == Target::RUST_LIBRARY ||
+          dep->output_type() == Target::GROUP) {
+        // Here we have: `this` --[depends-on]--> `dep`
+        //
+        // The `this` target has direct access to `dep` since its a direct
+        // dependency, regardless of the edge being a public_dep or not, so we
+        // pass true for public-ness. Whereas, anything depending on `this` can
+        // only gain direct access to `dep` if the edge between `this` and `dep`
+        // is public, so we pass `is_public`.
+        //
+        // TODO(danakj): We should only need to track Rust rlibs or dylibs here,
+        // as it's used for passing to rustc with --extern. We currently track
+        // everything then drop non-Rust libs in
+        // ninja_rust_binary_target_writer.cc.
+        rust_libs->inherited.Append(dep, true);
+        rust_libs->inheritable.Append(dep, is_public);
+
+        const TargetInfo* dep_info = GetRecursiveTargetRustLibs(dep);
+        rust_libs->inherited.AppendInherited(dep_info->rust_inheritable_libs,
+                                             true);
+        rust_libs->inheritable.AppendInherited(dep_info->rust_inheritable_libs,
+                                               is_public);
+      } else if (dep->output_type() == Target::RUST_PROC_MACRO) {
+        // Proc-macros are inherited as a transitive dependency, but the things
+        // they depend on can't be used elsewhere, as the proc macro is not
+        // linked into the target (as it's only used during compilation).
+        rust_libs->inherited.Append(dep, true);
+        rust_libs->inheritable.Append(dep, is_public);
+      }
+    }
+  }
+
+  TargetInfo* GetInfo(const Target* target) const {
+    auto ret = targets_.PushBackWithIndex(target);
+    if (!ret.first)
+      return infos_[ret.second].get();
+
+    infos_.push_back(std::make_unique<TargetInfo>(target));
+    return infos_.back().get();
+  }
+
+  // A { target -> TargetInfo } map that will create entries
+  // on demand. Implemented with a UniqueVector<> and a parallel
+  // vector of unique TargetInfo instances for best performance.
+  mutable UniqueVector<const Target*> targets_;
+  mutable std::vector<std::unique_ptr<TargetInfo>> infos_;
+};
+
+ResolvedTargetData::ResolvedTargetData() = default;
+
+ResolvedTargetData::~ResolvedTargetData() = default;
+
+ResolvedTargetData::ResolvedTargetData(ResolvedTargetData&&) noexcept = default;
+ResolvedTargetData& ResolvedTargetData::operator=(ResolvedTargetData&&) =
+    default;
+
+ResolvedTargetData::Impl* ResolvedTargetData::GetImpl() const {
+  if (!impl_)
+    impl_ = std::make_unique<ResolvedTargetData::Impl>();
+  return impl_.get();
+}
+
+LibInfo ResolvedTargetData::GetLibInfo(const Target* target) const {
+  return GetImpl()->GetLibInfo(target);
+}
+
+ImmutableVectorView<SourceDir> ResolvedTargetData::all_lib_dirs(
+    const Target* target) const {
+  return GetImpl()->all_lib_dirs(target);
+}
+
+ImmutableVectorView<LibFile> ResolvedTargetData::all_libs(
+    const Target* target) const {
+  return GetImpl()->all_libs(target);
+}
+
+FrameworkInfo ResolvedTargetData::GetFrameworkInfo(const Target* target) const {
+  return GetImpl()->GetFrameworkInfo(target);
+}
+
+ImmutableVectorView<SourceDir> ResolvedTargetData::all_framework_dirs(
+    const Target* target) const {
+  return GetImpl()->all_framework_dirs(target);
+}
+
+ImmutableVectorView<std::string> ResolvedTargetData::all_frameworks(
+    const Target* target) const {
+  return GetImpl()->all_frameworks(target);
+}
+
+ImmutableVectorView<std::string> ResolvedTargetData::all_weak_frameworks(
+    const Target* target) const {
+  return GetImpl()->all_weak_frameworks(target);
+}
+
+TargetSet ResolvedTargetData::recursive_hard_deps(const Target* target) const {
+  return GetImpl()->recursive_hard_deps(target);
+}
+
+TargetPublicPairList ResolvedTargetData::inherited_libraries(
+    const Target* target) const {
+  return GetImpl()->inherited_libraries(target);
+}
+
+TargetPublicPairList ResolvedTargetData::rust_transitive_inherited_libs(
+    const Target* target) const {
+  return GetImpl()->rust_transitive_inherited_libs(target);
+}
diff --git a/src/gn/resolved_target_data.h b/src/gn/resolved_target_data.h
new file mode 100644
index 0000000..67a616f
--- /dev/null
+++ b/src/gn/resolved_target_data.h
@@ -0,0 +1,79 @@
+// Copyright 2022 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_RESOLVED_TARGET_DATA_H_
+#define TOOLS_GN_RESOLVED_TARGET_DATA_H_
+
+#include <memory>
+
+#include "gn/immutable_vector.h"
+#include "gn/lib_file.h"
+#include "gn/source_dir.h"
+#include "gn/tagged_pointer.h"
+#include "gn/target.h"
+#include "gn/target_public_pair.h"
+
+class Target;
+
+// A list of (target_ptr, is_public_flag) pairs as returned by methods
+// of ResolvedTargetData.
+using TargetPublicPairList = ImmutableVectorView<TargetPublicPair>;
+
+// A class used to compute data.
+class ResolvedTargetData {
+ public:
+  ResolvedTargetData();
+  ~ResolvedTargetData();
+
+  // Move operations
+  ResolvedTargetData(ResolvedTargetData&&) noexcept;
+  ResolvedTargetData& operator=(ResolvedTargetData&&);
+
+  // Retrieve information about link-time libraries needed by this target.
+  struct LibInfo {
+    ImmutableVectorView<SourceDir> all_lib_dirs;
+    ImmutableVectorView<LibFile> all_libs;
+  };
+  LibInfo GetLibInfo(const Target*) const;
+
+  ImmutableVectorView<SourceDir> all_lib_dirs(const Target* target) const;
+
+  ImmutableVectorView<LibFile> all_libs(const Target* target) const;
+
+  // Retrieve information about link-time OS X frameworks needed by this target.
+  struct FrameworkInfo {
+    ImmutableVector<SourceDir> all_framework_dirs;
+    ImmutableVector<std::string> all_frameworks;
+    ImmutableVector<std::string> all_weak_frameworks;
+  };
+  FrameworkInfo GetFrameworkInfo(const Target* target) const;
+
+  ImmutableVectorView<SourceDir> all_framework_dirs(const Target* target) const;
+
+  ImmutableVectorView<std::string> all_frameworks(const Target* target) const;
+
+  ImmutableVectorView<std::string> all_weak_frameworks(
+      const Target* target) const;
+
+  // Retrieve a set of hard dependencies for this target.
+  TargetSet recursive_hard_deps(const Target* target) const;
+
+  // Retrieve an ordered list of (target, is_public) pairs for all link-time
+  // libraries inherited by this target.
+  TargetPublicPairList inherited_libraries(const Target* target) const;
+
+  // Retrieves an ordered list of (target, is_public) paris for all link-time
+  // libraries for Rust-specific binary targets.
+  TargetPublicPairList rust_transitive_inherited_libs(
+      const Target* target) const;
+
+ private:
+  class Impl;
+
+  Impl* GetImpl() const;
+
+  mutable std::unique_ptr<Impl> impl_;
+};
+
+#endif  // TOOLS_GN_RESOLVED_TARGET_DATA_H_
diff --git a/src/gn/resolved_target_data_unittest.cc b/src/gn/resolved_target_data_unittest.cc
new file mode 100644
index 0000000..1dd4d5a
--- /dev/null
+++ b/src/gn/resolved_target_data_unittest.cc
@@ -0,0 +1,308 @@
+// Copyright 2022 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 "gn/resolved_target_data.h"
+
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+// 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.
+  auto z_info = resolved.GetLibInfo(&z);
+  ASSERT_EQ(1u, z_info.all_libs.size());
+  EXPECT_EQ(lib, z_info.all_libs[0]);
+  ASSERT_EQ(1u, z_info.all_lib_dirs.size());
+  EXPECT_EQ(libdir, z_info.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 libinfo = resolved.GetLibInfo(&shared);
+  ASSERT_EQ(2u, libinfo.all_libs.size());
+  EXPECT_EQ(second_lib, libinfo.all_libs[0]);
+  EXPECT_EQ(lib, libinfo.all_libs[1]);
+  ASSERT_EQ(2u, libinfo.all_lib_dirs.size());
+  EXPECT_EQ(second_libdir, libinfo.all_lib_dirs[0]);
+  EXPECT_EQ(libdir, libinfo.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));
+  auto exec_libinfo = resolved.GetLibInfo(&exec);
+  EXPECT_EQ(0u, exec_libinfo.all_libs.size());
+  EXPECT_EQ(0u, exec_libinfo.all_lib_dirs.size());
+}
+
+// Tests that framework[_dir]s are inherited across deps boundaries for static
+// libraries but not executables.
+TEST(ResolvedTargetDataTest, FrameworkInheritance) {
+  TestWithScope setup;
+  Err err;
+
+  const std::string framework("Foo.framework");
+  const SourceDir frameworkdir("//out/foo/");
+
+  // Leaf target with ldflags set.
+  TestTarget z(setup, "//foo:z", Target::STATIC_LIBRARY);
+  z.config_values().frameworks().push_back(framework);
+  z.config_values().framework_dirs().push_back(frameworkdir);
+  ASSERT_TRUE(z.OnResolved(&err));
+
+  ResolvedTargetData resolved;
+
+  // All framework[_dir]s should be set when target is resolved.
+  auto info = resolved.GetFrameworkInfo(&z);
+  ASSERT_EQ(1u, info.all_frameworks.size());
+  EXPECT_EQ(framework, info.all_frameworks[0]);
+  ASSERT_EQ(1u, info.all_framework_dirs.size());
+  EXPECT_EQ(frameworkdir, info.all_framework_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 std::string second_framework("Bar.framework");
+  const SourceDir second_frameworkdir("//out/bar/");
+  TestTarget shared(setup, "//foo:shared", Target::SHARED_LIBRARY);
+  shared.config_values().frameworks().push_back(second_framework);
+  shared.config_values().framework_dirs().push_back(second_frameworkdir);
+  shared.private_deps().push_back(LabelTargetPair(&z));
+  ASSERT_TRUE(shared.OnResolved(&err));
+
+  auto shared_info = resolved.GetFrameworkInfo(&shared);
+  ASSERT_EQ(2u, shared_info.all_frameworks.size());
+  EXPECT_EQ(second_framework, shared_info.all_frameworks[0]);
+  EXPECT_EQ(framework, shared_info.all_frameworks[1]);
+  ASSERT_EQ(2u, shared_info.all_framework_dirs.size());
+  EXPECT_EQ(second_frameworkdir, shared_info.all_framework_dirs[0]);
+  EXPECT_EQ(frameworkdir, shared_info.all_framework_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));
+  auto exec_info = resolved.GetFrameworkInfo(&exec);
+  EXPECT_EQ(0u, exec_info.all_frameworks.size());
+  EXPECT_EQ(0u, exec_info.all_framework_dirs.size());
+}
+
+TEST(ResolvedTargetDataTest, InheritLibs) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (shared lib) -> C (static lib) -> D (source set)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::SHARED_LIBRARY);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  TestTarget d(setup, "//foo:d", Target::SOURCE_SET);
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+  c.private_deps().push_back(LabelTargetPair(&d));
+
+  ASSERT_TRUE(d.OnResolved(&err));
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  ResolvedTargetData resolved;
+
+  // C should have D in its inherited libs.
+  auto c_inherited_libs = resolved.inherited_libraries(&c);
+  ASSERT_EQ(1u, c_inherited_libs.size());
+  EXPECT_EQ(&d, c_inherited_libs[0].target());
+
+  // B should have C and D in its inherited libs.
+  auto b_inherited = resolved.inherited_libraries(&b);
+  ASSERT_EQ(2u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0].target());
+  EXPECT_EQ(&d, b_inherited[1].target());
+
+  // A should have B in its inherited libs, but not any others (the shared
+  // library will include the static library and source set).
+  auto a_inherited = resolved.inherited_libraries(&a);
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0].target());
+}
+
+TEST(ResolvedTargetData, NoActionDepPropgation) {
+  TestWithScope setup;
+  Err err;
+  ResolvedTargetData resolved;
+  // Create a dependency chain:
+  //   A (exe) -> B (action) -> C (source_set)
+  {
+    TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+    TestTarget b(setup, "//foo:b", Target::ACTION);
+    TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+
+    a.private_deps().push_back(LabelTargetPair(&b));
+    b.private_deps().push_back(LabelTargetPair(&c));
+
+    ASSERT_TRUE(c.OnResolved(&err));
+    ASSERT_TRUE(b.OnResolved(&err));
+    ASSERT_TRUE(a.OnResolved(&err));
+
+    // The executable should not have inherited the source set across the
+    // action.
+    ASSERT_TRUE(resolved.inherited_libraries(&a).empty());
+  }
+}
+
+TEST(ResolvedTargetDataTest, InheritCompleteStaticLib) {
+  TestWithScope setup;
+  Err err;
+
+  ResolvedTargetData resolved;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (source set)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+
+  const LibFile lib("foo");
+  const SourceDir lib_dir("/foo_dir/");
+  TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+  c.config_values().libs().push_back(lib);
+  c.config_values().lib_dirs().push_back(lib_dir);
+
+  a.public_deps().push_back(LabelTargetPair(&b));
+  b.public_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  // B should have C in its inherited libs.
+  auto b_inherited = resolved.inherited_libraries(&b);
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0].target());
+
+  // A should have B in its inherited libs, but not any others (the complete
+  // static library will include the source set).
+  auto a_inherited = resolved.inherited_libraries(&a);
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0].target());
+
+  // A should inherit the libs and lib_dirs from the C.
+  auto a_info = resolved.GetLibInfo(&a);
+  ASSERT_EQ(1u, a_info.all_libs.size());
+  EXPECT_EQ(lib, a_info.all_libs[0]);
+  ASSERT_EQ(1u, a_info.all_lib_dirs.size());
+  EXPECT_EQ(lib_dir, a_info.all_lib_dirs[0]);
+}
+
+TEST(ResolvedTargetDataTest, InheritCompleteStaticLibStaticLibDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (static lib)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  a.public_deps().push_back(LabelTargetPair(&b));
+  b.public_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  ResolvedTargetData resolved;
+
+  // B should have C in its inherited libs.
+  auto b_inherited = resolved.inherited_libraries(&b);
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0].target());
+
+  // A should have B in its inherited libs, but not any others (the complete
+  // static library will include the static library).
+  auto a_inherited = resolved.inherited_libraries(&a);
+  ASSERT_EQ(1u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0].target());
+}
+
+TEST(ResolvedTargetDataTest,
+     InheritCompleteStaticLibInheritedCompleteStaticLibDeps) {
+  TestWithScope setup;
+  Err err;
+
+  // Create a dependency chain:
+  //   A (executable) -> B (complete static lib) -> C (complete static lib)
+  TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+  TestTarget b(setup, "//foo:b", Target::STATIC_LIBRARY);
+  b.set_complete_static_lib(true);
+  TestTarget c(setup, "//foo:c", Target::STATIC_LIBRARY);
+  c.set_complete_static_lib(true);
+
+  a.private_deps().push_back(LabelTargetPair(&b));
+  b.private_deps().push_back(LabelTargetPair(&c));
+
+  ASSERT_TRUE(c.OnResolved(&err));
+  ASSERT_TRUE(b.OnResolved(&err));
+  ASSERT_TRUE(a.OnResolved(&err));
+
+  ResolvedTargetData resolved;
+
+  // B should have C in its inherited libs.
+  auto b_inherited = resolved.inherited_libraries(&b);
+  ASSERT_EQ(1u, b_inherited.size());
+  EXPECT_EQ(&c, b_inherited[0].target());
+
+  // A should have B and C in its inherited libs.
+  auto a_inherited = resolved.inherited_libraries(&a);
+  ASSERT_EQ(2u, a_inherited.size());
+  EXPECT_EQ(&b, a_inherited[0].target());
+  EXPECT_EQ(&c, a_inherited[1].target());
+}
+
+TEST(ResolvedTargetDataTest, NoActionDepPropgation) {
+  TestWithScope setup;
+  Err err;
+  ResolvedTargetData resolved;
+
+  // Create a dependency chain:
+  //   A (exe) -> B (action) -> C (source_set)
+  {
+    TestTarget a(setup, "//foo:a", Target::EXECUTABLE);
+    TestTarget b(setup, "//foo:b", Target::ACTION);
+    TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+
+    a.private_deps().push_back(LabelTargetPair(&b));
+    b.private_deps().push_back(LabelTargetPair(&c));
+
+    ASSERT_TRUE(c.OnResolved(&err));
+    ASSERT_TRUE(b.OnResolved(&err));
+    ASSERT_TRUE(a.OnResolved(&err));
+
+    // The executable should not have inherited the source set across the
+    // action.
+    auto libs = resolved.inherited_libraries(&a);
+    ASSERT_TRUE(libs.empty());
+  }
+}
diff --git a/src/gn/resolved_target_deps.h b/src/gn/resolved_target_deps.h
new file mode 100644
index 0000000..5d3903a
--- /dev/null
+++ b/src/gn/resolved_target_deps.h
@@ -0,0 +1,88 @@
+// Copyright 2022 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_RESOLVED_TARGET_DEPS_H_
+#define TOOLS_GN_RESOLVED_TARGET_DEPS_H_
+
+#include "base/containers/span.h"
+#include "gn/label_ptr.h"
+
+#include <memory>
+
+// A class used to record the dependencies of a given Target in
+// a way that is much more efficient to iterate over than having three
+// separate LabelTargetVector instances. Technically equivalent to
+// DepsIterator, but profiling shows that this class is much faster
+// to use during graph-traversal heavy operations.
+//
+// Usage is:
+//   1) Create instance, passing const references to the LabelTargetVector
+//      instances for the private, public and data deps for the target.
+//
+//   2) Use private_deps(), public_deps(), data_deps(), linked_deps()
+//      and all_deps() to retrieve spans that cover various subsets of
+//      interests. These can be used directly in for-range loops as in:
+//
+//       for (const Target* target : resolved.linked_deps()) {
+//         ..
+//       }
+//
+class ResolvedTargetDeps {
+ public:
+  ResolvedTargetDeps() = default;
+
+  ResolvedTargetDeps(const LabelTargetVector& public_deps,
+                     const LabelTargetVector& private_deps,
+                     const LabelTargetVector& data_deps)
+      : public_count_(static_cast<uint32_t>(public_deps.size())),
+        private_count_(static_cast<uint32_t>(private_deps.size())),
+        data_count_(static_cast<uint32_t>(data_deps.size())),
+        deps_(Allocate(public_deps, private_deps, data_deps)) {}
+
+  size_t size() const { return private_count_ + public_count_ + data_count_; }
+
+  base::span<const Target*> public_deps() const {
+    return {deps_.get(), public_count_};
+  }
+
+  base::span<const Target*> private_deps() const {
+    return {deps_.get() + public_count_, private_count_};
+  }
+
+  base::span<const Target*> data_deps() const {
+    return {deps_.get() + private_count_ + public_count_, data_count_};
+  }
+
+  base::span<const Target*> linked_deps() const {
+    return {deps_.get(), private_count_ + public_count_};
+  }
+
+  base::span<const Target*> all_deps() const {
+    return {deps_.get(), private_count_ + public_count_ + data_count_};
+  }
+
+  static std::unique_ptr<const Target* []> Allocate(
+      const LabelTargetVector& public_deps,
+      const LabelTargetVector& private_deps,
+      const LabelTargetVector& data_deps) {
+    size_t total_size =
+        private_deps.size() + public_deps.size() + data_deps.size();
+    auto result = std::make_unique<const Target*[]>(total_size);
+    const Target** ptr = result.get();
+    for (const auto& pair : public_deps)
+      *ptr++ = pair.ptr;
+    for (const auto& pair : private_deps)
+      *ptr++ = pair.ptr;
+    for (const auto& pair : data_deps)
+      *ptr++ = pair.ptr;
+    return result;
+  }
+
+  private : uint32_t public_count_ = 0;
+  uint32_t private_count_ = 0;
+  uint32_t data_count_ = 0;
+  std::unique_ptr<const Target*[]> deps_;
+};
+
+#endif  // TOOLS_GN_RESOLVED_TARGET_DEPS_H_
diff --git a/src/gn/resolved_target_deps_unittest.cc b/src/gn/resolved_target_deps_unittest.cc
new file mode 100644
index 0000000..74d623d
--- /dev/null
+++ b/src/gn/resolved_target_deps_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2022 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 "gn/resolved_target_deps.h"
+#include "gn/test_with_scope.h"
+#include "util/test/test.h"
+
+TEST(ResolvedTargetDeps, DefaultConstruction) {
+  ResolvedTargetDeps deps;
+  EXPECT_EQ(0u, deps.size());
+  EXPECT_TRUE(deps.public_deps().empty());
+  EXPECT_TRUE(deps.private_deps().empty());
+  EXPECT_TRUE(deps.data_deps().empty());
+  EXPECT_TRUE(deps.linked_deps().empty());
+  EXPECT_TRUE(deps.all_deps().empty());
+}
+
+TEST(ResolvedTargetDeps, Construction) {
+  TestWithScope setup;
+  TestTarget a(setup, "//foo:a", Target::STATIC_LIBRARY);
+  TestTarget b(setup, "//foo:b", Target::SOURCE_SET);
+  TestTarget c(setup, "//foo:c", Target::SOURCE_SET);
+  TestTarget d(setup, "//foo:d", Target::SOURCE_SET);
+  TestTarget e(setup, "//foo:e", Target::EXECUTABLE);
+
+  LabelTargetVector public_vec;
+  LabelTargetVector private_vec;
+  LabelTargetVector data_vec;
+
+  public_vec.emplace_back(&a);
+  public_vec.emplace_back(&b);
+  private_vec.emplace_back(&c);
+  private_vec.emplace_back(&d);
+  data_vec.emplace_back(&e);
+
+  ResolvedTargetDeps deps(public_vec, private_vec, data_vec);
+  EXPECT_EQ(5u, deps.size());
+
+  EXPECT_EQ(2u, deps.public_deps().size());
+  EXPECT_EQ(&a, deps.public_deps()[0]);
+  EXPECT_EQ(&b, deps.public_deps()[1]);
+
+  EXPECT_EQ(2u, deps.private_deps().size());
+  EXPECT_EQ(&c, deps.private_deps()[0]);
+  EXPECT_EQ(&d, deps.private_deps()[1]);
+
+  EXPECT_EQ(1u, deps.data_deps().size());
+  EXPECT_EQ(&e, deps.data_deps()[0]);
+
+  EXPECT_EQ(4u, deps.linked_deps().size());
+  EXPECT_EQ(&a, deps.linked_deps()[0]);
+  EXPECT_EQ(&b, deps.linked_deps()[1]);
+  EXPECT_EQ(&c, deps.linked_deps()[2]);
+  EXPECT_EQ(&d, deps.linked_deps()[3]);
+
+  EXPECT_EQ(5u, deps.all_deps().size());
+  EXPECT_EQ(&a, deps.all_deps()[0]);
+  EXPECT_EQ(&b, deps.all_deps()[1]);
+  EXPECT_EQ(&c, deps.all_deps()[2]);
+  EXPECT_EQ(&d, deps.all_deps()[3]);
+  EXPECT_EQ(&e, deps.all_deps()[4]);
+}
diff --git a/src/gn/target_public_pair.h b/src/gn/target_public_pair.h
new file mode 100644
index 0000000..c8ff655
--- /dev/null
+++ b/src/gn/target_public_pair.h
@@ -0,0 +1,111 @@
+// Copyright 2022 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_TARGET_PUBLIC_PAIR_H_
+#define TOOLS_GN_TARGET_PUBLIC_PAIR_H_
+
+#include "gn/immutable_vector.h"
+#include "gn/tagged_pointer.h"
+#include "gn/unique_vector.h"
+
+class Target;
+
+// A Compact encoding for a (target_ptr, is_public_flag) pair.
+class TargetPublicPair {
+ public:
+  TargetPublicPair() = default;
+  TargetPublicPair(const Target* target, bool is_public)
+      : pair_(target, static_cast<unsigned>(is_public)) {}
+  TargetPublicPair(std::pair<const Target*, bool> pair)
+      : pair_(pair.first, static_cast<unsigned>(pair.second)) {}
+
+  const Target* target() const { return pair_.ptr(); }
+  void set_target(const Target* target) { pair_.set_ptr(target); }
+
+  bool is_public() const { return pair_.tag() != 0; }
+  void set_is_public(bool is_public) { pair_.set_tag(is_public ? 1 : 0); }
+
+  // Utility structs that can be used to instantiante containers
+  // that only use the target for lookups / comparisons. E.g.
+  //
+  //   std::unordered_set<TargetPublicPair,
+  //                      TargetPublicPair::TargetHash,
+  //                      TargetPublicPair::TargetEqualTo>
+  //
+  //   std::set<TargetPublicPair, TargetPublicPair::TargetLess>
+  //
+  struct TargetHash {
+    size_t operator()(TargetPublicPair p) const noexcept {
+      return std::hash<const Target*>()(p.target());
+    }
+  };
+
+  struct TargetEqualTo {
+    bool operator()(TargetPublicPair a, TargetPublicPair b) const noexcept {
+      return a.target() == b.target();
+    }
+  };
+
+  struct TargetLess {
+    bool operator()(TargetPublicPair a, TargetPublicPair b) const noexcept {
+      return a.target() < b.target();
+    }
+  };
+
+ private:
+  TaggedPointer<const Target, 1> pair_;
+};
+
+// A helper type to build a list of (target, is_public) pairs, where target
+// pointers are unique. Usage is:
+//
+//   1) Create builder instance.
+//   2) Call Append() or AppendInherited() as many times as necessary.
+//   3) Call Build() to retrieve final list as an immutable vector.
+//
+class TargetPublicPairListBuilder
+    : public UniqueVector<TargetPublicPair,
+                          TargetPublicPair::TargetHash,
+                          TargetPublicPair::TargetEqualTo> {
+ public:
+  // Add (target, is_public) to the list being constructed. If the target
+  // was not already in the list, recorded the |is_public| flag as is,
+  // otherwise, set the recorded flag to true only if |is_public| is true, or
+  // don't do anything otherwise.
+  void Append(const Target* target, bool is_public) {
+    auto ret = EmplaceBackWithIndex(target, is_public);
+    if (!ret.first && is_public) {
+      // UniqueVector<T>::operator[]() always returns a const reference
+      // because the returned values are lookup keys in its set-like data
+      // structure (thus modifying them would break its internal consistency).
+      // However, because TargetHash and TargetEqualTo are being used to
+      // instantiate this template, only the target() part of the value must
+      // remain constant, and it is possible to modify the is_public() part
+      // in-place safely.
+      auto* pair = const_cast<TargetPublicPair*>(&(*this)[ret.second]);
+      pair->set_is_public(true);
+    }
+  }
+
+  // Append all pairs from any container with begin() and end() iterators
+  // that dereference to values that convert to a TargetPublicPair value.
+  // If |is_public| is false, the input pair will be appended with the
+  // value of the public flag to false.
+  template <
+      typename C,
+      typename = std::void_t<
+          decltype(static_cast<TargetPublicPair>(*std::declval<C>().begin())),
+          decltype(static_cast<TargetPublicPair>(*std::declval<C>().end()))>>
+  void AppendInherited(const C& other, bool is_public) {
+    for (const auto& pair : other) {
+      Append(pair.target(), is_public && pair.is_public());
+    }
+  }
+
+  ImmutableVector<TargetPublicPair> Build() {
+    return ImmutableVector<TargetPublicPair>(release());
+  }
+};
+
+#endif  // TOOLS_GN_TARGET_PUBLIC_PAIR_H_
diff --git a/src/gn/target_public_pair_unittest.cc b/src/gn/target_public_pair_unittest.cc
new file mode 100644
index 0000000..8b08a0c
--- /dev/null
+++ b/src/gn/target_public_pair_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2022 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 "gn/target_public_pair.h"
+#include "util/test/test.h"
+
+TEST(TargetPublicPairTest, ConstructionAndMutation) {
+  // Fake target pointer values.
+  const auto* a_target = reinterpret_cast<const Target*>(1000);
+  const auto* b_target = reinterpret_cast<const Target*>(2000);
+
+  TargetPublicPair a_pair(a_target, true);
+  EXPECT_EQ(a_target, a_pair.target());
+  EXPECT_TRUE(a_pair.is_public());
+
+  TargetPublicPair b_pair(b_target, false);
+  EXPECT_EQ(b_target, b_pair.target());
+  EXPECT_FALSE(b_pair.is_public());
+
+  a_pair.set_target(b_target);
+  EXPECT_EQ(b_target, a_pair.target());
+  EXPECT_TRUE(a_pair.is_public());
+
+  a_pair.set_is_public(false);
+  EXPECT_EQ(b_target, a_pair.target());
+  EXPECT_FALSE(a_pair.is_public());
+
+  a_pair = TargetPublicPair(a_target, true);
+  EXPECT_EQ(a_target, a_pair.target());
+  EXPECT_TRUE(a_pair.is_public());
+
+  b_pair = std::move(a_pair);
+  EXPECT_EQ(a_target, b_pair.target());
+  EXPECT_TRUE(b_pair.is_public());
+}
+
+TEST(TargetPublicPairTest, Builder) {
+  const auto* a_target = reinterpret_cast<const Target*>(1000);
+  const auto* b_target = reinterpret_cast<const Target*>(2000);
+  TargetPublicPairListBuilder builder;
+
+  builder.Append(a_target, false);
+  builder.Append(b_target, false);
+  builder.Append(a_target, true);
+  builder.Append(b_target, false);
+
+  auto list = builder.Build();
+  EXPECT_EQ(2u, list.size());
+  EXPECT_EQ(a_target, list[0].target());
+  EXPECT_EQ(b_target, list[1].target());
+  EXPECT_TRUE(list[0].is_public());
+  EXPECT_FALSE(list[1].is_public());
+}