ninja_rust_binary_target_writer: link C++-reachable transitive rlibs

A rustc-driven link (rust_bin/cdylib/dylib) only links rlibs reachable
through the Rust crate graph (--extern + crate metadata). Rust libraries
reachable only through a C++ dependency were dropped from the link,
producing undefined cxx-bridge symbols for bridges whose Rust half lives
behind a C++ target (e.g. //base's rust_logger, serde_json_lenient).

The C++ linker path already handles this via the "rlibs" ninja variable
(NinjaCBinaryTargetWriter). Mirror it for final Rust targets: walk the
inherited library closure and link, as an archive via the existing
nonrustdeps -Clink-arg path, any RUST_LIBRARY that rustc does not link
itself. The "rustc links it" set is computed precisely as the
crate-graph closure of the --extern (directly-accessible) crates, so
rlibs reachable purely through rust deps are not passed twice.

Updates RlibInLibrary and TransitiveRustDepsThroughSourceSet goldens,
which previously asserted the (buggy) dropped-rlib behavior.

Bug: 513478482
Change-Id: I069dde441c0e1648f39562f18d59e0efde125038
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23360
Commit-Queue: Philipp Wollermann <philwo@chromium.org>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Matt Stark <msta@google.com>
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index b77157a..a8bea8f 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -5,6 +5,7 @@
 #include "gn/ninja_rust_binary_target_writer.h"
 
 #include <sstream>
+#include <unordered_set>
 
 #include "base/strings/string_util.h"
 #include "gn/deps_iterator.h"
@@ -221,6 +222,58 @@
     }
   }
 
+  // rustc only links rlibs reachable through the Rust crate graph (via
+  // --extern / -Ldependency). Rust libraries reachable only through a non-Rust
+  // (C++) dependency are otherwise dropped from a rustc-driven link, which
+  // breaks cxx bridges whose Rust half lives behind a C++ target (e.g. //base's
+  // rust_logger or serde_json_lenient). The C++ linker path handles this via
+  // the "rlibs" ninja variable (see NinjaCBinaryTargetWriter); mirror it here
+  // by linking those rlibs directly as archives through the linker. Crates
+  // already covered by the crate graph above are skipped to avoid linking them
+  // twice.
+  if (target_->IsFinal()) {
+    // Compute the set of rlibs rustc links by itself: the crates passed to
+    // --extern (those with direct access), plus everything reachable from them
+    // by following Rust crate dependencies (rustc resolves these through crate
+    // metadata). A Rust library is NOT in this set if it can only be reached
+    // through a non-Rust (C++) dependency, since the C++ boundary breaks the
+    // crate graph.
+    std::unordered_set<const Target*> linked_by_rustc;
+    std::vector<const Target*> work;
+    for (const auto& crate : transitive_crates) {
+      if (crate.has_direct_access)
+        work.push_back(crate.target);
+    }
+    while (!work.empty()) {
+      const Target* crate = work.back();
+      work.pop_back();
+      if (!linked_by_rustc.insert(crate).second)
+        continue;
+      for (const auto& pair : crate->GetDeps(Target::DEPS_LINKED)) {
+        // Groups are flattened into the crate graph, so rustc links rlibs
+        // reachable through them; treat groups as transparent in this walk so
+        // those rlibs aren't passed to the linker a second time below.
+        if (pair.ptr->output_type() == Target::GROUP ||
+            (pair.ptr->source_types_used().RustSourceUsed() &&
+             RustValues::IsRustLibrary(pair.ptr)))
+          work.push_back(pair.ptr);
+      }
+    }
+    // Link any Rust library in the transitive closure that rustc won't link
+    // itself directly as an archive, mirroring the "rlibs" handling in
+    // NinjaCBinaryTargetWriter. This is what lets cxx bridges whose Rust half
+    // sits behind a C++ target (e.g. //base's rust_logger) resolve.
+    for (const auto& inherited : resolved().GetInheritedLibraries(target_)) {
+      const Target* dep = inherited.target();
+      if (dep->output_type() == Target::RUST_LIBRARY &&
+          !linked_by_rustc.contains(dep)) {
+        CHECK(dep->has_dependency_output_file());
+        nonrustdeps.push_back(dep->dependency_output_file());
+        implicit_deps.push_back(dep->dependency_output_file());
+      }
+    }
+  }
+
   std::vector<OutputFile> tool_outputs;
   SubstitutionWriter::ApplyListToLinkerAsOutputFile(
       target_, tool_, tool_->outputs(), &tool_outputs);
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index f38b892..3f20c5a 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -1187,7 +1187,8 @@
       "-Clink-arg=-Bdynamic "
       "-Clink-arg=obj/pub_sset_in_staticlib/pub_sset_in_staticlib.lib.o "
       "-Clink-arg=obj/priv_sset_in_staticlib/priv_sset_in_staticlib.lib.o "
-      "-Clink-arg=obj/staticlib/libstaticlib.a\n"
+      "-Clink-arg=obj/staticlib/libstaticlib.a "
+      "-Clink-arg=obj/priv_in_staticlib/libpriv_in_staticlib.rlib\n"
       "  ldflags =\n"
       "  sources = ../../exe/main.rs\n";
 
@@ -1997,7 +1998,8 @@
         "behind_sourceset_public=obj/public/libbehind_sourceset_public.rlib\n"
         "  rustdeps = -Ldependency=obj/public -Ldependency=obj/private "
         "-Clink-arg=-Bdynamic "
-        "-Clink-arg=obj/sset/bar.input1.o\n"
+        "-Clink-arg=obj/sset/bar.input1.o "
+        "-Clink-arg=obj/private/libbehind_sourceset_private.rlib\n"
         "  ldflags =\n"
         "  sources = ../../linked/exe.rs\n";
     std::string out_str = out.str();