[rust-project] Correct sysroot dependencies

liballoc also has a dependency on libcore, which prompted a change
to use a more extensible map of deps, and the separate short-
circuiting of adding a sysroot as well as each crate within it.

Change-Id: I818b3373f4ec6330e2976194b76c81ffbef07c9d
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8442
Commit-Queue: Petr Hosek <phosek@google.com>
Reviewed-by: Benjamin Brittain <bwb@google.com>
Reviewed-by: Tyler Mandry <tmandry@google.com>
Reviewed-by: Petr Hosek <phosek@google.com>
diff --git a/build/gen.py b/build/gen.py
index 99e06f7..34f349c 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -652,6 +652,7 @@
         'src/gn/input_conversion_unittest.cc',
         'src/gn/json_project_writer_unittest.cc',
         'src/gn/rust_project_writer_unittest.cc',
+        'src/gn/rust_project_writer_helpers_unittest.cc',
         'src/gn/label_pattern_unittest.cc',
         'src/gn/label_unittest.cc',
         'src/gn/loader_unittest.cc',
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
index 8452dae..f78ca2d 100644
--- a/src/gn/rust_project_writer.cc
+++ b/src/gn/rust_project_writer.cc
@@ -13,6 +13,7 @@
 #include "gn/deps_iterator.h"
 #include "gn/filesystem_utils.h"
 #include "gn/ninja_target_command_util.h"
+#include "gn/rust_project_writer_helpers.h"
 #include "gn/rust_tool.h"
 #include "gn/source_file.h"
 #include "gn/string_output_buffer.h"
@@ -72,10 +73,11 @@
   return out_buffer.WriteToFile(output_path, err);
 }
 
+// Map of Targets to their index in the crates list (for linking dependencies to
+// their indexes).
 using TargetIdxMap = std::unordered_map<const Target*, uint32_t>;
-using SysrootIdxMap =
-    std::unordered_map<std::string_view,
-                       std::unordered_map<std::string_view, uint32_t>>;
+
+// A collection of Targets.
 using TargetsVec = UniqueVector<const Target*>;
 
 // Get the Rust deps for a target, recursively expanding OutputType::GROUPS
@@ -105,7 +107,7 @@
                TargetIdxMap& lookup,
                SysrootIdxMap& sysroot_lookup,
                std::ostream& rust_project) {
-  bool first = true;
+  bool first_dep = true;
 
   rust_project << "      \"deps\": [";
 
@@ -117,26 +119,26 @@
     // TODO(bwb) If this library doesn't depend on std, use core instead
     auto std_idx = sysroot_lookup[current_sysroot].find("std");
     if (std_idx != sysroot_lookup[current_sysroot].end()) {
-      if (!first)
+      if (!first_dep)
         rust_project << ",";
       rust_project << NEWLINE << "        {" NEWLINE
                    << "          \"crate\": " << std::to_string(std_idx->second)
                    << "," NEWLINE << "          \"name\": \"std\"" NEWLINE
                    << "        }";
-      first = false;
+      first_dep = false;
     }
   }
 
   for (const auto& dep : GetRustDeps(target)) {
     auto idx = lookup[dep];
-    if (!first)
+    if (!first_dep)
       rust_project << ",";
     rust_project << NEWLINE << "        {" NEWLINE
                  << "          \"crate\": " << std::to_string(idx)
                  << "," NEWLINE << "          \"name\": \""
                  << dep->rust_values().crate_name() << "\"" NEWLINE
                  << "        }";
-    first = false;
+    first_dep = false;
   }
   rust_project << NEWLINE "      ]," NEWLINE;
 }
@@ -167,31 +169,40 @@
                                            "rustc_tsan",
                                            "syntax"};
 
-const std::string_view std_deps[] = {
-    "alloc",
-    "core",
-    "panic_abort",
-    "unwind",
-};
+// Multiple sysroot crates have dependenices on each other.  This provides a
+// mechanism for specifiying that in an extendible manner.
+const std::unordered_map<std::string_view, std::vector<std::string_view>>
+    sysroot_deps_map = {{"alloc", {"core"}},
+                        {"std", {"alloc", "core", "panic_abort", "unwind"}}};
 
+// Add each of the crates a sysroot has, including their dependencies.
 void AddSysrootCrate(const std::string_view crate,
                      const std::string_view current_sysroot,
                      uint32_t* count,
-                     SysrootIdxMap& sysroot_lookup,
+                     SysrootCrateIdxMap& sysroot_crate_lookup,
                      std::ostream& rust_project,
                      const BuildSettings* build_settings,
-                     bool first) {
-  if (crate == "std") {
-    for (auto dep : std_deps) {
-      AddSysrootCrate(dep, current_sysroot, count, sysroot_lookup, rust_project,
-                      build_settings, first);
-      first = false;
+                     bool first_crate) {
+  if (sysroot_crate_lookup.find(crate) != sysroot_crate_lookup.end()) {
+    // If this sysroot crate is already in the lookup, we don't add it again.
+    return;
+  }
+
+  // Add any crates that this sysroot crate depends on.
+  auto deps_lookup = sysroot_deps_map.find(crate);
+  if (deps_lookup != sysroot_deps_map.end()) {
+    auto deps = (*deps_lookup).second;
+    for (auto dep : deps) {
+      AddSysrootCrate(dep, current_sysroot, count, sysroot_crate_lookup,
+                      rust_project, build_settings, first_crate);
+      first_crate = false;
     }
   }
 
-  if (!first)
+  if (!first_crate)
     rust_project << "," NEWLINE;
-  sysroot_lookup[current_sysroot].insert(std::make_pair(crate, *count));
+  first_crate = false;
+  sysroot_crate_lookup.insert(std::make_pair(crate, *count));
 
   base::FilePath rebased_out_dir =
       build_settings->GetFullPath(build_settings->build_dir());
@@ -208,14 +219,14 @@
   rust_project << "      \"edition\": \"2018\"," NEWLINE;
   rust_project << "      \"deps\": [";
   (*count)++;
-  if (crate == "std") {
-    first = true;
-    for (auto dep : std_deps) {
-      auto idx = sysroot_lookup[current_sysroot][dep];
-      if (!first) {
+  if (deps_lookup != sysroot_deps_map.end()) {
+    auto deps = (*deps_lookup).second;
+    bool first_dep = true;
+    for (auto dep : deps) {
+      auto idx = sysroot_crate_lookup[dep];
+      if (!first_dep)
         rust_project << ",";
-      }
-      first = false;
+      first_dep = false;
       rust_project << NEWLINE "        {" NEWLINE
                    << "          \"crate\": " << std::to_string(idx)
                    << "," NEWLINE "          \"name\": \"" << dep
@@ -229,13 +240,33 @@
   rust_project << "    }";
 }
 
+// Add the given sysroot to the project, if it hasn't already been added.
+void AddSysroot(const std::string_view sysroot,
+                uint32_t* count,
+                SysrootIdxMap& sysroot_lookup,
+                std::ostream& rust_project,
+                const BuildSettings* build_settings,
+                bool first_crate) {
+  // If this sysroot is already in the lookup, we don't add it again.
+  if (sysroot_lookup.find(sysroot) != sysroot_lookup.end()) {
+    return;
+  }
+
+  // Otherwise, add all of its crates
+  for (auto crate : sysroot_crates) {
+    AddSysrootCrate(crate, sysroot, count, sysroot_lookup[sysroot],
+                    rust_project, build_settings, first_crate);
+    first_crate = false;
+  }
+}
+
 void AddTarget(const Target* target,
                uint32_t* count,
                TargetIdxMap& lookup,
                SysrootIdxMap& sysroot_lookup,
                const BuildSettings* build_settings,
                std::ostream& rust_project,
-               bool first) {
+               bool first_crate) {
   if (lookup.find(target) != lookup.end()) {
     // If target is already in the lookup, we don't add it again.
     return;
@@ -246,24 +277,19 @@
       target->toolchain()->GetToolForSourceTypeAsRust(SourceFile::SOURCE_RS);
   auto current_sysroot = rust_tool->GetSysroot();
   if (current_sysroot != "" && sysroot_lookup.count(current_sysroot) == 0) {
-    for (const auto& crate : sysroot_crates) {
-      AddSysrootCrate(crate, current_sysroot, count, sysroot_lookup,
-                      rust_project, build_settings, first);
-      first = false;
-    }
+    AddSysroot(current_sysroot, count, sysroot_lookup, rust_project,
+               build_settings, first_crate);
+    first_crate = 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;
-    }
+    AddTarget(dep, count, lookup, sysroot_lookup, build_settings, rust_project,
+              first_crate);
+    first_crate = false;
   }
 
-  if (!first) {
+  if (!first_crate)
     rust_project << "," NEWLINE;
-  }
 
   // Construct the crate info.
   rust_project << "    {" NEWLINE;
@@ -306,15 +332,14 @@
     }
   }
 
-if (!edition_set)
+  if (!edition_set)
     rust_project << "      \"edition\": \"2015\"," NEWLINE;
 
   rust_project << "      \"cfg\": [";
   bool first_cfg = true;
   for (const auto& cfg : cfgs) {
-    if (!first_cfg) {
+    if (!first_cfg)
       rust_project << ",";
-    }
     first_cfg = false;
     rust_project << NEWLINE;
     rust_project << "        \"" << cfg << "\"";
diff --git a/src/gn/rust_project_writer_helpers.h b/src/gn/rust_project_writer_helpers.h
new file mode 100644
index 0000000..a70b280
--- /dev/null
+++ b/src/gn/rust_project_writer_helpers.h
@@ -0,0 +1,43 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
+#define TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
+
+#include <fstream>
+#include <sstream>
+#include <unordered_map>
+
+#include "build_settings.h"
+#include "gn/target.h"
+
+// These are internal types and helper functions for RustProjectWriter that have
+// been extracted for easier testability.
+
+// Mapping of a sysroot crate (path) to it's index in the crates list.
+using SysrootCrateIdxMap = std::unordered_map<std::string_view, uint32_t>;
+
+// Mapping of a sysroot (path) to the mapping of each of the sysroot crates to
+// their index in the crates list.
+using SysrootIdxMap = std::unordered_map<std::string_view, SysrootCrateIdxMap>;
+
+// Add all of the crates for a sysroot (path) to the rust_project ostream.
+void AddSysroot(const std::string_view sysroot,
+                uint32_t* count,
+                SysrootIdxMap& sysroot_lookup,
+                std::ostream& rust_project,
+                const BuildSettings* build_settings,
+                bool first_crate);
+
+// Add a sysroot crate to the rust_project ostream, first recursively adding its
+// sysroot crate depedencies.
+void AddSysrootCrate(const std::string_view crate,
+                     const std::string_view current_sysroot,
+                     uint32_t* count,
+                     SysrootCrateIdxMap& sysroot_crate_lookup,
+                     std::ostream& rust_project,
+                     const BuildSettings* build_settings,
+                     bool first_crate);
+
+#endif  // TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
\ No newline at end of file
diff --git a/src/gn/rust_project_writer_helpers_unittest.cc b/src/gn/rust_project_writer_helpers_unittest.cc
new file mode 100644
index 0000000..bb932ad
--- /dev/null
+++ b/src/gn/rust_project_writer_helpers_unittest.cc
@@ -0,0 +1,231 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gn/rust_project_writer_helpers.h"
+
+#include "base/strings/string_util.h"
+#include "gn/string_output_buffer.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using RustProjectWriterHelper = TestWithScheduler;
+
+TEST_F(RustProjectWriterHelper, SysrootDepsAreCorrect) {
+  TestWithScope setup;
+
+  SysrootIdxMap sysroot_lookup;
+  uint32_t current_crate_count = 2;
+
+  std::ostringstream stream;
+
+  AddSysroot("path", &current_crate_count, sysroot_lookup, stream, setup.build_settings(),
+             false);
+
+  std::string out = stream.str();
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+
+const char expected_json[] = ",\n"
+    "    {\n"
+    "      \"crate_id\": 2,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libcore/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 3,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liballoc/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "        {\n"
+    "          \"crate\": 2,\n"
+    "          \"name\": \"core\"\n"
+    "        }\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 4,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libpanic_abort/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 5,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libunwind/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 6,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libstd/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "        {\n"
+    "          \"crate\": 3,\n"
+    "          \"name\": \"alloc\"\n"
+    "        },\n"
+    "        {\n"
+    "          \"crate\": 2,\n"
+    "          \"name\": \"core\"\n"
+    "        },\n"
+    "        {\n"
+    "          \"crate\": 4,\n"
+    "          \"name\": \"panic_abort\"\n"
+    "        },\n"
+    "        {\n"
+    "          \"crate\": 5,\n"
+    "          \"name\": \"unwind\"\n"
+    "        }\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 7,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libcollections/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 8,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liblibc/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 9,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libpanic_unwind/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 10,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libproc_macro/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 11,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/librustc_unicode/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 12,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libstd_unicode/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 13,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libtest/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 14,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liballoc_jemalloc/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 15,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liballoc_system/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 16,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libcompiler_builtins/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 17,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libgetopts/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 18,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libbuild_helper/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 19,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/librustc_asan/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 20,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/librustc_lsan/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 21,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/librustc_msan/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 22,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/librustc_tsan/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 23,\n"
+    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libsyntax/lib.rs\",\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"cfg\": []\n"
+    "    }"
+;
+  EXPECT_EQ(expected_json, out);
+}