Link Rust binaries against transitive public_deps

An earlier commit, 646a62e, changed how Rust links against native
dependencies. Rather than a `-l...` argument to instruct rustc to
link against the specified library, GN uses `-Clink-arg=` to bypass
rustc and directly instruct the linker to link the native library.

One implication of this change is that Rust binaries no longer link
against native libraries that they transitively depend on through
a Rust library dependency's public dependency.

More concretely, say Rust binary A depends on Rust library B,
which depends on native library C, which also declares a public_dep
on D. B is therefore allowed to use D, and must link against it.
However, B isn't actually linked -- A is the binary that gets
linked. If A uses a method from B that uses a method from D, A
must link against D.

After the previously mentioned change, A no longer links against D.
If B were to explicitly depend on D (rather than depend on it through
C's public_deps), this would work.

This commit fixes the issue by ensuring that public dependencies on
shared libraries are propagated up through the list of Rust
transitive library dependencies, allowing the final Rust binary
to link against all necessary native libraries.

Change-Id: I374167db1b9338f42426f1cbd6f15dd535358d94
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12001
Reviewed-by: Tyler Mandry <tmandry@google.com>
Commit-Queue: Tyler Mandry <tmandry@google.com>
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index 8d3f07e..b03b2cb 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -990,3 +990,78 @@
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 }
+
+TEST_F(NinjaRustBinaryTargetWriterTest, TransitivePublicNonRustDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // This test verifies that the Rust binary "target" links against this lib.
+  Target implicitlib(setup.settings(), Label(SourceDir("//foo/"), "implicit"));
+  implicitlib.set_output_type(Target::SHARED_LIBRARY);
+  implicitlib.visibility().SetPublic();
+  implicitlib.sources().push_back(SourceFile("//foo/implicit.cpp"));
+  implicitlib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  implicitlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(implicitlib.OnResolved(&err));
+
+  Target sharedlib(setup.settings(), Label(SourceDir("//foo/"), "shared"));
+  sharedlib.set_output_type(Target::SHARED_LIBRARY);
+  sharedlib.visibility().SetPublic();
+  sharedlib.sources().push_back(SourceFile("//foo/shared.cpp"));
+  sharedlib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  sharedlib.SetToolchain(setup.toolchain());
+  sharedlib.public_deps().push_back(LabelTargetPair(&implicitlib));
+  ASSERT_TRUE(sharedlib.OnResolved(&err));
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.SetToolchain(setup.toolchain());
+  rlib.private_deps().push_back(LabelTargetPair(&sharedlib));
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::EXECUTABLE);
+  target.visibility().SetPublic();
+  SourceFile main("//foo/main.rs");
+  target.sources().push_back(main);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(main);
+  target.rust_values().crate_name() = "foo_bar";
+  target.private_deps().push_back(LabelTargetPair(&rlib));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = foo_bar\n"
+        "crate_type = bin\n"
+        "output_extension = \n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/main.rs "
+        "obj/bar/libmylib.rlib ./libshared.so ./libimplicit.so\n"
+        "  externs = --extern mylib=obj/bar/libmylib.rlib\n"
+        "  rustdeps = -Ldependency=obj/bar -Lnative=. -Clink-arg=-Bdynamic "
+        "-Clink-arg=./libshared.so -Clink-arg=./libimplicit.so\n"
+        "  ldflags =\n"
+        "  sources = ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/target.cc b/src/gn/target.cc
index bd3fb58..5600f46 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -656,6 +656,16 @@
     inherited_libraries_.Append(dep, is_public);
   }
 
+  // Propagate public dependent libraries.
+  for (const auto& transitive :
+       dep->rust_values().transitive_libs().GetOrderedAndPublicFlag()) {
+    if (transitive.second &&
+        (dep->output_type() == STATIC_LIBRARY ||
+         dep->output_type() == SHARED_LIBRARY)) {
+      rust_values().transitive_libs().Append(transitive.first, is_public);
+    }
+  }
+
   if (dep->output_type() == RUST_LIBRARY) {
     rust_values().transitive_libs().Append(dep, is_public);
     rust_values().transitive_libs().AppendInherited(