Don't propagate rlibs through shared libraries for linking purposes.
If a C++ target depends on an rlib R, the rlibs are treated as libraries
for linking purposes, and all transitive dependencies of R are required
at linking time.
The above holds true within a single final linking target, such as an
executable or shared library. However currently the rlibs inside a
shared library get linked again into anything that depends on the
shared library. This results in linking issues, such as missing
symbols.
executable(A) -> dylib(B) -> rlib(C) -> rlib(D)
If A wants to use C or D, and they are public dependencies, it does
not need to link them into A. They are already linked into B and A
will be linked against B.
I believe this becomes a more visible problem when C or D depends on a
C++ static library which has non-public symbols, as then A ends up
incorrectly linking C without linking its non-Rust dependencies too.
Bug: 276
Change-Id: I8ee2c1d89190920cb5a9eee0e0b6da8a18d4a235
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13080
Reviewed-by: Tyler Mandry <tmandry@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index 3321191..438e185 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -775,12 +775,13 @@
std::copy(input_deps.begin(), input_deps.end(),
std::back_inserter(implicit_deps));
- // Any C++ target which depends on a Rust .rlib has to depend on its
- // entire tree of transitive rlibs.
+ // Any C++ target which depends on a Rust .rlib has to depend on its entire
+ // tree of transitive rlibs found inside the linking target (which excludes
+ // rlibs only depended on inside a shared library dependency).
std::vector<OutputFile> transitive_rustlibs;
if (target_->IsFinal()) {
for (const auto* dep :
- target_->rust_transitive_inherited_libs().GetOrdered()) {
+ target_->rust_linkable_inherited_libs().GetOrdered()) {
if (dep->output_type() == Target::RUST_LIBRARY) {
transitive_rustlibs.push_back(dep->dependency_output_file());
implicit_deps.push_back(dep->dependency_output_file());
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 61bc517..e1bdbcc 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -1500,6 +1500,110 @@
}
}
+TEST_F(NinjaCBinaryTargetWriterTest, RustDepsOverDynamicLinking) {
+ Err err;
+ TestWithScope setup;
+
+ Target rlib3(setup.settings(), Label(SourceDir("//baz/"), "baz"));
+ rlib3.set_output_type(Target::RUST_LIBRARY);
+ rlib3.visibility().SetPublic();
+ SourceFile lib3("//baz/lib.rs");
+ rlib3.sources().push_back(lib3);
+ rlib3.source_types_used().Set(SourceFile::SOURCE_RS);
+ rlib3.rust_values().set_crate_root(lib3);
+ rlib3.rust_values().crate_name() = "baz";
+ rlib3.public_deps().push_back(LabelTargetPair(&rlib3));
+ rlib3.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rlib3.OnResolved(&err));
+
+ Target rlib2(setup.settings(), Label(SourceDir("//bar/"), "bar"));
+ rlib2.set_output_type(Target::RUST_LIBRARY);
+ rlib2.visibility().SetPublic();
+ SourceFile lib2("//bar/lib.rs");
+ rlib2.sources().push_back(lib2);
+ rlib2.source_types_used().Set(SourceFile::SOURCE_RS);
+ rlib2.rust_values().set_crate_root(lib2);
+ rlib2.rust_values().crate_name() = "bar";
+ rlib2.public_deps().push_back(LabelTargetPair(&rlib2));
+ rlib2.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rlib2.OnResolved(&err));
+
+ Target rlib(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+ rlib.set_output_type(Target::RUST_LIBRARY);
+ rlib.visibility().SetPublic();
+ SourceFile lib("//foo/lib.rs");
+ rlib.sources().push_back(lib);
+ rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+ rlib.rust_values().set_crate_root(lib);
+ rlib.rust_values().crate_name() = "foo";
+ rlib.public_deps().push_back(LabelTargetPair(&rlib2));
+ rlib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(rlib.OnResolved(&err));
+
+ Target cdylib(setup.settings(), Label(SourceDir("//sh/"), "mylib"));
+ cdylib.set_output_type(Target::SHARED_LIBRARY);
+ cdylib.visibility().SetPublic();
+ SourceFile barlib("//sh/lib.rs");
+ cdylib.sources().push_back(barlib);
+ cdylib.source_types_used().Set(SourceFile::SOURCE_RS);
+ cdylib.rust_values().set_crate_type(RustValues::CRATE_CDYLIB);
+ cdylib.rust_values().set_crate_root(barlib);
+ cdylib.rust_values().crate_name() = "mylib";
+ cdylib.private_deps().push_back(LabelTargetPair(&rlib));
+ cdylib.public_deps().push_back(LabelTargetPair(&rlib3));
+ cdylib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(cdylib.OnResolved(&err));
+
+ Target nearrlib(setup.settings(), Label(SourceDir("//near/"), "near"));
+ nearrlib.set_output_type(Target::RUST_LIBRARY);
+ nearrlib.visibility().SetPublic();
+ SourceFile nearlib("//near/lib.rs");
+ nearrlib.sources().push_back(nearlib);
+ nearrlib.source_types_used().Set(SourceFile::SOURCE_RS);
+ nearrlib.rust_values().set_crate_root(nearlib);
+ nearrlib.rust_values().crate_name() = "near";
+ nearrlib.public_deps().push_back(LabelTargetPair(&cdylib));
+ nearrlib.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(nearrlib.OnResolved(&err));
+
+ Target target(setup.settings(), Label(SourceDir("//exe/"), "binary"));
+ target.set_output_type(Target::EXECUTABLE);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//exe/main.cc"));
+ target.source_types_used().Set(SourceFile::SOURCE_CPP);
+ target.private_deps().push_back(LabelTargetPair(&nearrlib));
+ target.SetToolchain(setup.toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+
+ std::ostringstream out;
+ NinjaCBinaryTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected[] =
+ "defines =\n"
+ "include_dirs =\n"
+ "cflags =\n"
+ "cflags_cc =\n"
+ "root_out_dir = .\n"
+ "target_out_dir = obj/exe\n"
+ "target_output_name = binary\n"
+ "\n"
+ "build obj/exe/binary.main.o: cxx ../../exe/main.cc\n"
+ "\n"
+ "build ./binary: link obj/exe/binary.main.o obj/sh/libmylib.so | "
+ "obj/near/libnear.rlib\n"
+ " ldflags =\n"
+ " libs =\n"
+ " frameworks =\n"
+ " swiftmodules =\n"
+ " output_extension = \n"
+ " output_dir = \n"
+ " rlibs = obj/near/libnear.rlib\n";
+
+ std::string out_str = out.str();
+ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+}
+
TEST_F(NinjaCBinaryTargetWriterTest, ModuleMapInStaticLibrary) {
TestWithScope setup;
Err err;
diff --git a/src/gn/target.cc b/src/gn/target.cc
index c491897..53c6510 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -769,8 +769,7 @@
if (dep->output_type() == STATIC_LIBRARY ||
dep->output_type() == SHARED_LIBRARY ||
- dep->output_type() == RUST_LIBRARY ||
- dep->output_type() == GROUP) {
+ dep->output_type() == RUST_LIBRARY || dep->output_type() == GROUP) {
// Here we have: `this` --[depends-on]--> `dep`
//
// The `this` target has direct access to `dep` since its a direct
@@ -792,6 +791,20 @@
rust_transitive_inherited_libs_.Append(dep, true);
rust_transitive_inheritable_libs_.Append(dep, is_public);
}
+ if (!dep->IsFinal()) {
+ // A "final" dependency is not an rlib, so while a final dependency would be
+ // part of another linking target, we don't inherit it here. Example:
+ //
+ // [bin E] -> [rlib A] -> [so D] -> [rlib B]
+ //
+ // The [so D] is linked into [bin E] along with [rlib A]. But we didn't add
+ // the edge to [so D] as an inherited rlib.
+ rust_linkable_inherited_libs_.Append(dep, true);
+ // Gathers the set of rlibs that are part of the current target's final
+ // linking target.
+ rust_linkable_inherited_libs_.AppendInherited(
+ dep->rust_linkable_inherited_libs(), true);
+ }
if (dep->output_type() == RUST_LIBRARY ||
RustValues::InferredCrateType(dep) == RustValues::CRATE_DYLIB) {
diff --git a/src/gn/target.h b/src/gn/target.h
index de076a8..31d4e3a 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -322,6 +322,12 @@
const InheritedLibraries& rust_transitive_inheritable_libs() const {
return rust_transitive_inheritable_libs_;
}
+ // The transitive closure of libraries that are depended on by this target
+ // and are part of the current linking step. Previously-linked libraries are
+ // not included.
+ const InheritedLibraries& rust_linkable_inherited_libs() const {
+ return rust_linkable_inherited_libs_;
+ }
const UniqueVector<SourceDir>& all_lib_dirs() const { return all_lib_dirs_; }
const UniqueVector<LibFile>& all_libs() const { return all_libs_; }
@@ -534,6 +540,10 @@
// For each library marked public: "If you depend on me, you get access to
// these targets."
InheritedLibraries rust_transitive_inheritable_libs_;
+ // Lists all transitive libraries in the current linking target. Unlike the
+ // two sets above, this does not include libraries that have already been
+ // linked into a dependency. The public bit is ignored.
+ InheritedLibraries rust_linkable_inherited_libs_;
// User for Swift targets.
std::unique_ptr<SwiftValues> swift_values_;