Allow Rust targets to link against Rust cdylibs

There was previously a deficiency in the Rust binary target
generator that caused GN to fail to emit a link argument for
Rust cdylib dependencies.

This fixes the issue by linking against Rust cdylibs the same way
that other non-Rust native dependencies are handled. This also
fixes an issue in the unit test toolchain that prevented Rust
cdylib/dylib outputs from being set.

Fixes https://bugs.chromium.org/p/gn/issues/detail?id=233 .

Change-Id: Ief9c5f0dd652c0430ada756cbe09b5800d069962
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/11320
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: Tyler Mandry <tmandry@google.com>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/json_project_writer_unittest.cc b/src/gn/json_project_writer_unittest.cc
index a22511b..1915c30 100644
--- a/src/gn/json_project_writer_unittest.cc
+++ b/src/gn/json_project_writer_unittest.cc
@@ -172,7 +172,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_dylib": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
@@ -182,7 +182,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_macro": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
@@ -391,7 +391,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_dylib": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
@@ -401,7 +401,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_macro": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
@@ -634,7 +634,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_dylib": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
@@ -644,7 +644,7 @@
             "lib_switch": "-l",
             "linker_arg": "-Clink-arg=",
             "output_prefix": "lib",
-            "outputs": [ "" ]
+            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
          },
          "rust_macro": {
             "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 38f4686..568c2b4 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -154,7 +154,9 @@
     order_only_deps.push_back(non_linkable_dep->dependency_output_file());
   }
   for (const auto* linkable_dep : classified_deps.linkable_deps) {
-    if (linkable_dep->source_types_used().RustSourceUsed()) {
+    // Rust cdylibs are treated as non-Rust dependencies for linking purposes.
+    if (linkable_dep->source_types_used().RustSourceUsed() &&
+        linkable_dep->rust_values().crate_type() != RustValues::CRATE_CDYLIB) {
       rustdeps.push_back(linkable_dep->link_output_file());
     } else {
       nonrustdeps.push_back(linkable_dep->link_output_file());
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc
index 6840b72..a65a961 100644
--- a/src/gn/ninja_rust_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -890,3 +890,80 @@
     EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
   }
 }
+
+TEST_F(NinjaRustBinaryTargetWriterTest, CdylibDeps) {
+  Err err;
+  TestWithScope setup;
+  Target cdylib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  cdylib.set_output_type(Target::SHARED_LIBRARY);
+  cdylib.visibility().SetPublic();
+  SourceFile barlib("//bar/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.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(cdylib.OnResolved(&err));
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&cdylib, out);
+    writer.Run();
+    const char expected[] =
+        "crate_name = mylib\n"
+        "crate_type = cdylib\n"
+        "output_extension = .so\n"
+        "output_dir = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = libmylib\n"
+        "\n"
+        "build obj/bar/libmylib.so: rust_cdylib ../../bar/lib.rs | "
+        "../../bar/lib.rs\n"
+        "  externs =\n"
+        "  rustdeps =\n"
+        "  sources = ../../bar/lib.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+
+  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(SourceFile("//foo/source.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(&cdylib));
+  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/source.rs "
+        "../../foo/main.rs obj/bar/libmylib.so\n"
+        "  externs =\n"
+        "  rustdeps = -Ldependency=obj/bar -Lnative=obj/bar "
+        "-Clink-arg=obj/bar/libmylib.so\n"
+        "  sources = ../../foo/source.rs ../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/test_with_scope.cc b/src/gn/test_with_scope.cc
index 583eebb..757dd34 100644
--- a/src/gn/test_with_scope.cc
+++ b/src/gn/test_with_scope.cc
@@ -252,7 +252,7 @@
   cdylib_tool->set_output_prefix("lib");
   cdylib_tool->set_default_output_extension(".so");
   cdylib_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
   toolchain->SetTool(std::move(cdylib_tool));
 
   // DYLIB
@@ -265,7 +265,7 @@
   dylib_tool->set_output_prefix("lib");
   dylib_tool->set_default_output_extension(".so");
   dylib_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{root_output_dir}}/{{target_output_name}}{{output_extension}}"));
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}"));
   toolchain->SetTool(std::move(dylib_tool));
 
   // RUST_PROC_MACRO