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();