Cache reverse dependencies for dependency exposure.

This is part of a larger process to improve the performance of
suggestions, but does not have change the time complexity until gn-review.googlesource.com/c/gn/+/26242 is submitted (benchmarks in that commit)

Change-Id: Ic0bd366f7b2639f16a9e633dde527cec6a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/26281
Commit-Queue: Matt Stark <msta@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/command_suggest.cc b/src/gn/command_suggest.cc
index 2aadfdd..9a0ca03 100644
--- a/src/gn/command_suggest.cc
+++ b/src/gn/command_suggest.cc
@@ -187,38 +187,6 @@
   return SourceFile();
 }
 
-// Returns true if depending on target is supposed to give you access to
-// everything in the underlying target.
-bool Exposes(const Target& target, const Target& underlying) {
-  std::vector<const Target*> stack = {&target};
-  std::unordered_set<const Target*> visited;
-  while (!stack.empty()) {
-    const Target* current = stack.back();
-    stack.pop_back();
-    if (visited.insert(current).second) {
-      if (current == &underlying) {
-        return true;
-      }
-      // If we have no headers and no sources, then the only use of depending
-      // on this target is to gain access to its dependencies.
-      if (current->sources().empty() && current->public_headers().empty()) {
-        for (const auto& dep : current->public_deps()) {
-          stack.push_back(dep.ptr);
-        }
-        // If you declare `public_deps = ...` on a group, it shows up as a
-        // private dep. Probably because groups don't distinguish between
-        // public and private deps.
-        if (current->output_type() == Target::GROUP) {
-          for (const auto& dep : current->private_deps()) {
-            stack.push_back(dep.ptr);
-          }
-        }
-      }
-    }
-  }
-  return false;
-}
-
 // Finds the shortest dependency path from `from` to `to`.
 // Returns a vector where the first element is `from` and the last is `to`.
 // Returns the empty vector if no path was found.
@@ -307,6 +275,65 @@
   return it->second;
 }
 
+// Returns targets that directly expose a given target
+// (all targets that forward target through groups or header-less source sets).
+std::vector<const Target*> TargetResolutionCache::GetTargetsExposing(
+    const Target& target,
+    const std::vector<const Target*>& all_targets) {
+  std::call_once(forwarding_parents_initialized_, [&]() {
+    for (const Target* t : all_targets) {
+      // If we have no headers and no sources, then the only use of depending
+      // on this target is to gain access to its dependencies.
+      if (t->sources().empty() && t->public_headers().empty()) {
+        for (const auto& dep : t->public_deps()) {
+          if (dep.ptr) {
+            forwarding_parents_[dep.ptr].push_back(t);
+          }
+        }
+        // If you declare `public_deps = ...` on a group, it shows up as a
+        // private dep. Probably because groups don't distinguish between
+        // public and private deps.
+        if (t->output_type() == Target::GROUP) {
+          for (const auto& dep : t->private_deps()) {
+            if (dep.ptr) {
+              forwarding_parents_[dep.ptr].push_back(t);
+            }
+          }
+        }
+      }
+    }
+  });
+
+  if (!forwarding_parents_.contains(&target)) {
+    return {};
+  }
+
+  std::vector<const Target*> results;
+  std::vector<const Target*> stack = {&target};
+  std::unordered_set<const Target*> visited = {&target};
+
+  while (!stack.empty()) {
+    const Target* cur = stack.back();
+    stack.pop_back();
+
+    auto parent_it = forwarding_parents_.find(cur);
+    if (parent_it != forwarding_parents_.end()) {
+      for (const Target* parent : parent_it->second) {
+        if (visited.insert(parent).second) {
+          results.push_back(parent);
+          stack.push_back(parent);
+        }
+      }
+    }
+  }
+
+  std::sort(
+      results.begin(), results.end(),
+      [](const Target* a, const Target* b) { return a->label() < b->label(); });
+
+  return results;
+}
+
 // Resolves an input to a list of targets, and whether each are private.
 // The input can be:
 // * A module name for a target
@@ -866,13 +893,13 @@
   std::vector<const Target*> visible_candidates;
   std::vector<const Target*> nonpublic_candidates;
   std::vector<const Target*> all_candidates;
-  for (const Target* candidate : all_targets) {
+  for (const Target* candidate :
+       cache.GetTargetsExposing(*included, all_targets)) {
     if (candidate == included)
       continue;
     // Check that the toolchains are the same to avoid picking up both //:foo
     // and //:foo(other_toolchain).
-    if (candidate->label().ToolchainsEqual(includer->label()) &&
-        Exposes(*candidate, *included)) {
+    if (candidate->label().ToolchainsEqual(includer->label())) {
       all_candidates.push_back(candidate);
       if (candidate->visibility().CanSeeMe(includer->label())) {
         visible_candidates.push_back(candidate);
diff --git a/src/gn/commands.h b/src/gn/commands.h
index 471ab2f..6325058 100644
--- a/src/gn/commands.h
+++ b/src/gn/commands.h
@@ -143,6 +143,12 @@
       const SourceFile& file,
       const std::vector<const Target*>& all_targets);
 
+  // Returns vector of targets that expose the given target
+  // (e.g. forwarding groups or header-less source sets that depend on it).
+  std::vector<const Target*> GetTargetsExposing(
+      const Target& target,
+      const std::vector<const Target*>& all_targets);
+
  private:
   std::once_flag file_to_target_initialized_;
   std::unordered_map<SourceFile,
@@ -150,6 +156,11 @@
       file_to_targets_;
   // Never mutated. Used when a file is not in any target.
   const std::vector<std::pair<const Target*, ApiScope>> empty_targets_;
+
+  // Maps a Target to the list of forwarding targets that directly expose it.
+  std::once_flag forwarding_parents_initialized_;
+  std::unordered_map<const Target*, std::vector<const Target*>>
+      forwarding_parents_;
 };
 
 SuggestResult OutputSuggestions(const std::vector<const Target*>& all_targets,