Always link dependencies into Rust static libraries

Rust handling of static libraries differs from C/C++ where we always
need to link all dependencies, but they are not considered final.

Change-Id: I843b0d0b9db435560b5b6da75f0696852a11ace8
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/10500
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Petr Hosek <phosek@google.com>
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index 08e8c93..e4b0652 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -159,14 +159,22 @@
   if (can_link_libs && dep->swift_values().builds_module())
     classified_deps->swiftmodule_deps.push_back(dep);
 
-  if (dep->output_type() == Target::SOURCE_SET ||
-      // If a complete static library depends on an incomplete static library,
-      // manually link in the object files of the dependent library as if it
-      // were a source set. This avoids problems with braindead tools such as
-      // ar which don't properly link dependent static libraries.
-      (target_->complete_static_lib() &&
-       (dep->output_type() == Target::STATIC_LIBRARY &&
-        !dep->complete_static_lib()))) {
+  if (target_->source_types_used().RustSourceUsed() &&
+      (target_->output_type() == Target::RUST_LIBRARY ||
+       target_->output_type() == Target::STATIC_LIBRARY) &&
+      dep->IsLinkable()) {
+    // Rust libraries and static libraries aren't final, but need to have the
+    // link lines of all transitive deps specified.
+    classified_deps->linkable_deps.push_back(dep);
+  } else if (dep->output_type() == Target::SOURCE_SET ||
+             // If a complete static library depends on an incomplete static
+             // library, manually link in the object files of the dependent
+             // library as if it were a source set. This avoids problems with
+             // braindead tools such as ar which don't properly link dependent
+             // static libraries.
+             (target_->complete_static_lib() &&
+              (dep->output_type() == Target::STATIC_LIBRARY &&
+               !dep->complete_static_lib()))) {
     // Source sets have their object files linked into final targets
     // (shared libraries, executables, loadable modules, and complete static
     // libraries). Intermediate static libraries and other source sets
@@ -182,11 +190,6 @@
     // can be complete. Otherwise, these will be skipped since this target
     // will depend only on the source set's object files.
     classified_deps->non_linkable_deps.push_back(dep);
-  } else if (target_->output_type() == Target::RUST_LIBRARY &&
-             dep->IsLinkable()) {
-    // Rust libraries aren't final, but need to have the link lines of all
-    // transitive deps specified.
-    classified_deps->linkable_deps.push_back(dep);
   } else if (target_->complete_static_lib() && dep->IsFinal()) {
     classified_deps->non_linkable_deps.push_back(dep);
   } else if (can_link_libs && dep->IsLinkable()) {
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index 34d87f0..e58572b 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -351,6 +351,14 @@
   Err err;
   TestWithScope setup;
 
+  Target staticlib(setup.settings(), Label(SourceDir("//foo/"), "static"));
+  staticlib.set_output_type(Target::STATIC_LIBRARY);
+  staticlib.visibility().SetPublic();
+  staticlib.sources().push_back(SourceFile("//foo/static.cpp"));
+  staticlib.source_types_used().Set(SourceFile::SOURCE_CPP);
+  staticlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(staticlib.OnResolved(&err));
+
   Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
   rlib.set_output_type(Target::RUST_LIBRARY);
   rlib.visibility().SetPublic();
@@ -363,14 +371,6 @@
   rlib.SetToolchain(setup.toolchain());
   ASSERT_TRUE(rlib.OnResolved(&err));
 
-  Target staticlib(setup.settings(), Label(SourceDir("//foo/"), "static"));
-  staticlib.set_output_type(Target::STATIC_LIBRARY);
-  staticlib.visibility().SetPublic();
-  staticlib.sources().push_back(SourceFile("//foo/static.cpp"));
-  staticlib.source_types_used().Set(SourceFile::SOURCE_CPP);
-  staticlib.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(staticlib.OnResolved(&err));
-
   Target sharedlib(setup.settings(), Label(SourceDir("//foo/"), "shared"));
   sharedlib.set_output_type(Target::SHARED_LIBRARY);
   sharedlib.visibility().SetPublic();
@@ -482,6 +482,43 @@
     std::string out_str = out.str();
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
+
+  Target rstaticlib(setup.settings(), Label(SourceDir("//baz/"), "baz"));
+  rstaticlib.set_output_type(Target::STATIC_LIBRARY);
+  rstaticlib.visibility().SetPublic();
+  SourceFile bazlib("//baz/lib.rs");
+  rstaticlib.sources().push_back(bazlib);
+  rstaticlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rstaticlib.rust_values().set_crate_root(bazlib);
+  rstaticlib.rust_values().crate_name() = "baz";
+  rstaticlib.private_deps().push_back(LabelTargetPair(&staticlib));
+  rstaticlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rstaticlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rstaticlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = baz\n"
+        "crate_type = staticlib\n"
+        "output_extension = .a\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/baz\n"
+        "target_output_name = libbaz\n"
+        "\n"
+        "build obj/baz/libbaz.a: rust_staticlib ../../baz/lib.rs | ../../baz/lib.rs "
+        "obj/foo/libstatic.a\n"
+        "  externs =\n"
+        "  rustdeps = -Lnative=obj/foo -lstatic\n"
+        "  sources = ../../baz/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
 }
 
 TEST_F(NinjaRustBinaryTargetWriterTest, RustOutputExtensionAndDir) {