[rust-project] Only list direct dependencies, not transitive deps

For each target in the file, only list that target's direct dependencies
and not the transitive dependencies.

Add a "label" field to each crate entry to specifiy which GN target the rust
crate corresponds to (debugging aid).

Change-Id: I59074ef7af647a7095db6cec6579aa8f2530c354
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8440
Reviewed-by: Benjamin Brittain <bwb@google.com>
Commit-Queue: Benjamin Brittain <bwb@google.com>
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
index 19eb426..0c238ce 100644
--- a/src/gn/rust_project_writer.cc
+++ b/src/gn/rust_project_writer.cc
@@ -10,6 +10,7 @@
 
 #include "base/json/string_escape.h"
 #include "gn/builder.h"
+#include "gn/deps_iterator.h"
 #include "gn/filesystem_utils.h"
 #include "gn/ninja_target_command_util.h"
 #include "gn/rust_tool.h"
@@ -75,6 +76,30 @@
 using SysrootIdxMap =
     std::unordered_map<std::string_view,
                        std::unordered_map<std::string_view, uint32_t>>;
+using TargetsVec = UniqueVector<const Target*>;
+
+// Get the Rust deps for a target, recursively expanding OutputType::GROUPS
+// that are present in the GN structure.  This will return a flattened list of
+// deps from the groups, but will not expand a Rust lib dependency to find any
+// transitive Rust dependencies.
+void GetRustDeps(const Target* target, TargetsVec* rust_deps) {
+  for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+    const Target* dep = pair.ptr;
+
+    if (dep->source_types_used().RustSourceUsed()) {
+      // Include any Rust dep.
+      rust_deps->push_back(dep);
+    } else if (dep->output_type() == Target::OutputType::GROUP) {
+      // Inspect (recursively) any group to see if it contains Rust deps.
+      GetRustDeps(dep, rust_deps);
+    }
+  }
+}
+TargetsVec GetRustDeps(const Target* target) {
+  TargetsVec deps;
+  GetRustDeps(target, &deps);
+  return deps;
+}
 
 void WriteDeps(const Target* target,
                TargetIdxMap& lookup,
@@ -102,7 +127,7 @@
     }
   }
 
-  for (const auto& dep : target->rust_values().transitive_libs().GetOrdered()) {
+  for (const auto& dep : GetRustDeps(target)) {
     auto idx = lookup[dep];
     if (!first)
       rust_project << ",";
@@ -228,11 +253,12 @@
     }
   }
 
-  // Add each dependency first before we write any of the parent target.
-  for (const auto& dep : target->rust_values().transitive_libs().GetOrdered()) {
-    AddTarget(dep, count, lookup, sysroot_lookup, build_settings, rust_project,
-              first);
-    first = false;
+  for (const auto& dep : GetRustDeps(target)) {
+    if (dep->source_types_used().RustSourceUsed()) {
+      AddTarget(dep, count, lookup, sysroot_lookup, build_settings,
+                rust_project, first);
+      first = false;
+    }
   }
 
   if (!first) {
@@ -253,6 +279,8 @@
 
   rust_project << "      \"root_module\": \"" << FilePathToUTF8(crate_root)
                << "\"," NEWLINE;
+  rust_project << "      \"label\": \""
+               << target->label().GetUserVisibleName(false) << "\"," NEWLINE;
 
   WriteDeps(target, lookup, sysroot_lookup, rust_project);
 
diff --git a/src/gn/rust_project_writer_unittest.cc b/src/gn/rust_project_writer_unittest.cc
index 1d8d153..e43641d 100644
--- a/src/gn/rust_project_writer_unittest.cc
+++ b/src/gn/rust_project_writer_unittest.cc
@@ -43,6 +43,7 @@
       "    {\n"
       "      \"crate_id\": 0,\n"
       "      \"root_module\": \"foo/lib.rs\",\n"
+      "      \"label\": \"//foo:bar\",\n"
       "      \"deps\": [\n"
       "      ],\n"
       "      \"edition\": \"2015\",\n"
@@ -98,6 +99,7 @@
       "    {\n"
       "      \"crate_id\": 0,\n"
       "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
       "      \"deps\": [\n"
       "      ],\n"
       "      \"edition\": \"2015\",\n"
@@ -109,6 +111,7 @@
       "    {\n"
       "      \"crate_id\": 1,\n"
       "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
       "      \"deps\": [\n"
       "        {\n"
       "          \"crate\": 0,\n"
@@ -179,6 +182,7 @@
       "    {\n"
       "      \"crate_id\": 0,\n"
       "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
       "      \"deps\": [\n"
       "      ],\n"
       "      \"edition\": \"2015\",\n"
@@ -190,6 +194,7 @@
       "    {\n"
       "      \"crate_id\": 1,\n"
       "      \"root_module\": \"achilles/lib.rs\",\n"
+      "      \"label\": \"//achilles:bar\",\n"
       "      \"deps\": [\n"
       "      ],\n"
       "      \"edition\": \"2015\",\n"
@@ -201,6 +206,7 @@
       "    {\n"
       "      \"crate_id\": 2,\n"
       "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
       "      \"deps\": [\n"
       "        {\n"
       "          \"crate\": 0,\n"
@@ -221,3 +227,117 @@
       "}\n";
   EXPECT_EQ(expected_json, out);
 }
+
+// Test that when outputting dependencies, only Rust deps are returned,
+// and that any groups are inspected to see if they include Rust deps.
+TEST_F(RustProjectJSONWriter, RustTargetGetDepRustOnly) {
+  Err err;
+  TestWithScope setup;
+
+  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
+  dep.set_output_type(Target::RUST_LIBRARY);
+  dep.visibility().SetPublic();
+  SourceFile tlib("//tortoise/lib.rs");
+  dep.sources().push_back(tlib);
+  dep.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep.rust_values().set_crate_root(tlib);
+  dep.rust_values().crate_name() = "tortoise";
+  dep.SetToolchain(setup.toolchain());
+
+  Target dep2(setup.settings(), Label(SourceDir("//achilles/"), "bar"));
+  dep2.set_output_type(Target::STATIC_LIBRARY);
+  dep2.visibility().SetPublic();
+  SourceFile alib("//achilles/lib.o");
+  dep2.SetToolchain(setup.toolchain());
+
+  Target dep3(setup.settings(), Label(SourceDir("//achilles/"), "group"));
+  dep3.set_output_type(Target::GROUP);
+  dep3.visibility().SetPublic();
+  dep3.public_deps().push_back(LabelTargetPair(&dep));
+  dep3.SetToolchain(setup.toolchain());
+
+  Target dep4(setup.settings(), Label(SourceDir("//tortoise/"), "macro"));
+  dep4.set_output_type(Target::RUST_PROC_MACRO);
+  dep4.visibility().SetPublic();
+  SourceFile tmlib("//tortoise/macro/lib.rs");
+  dep4.sources().push_back(tmlib);
+  dep4.source_types_used().Set(SourceFile::SOURCE_RS);
+  dep4.rust_values().set_crate_root(tmlib);
+  dep4.rust_values().crate_name() = "tortoise_macro";
+  dep4.SetToolchain(setup.toolchain());
+
+  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
+  target.set_output_type(Target::RUST_LIBRARY);
+  target.visibility().SetPublic();
+  SourceFile harelib("//hare/lib.rs");
+  target.sources().push_back(harelib);
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.rust_values().set_crate_root(harelib);
+  target.rust_values().crate_name() = "hare";
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep3));
+  target.public_deps().push_back(LabelTargetPair(&dep4));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream stream;
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "  \"roots\": [],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"atom_cfgs\": [\n"
+      "      ],\n"
+      "      \"key_value_cfgs\": {\n"
+      "      }\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"tortoise/macro/lib.rs\",\n"
+      "      \"label\": \"//tortoise:macro\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"atom_cfgs\": [\n"
+      "      ],\n"
+      "      \"key_value_cfgs\": {\n"
+      "      }\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 2,\n"
+      "      \"root_module\": \"hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        },\n"
+      "        {\n"
+      "          \"crate\": 1,\n"
+      "          \"name\": \"tortoise_macro\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"atom_cfgs\": [\n"
+      "      ],\n"
+      "      \"key_value_cfgs\": {\n"
+      "      }\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
+
+  EXPECT_EQ(expected_json, out);
+}