[rust-project.json]  Refactor and print each crate source root

The source root for each crate is now required by rust analyzer.
This change refactors the RustProjectJsonWriter to work in two
phases:
1) generate the full list of crates (with dependencies and cfgs)
2) write out that list of crates to the output stream

This also normalizes the output of the sysroot crates to use the
same json writing code as the rest of the crates (but sysroot
crates are still added to the crate list in a special manner).

Change-Id: I4b78ecf5444edf538b2c20088ca83372054e2098
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8720
Commit-Queue: Aaron Wood <aaronwood@google.com>
Reviewed-by: Petr Hosek <phosek@google.com>
Reviewed-by: Tyler Mandry <tmandry@google.com>
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
index f78ca2d..1dc9f7a 100644
--- a/src/gn/rust_project_writer.cc
+++ b/src/gn/rust_project_writer.cc
@@ -28,7 +28,9 @@
 // Current structure of rust-project.json output file
 //
 // {
-//    "roots": [] // always empty for GN. To be deprecated.
+//    "roots": [
+//      "some/source/root"  // each crate's source root
+//    ],
 //    "crates": [
 //        {
 //            "deps": [
@@ -75,16 +77,16 @@
 
 // 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 TargetIndexMap = std::unordered_map<const Target*, uint32_t>;
 
 // A collection of Targets.
-using TargetsVec = UniqueVector<const Target*>;
+using TargetsVector = 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) {
+void GetRustDeps(const Target* target, TargetsVector* rust_deps) {
   for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
     const Target* dep = pair.ptr;
 
@@ -97,52 +99,12 @@
     }
   }
 }
-TargetsVec GetRustDeps(const Target* target) {
-  TargetsVec deps;
+TargetsVector GetRustDeps(const Target* target) {
+  TargetsVector deps;
   GetRustDeps(target, &deps);
   return deps;
 }
 
-void WriteDeps(const Target* target,
-               TargetIdxMap& lookup,
-               SysrootIdxMap& sysroot_lookup,
-               std::ostream& rust_project) {
-  bool first_dep = true;
-
-  rust_project << "      \"deps\": [";
-
-  // Check if this target has had it's sysroot setup yet
-  auto rust_tool =
-      target->toolchain()->GetToolForSourceTypeAsRust(SourceFile::SOURCE_RS);
-  auto current_sysroot = rust_tool->GetSysroot();
-  if (current_sysroot != "") {
-    // 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_dep)
-        rust_project << ",";
-      rust_project << NEWLINE << "        {" NEWLINE
-                   << "          \"crate\": " << std::to_string(std_idx->second)
-                   << "," NEWLINE << "          \"name\": \"std\"" NEWLINE
-                   << "        }";
-      first_dep = false;
-    }
-  }
-
-  for (const auto& dep : GetRustDeps(target)) {
-    auto idx = lookup[dep];
-    if (!first_dep)
-      rust_project << ",";
-    rust_project << NEWLINE << "        {" NEWLINE
-                 << "          \"crate\": " << std::to_string(idx)
-                 << "," NEWLINE << "          \"name\": \""
-                 << dep->rust_values().crate_name() << "\"" NEWLINE
-                 << "        }";
-    first_dep = false;
-  }
-  rust_project << NEWLINE "      ]," NEWLINE;
-}
-
 // TODO(bwb) Parse sysroot structure from toml files. This is fragile and might
 // break if upstream changes the dependency structure.
 const std::string_view sysroot_crates[] = {"std",
@@ -159,7 +121,6 @@
                                            "alloc_system",
                                            "compiler_builtins",
                                            "getopts",
-                                           "panic_unwind",
                                            "panic_abort",
                                            "unwind",
                                            "build_helper",
@@ -176,13 +137,11 @@
                         {"std", {"alloc", "core", "panic_abort", "unwind"}}};
 
 // Add each of the crates a sysroot has, including their dependencies.
-void AddSysrootCrate(const std::string_view crate,
+void AddSysrootCrate(const BuildSettings* build_settings,
+                     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) {
+                     SysrootCrateIndexMap& sysroot_crate_lookup,
+                     CrateList& crate_list) {
   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;
@@ -193,60 +152,39 @@
   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;
+      AddSysrootCrate(build_settings, dep, current_sysroot,
+                      sysroot_crate_lookup, crate_list);
     }
   }
 
-  if (!first_crate)
-    rust_project << "," NEWLINE;
-  first_crate = false;
-  sysroot_crate_lookup.insert(std::make_pair(crate, *count));
+  size_t crate_index = crate_list.size();
+  sysroot_crate_lookup.insert(std::make_pair(crate, crate_index));
 
   base::FilePath rebased_out_dir =
       build_settings->GetFullPath(build_settings->build_dir());
   auto crate_path =
       FilePathToUTF8(rebased_out_dir) + std::string(current_sysroot) +
       "/lib/rustlib/src/rust/src/lib" + std::string(crate) + "/lib.rs";
-  base::FilePath crate_root = build_settings->GetFullPath(crate_path, false);
 
-  rust_project << "    {" NEWLINE;
-  rust_project << "      \"crate_id\": " << std::to_string(*count)
-               << "," NEWLINE;
-  rust_project << "      \"root_module\": \"" << FilePathToUTF8(crate_root)
-               << "\"," NEWLINE;
-  rust_project << "      \"edition\": \"2018\"," NEWLINE;
-  rust_project << "      \"deps\": [";
-  (*count)++;
+  Crate sysroot_crate =
+      Crate(SourceFile(crate_path), crate_index, std::string(crate), "2018");
+
   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_dep = false;
-      rust_project << NEWLINE "        {" NEWLINE
-                   << "          \"crate\": " << std::to_string(idx)
-                   << "," NEWLINE "          \"name\": \"" << dep
-                   << "\"" NEWLINE "        }";
+      sysroot_crate.AddDependency(idx, std::string(dep));
     }
   }
-  rust_project << NEWLINE "      ]," NEWLINE;
 
-  rust_project << "      \"cfg\": []" NEWLINE;
-
-  rust_project << "    }";
+  crate_list.push_back(sysroot_crate);
 }
 
 // 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) {
+void AddSysroot(const BuildSettings* build_settings,
+                const std::string_view sysroot,
+                SysrootIndexMap& sysroot_lookup,
+                CrateList& crate_list) {
   // If this sysroot is already in the lookup, we don't add it again.
   if (sysroot_lookup.find(sysroot) != sysroot_lookup.end()) {
     return;
@@ -254,74 +192,57 @@
 
   // 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;
+    AddSysrootCrate(build_settings, crate, sysroot, sysroot_lookup[sysroot],
+                    crate_list);
   }
 }
 
-void AddTarget(const Target* target,
-               uint32_t* count,
-               TargetIdxMap& lookup,
-               SysrootIdxMap& sysroot_lookup,
-               const BuildSettings* build_settings,
-               std::ostream& rust_project,
-               bool first_crate) {
+void AddTarget(const BuildSettings* build_settings,
+               const Target* target,
+               TargetIndexMap& lookup,
+               SysrootIndexMap& sysroot_lookup,
+               CrateList& crate_list) {
   if (lookup.find(target) != lookup.end()) {
     // If target is already in the lookup, we don't add it again.
     return;
   }
 
-  // Check what sysroot this target needs.
+  // Check what sysroot this target needs.  Add it to the crate list if it
+  // hasn't already been added.
   auto rust_tool =
       target->toolchain()->GetToolForSourceTypeAsRust(SourceFile::SOURCE_RS);
   auto current_sysroot = rust_tool->GetSysroot();
   if (current_sysroot != "" && sysroot_lookup.count(current_sysroot) == 0) {
-    AddSysroot(current_sysroot, count, sysroot_lookup, rust_project,
-               build_settings, first_crate);
-    first_crate = false;
+    AddSysroot(build_settings, current_sysroot, sysroot_lookup, crate_list);
   }
 
-  for (const auto& dep : GetRustDeps(target)) {
-    AddTarget(dep, count, lookup, sysroot_lookup, build_settings, rust_project,
-              first_crate);
-    first_crate = false;
+  auto crate_deps = GetRustDeps(target);
+
+  // Add all dependencies of this crate, before this crate.
+  for (const auto& dep : crate_deps) {
+    AddTarget(build_settings, dep, lookup, sysroot_lookup, crate_list);
   }
 
-  if (!first_crate)
-    rust_project << "," NEWLINE;
-
-  // Construct the crate info.
-  rust_project << "    {" NEWLINE;
-  rust_project << "      \"crate_id\": " << std::to_string(*count)
-               << "," NEWLINE;
+  // The index of a crate is its position (0-based) in the list of crates.
+  size_t crate_id = crate_list.size();
 
   // Add the target to the crate lookup.
-  lookup.insert(std::make_pair(target, *count));
-  (*count)++;
+  lookup.insert(std::make_pair(target, crate_id));
 
-  base::FilePath crate_root =
-      build_settings->GetFullPath(target->rust_values().crate_root());
-
-  rust_project << "      \"root_module\": \"" << FilePathToUTF8(crate_root)
-               << "\"," NEWLINE;
-  rust_project << "      \"label\": \""
-               << target->label().GetUserVisibleName(false) << "\"," NEWLINE;
-
-  WriteDeps(target, lookup, sysroot_lookup, rust_project);
+  SourceFile crate_root = target->rust_values().crate_root();
+  std::string crate_label = target->label().GetUserVisibleName(false);
 
   std::string cfg_prefix("--cfg=");
   std::string edition_prefix("--edition=");
-  std::vector<std::string> cfgs;
+  ConfigList cfgs;
 
-  bool edition_set = false;
+  std::string edition = "2015";  // default to be overridden as needed
+
   for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
     for (const auto& flag : iter.cur().rustflags()) {
       // extract the edition of this target
       if (!flag.compare(0, edition_prefix.size(), edition_prefix)) {
-        auto edition = flag.substr(edition_prefix.size());
-        rust_project << "      \"edition\": \"" << edition << "\"," NEWLINE;
-        edition_set = true;
+        edition = flag.substr(edition_prefix.size());
       }
       if (!flag.compare(0, cfg_prefix.size(), cfg_prefix)) {
         auto cfg = flag.substr(cfg_prefix.size());
@@ -332,47 +253,109 @@
     }
   }
 
-  if (!edition_set)
-    rust_project << "      \"edition\": \"2015\"," NEWLINE;
+  Crate crate = Crate(crate_root, crate_id, crate_label, edition);
 
-  rust_project << "      \"cfg\": [";
-  bool first_cfg = true;
-  for (const auto& cfg : cfgs) {
-    if (!first_cfg)
-      rust_project << ",";
-    first_cfg = false;
-    rust_project << NEWLINE;
-    rust_project << "        \"" << cfg << "\"";
+  for (auto& cfg : cfgs) {
+    crate.AddConfigItem(cfg);
   }
-  rust_project << NEWLINE;
-  rust_project << "      ]" NEWLINE;
 
-  rust_project << "    }";
+  // Add the sysroot dependency, if there is one.
+  if (current_sysroot != "") {
+    // 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()) {
+      crate.AddDependency(std_idx->second, "std");
+    }
+  }
+
+  // Add the rest of the crate dependencies.
+  for (const auto& dep : crate_deps) {
+    auto idx = lookup[dep];
+    crate.AddDependency(idx, dep->rust_values().crate_name());
+  }
+
+  crate_list.push_back(crate);
+}
+
+void WriteCrates(const BuildSettings* build_settings,
+                 CrateList& crate_list,
+                 std::ostream& rust_project) {
+  rust_project << "{" NEWLINE;
+  rust_project << "  \"roots\": [";
+  bool first_root = true;
+  for (auto& crate : crate_list) {
+    if (!first_root)
+      rust_project << ",";
+    first_root = false;
+
+    std::string root_dir =
+        FilePathToUTF8(build_settings->GetFullPath(crate.root().GetDir()));
+    rust_project << NEWLINE "    \"" << root_dir << "\"";
+  }
+  rust_project << NEWLINE "  ]," NEWLINE;
+  rust_project << "  \"crates\": [";
+  bool first_crate = true;
+  for (auto& crate : crate_list) {
+    if (!first_crate)
+      rust_project << ",";
+    first_crate = false;
+
+    auto crate_root = FilePathToUTF8(build_settings->GetFullPath(crate.root()));
+
+    rust_project << NEWLINE << "    {" NEWLINE
+                 << "      \"crate_id\": " << crate.index() << "," NEWLINE
+                 << "      \"root_module\": \"" << crate_root << "\"," NEWLINE
+                 << "      \"label\": \"" << crate.label() << "\"," NEWLINE
+                 << "      \"deps\": [";
+
+    bool first_dep = true;
+    for (auto& dep : crate.dependencies()) {
+      if (!first_dep)
+        rust_project << ",";
+      first_dep = false;
+
+      rust_project << NEWLINE << "        {" NEWLINE
+                   << "          \"crate\": " << dep.first << "," NEWLINE
+                   << "          \"name\": \"" << dep.second << "\"" NEWLINE
+                   << "        }";
+    }
+    rust_project << NEWLINE "      ]," NEWLINE;  // end dep list
+
+    rust_project << "      \"edition\": \"" << crate.edition() << "\"," NEWLINE;
+
+    rust_project << "      \"cfg\": [";
+    bool first_cfg = true;
+    for (const auto& cfg : crate.configs()) {
+      if (!first_cfg)
+        rust_project << ",";
+      first_cfg = false;
+
+      rust_project << NEWLINE;
+      rust_project << "        \"" << cfg << "\"";
+    }
+    rust_project << NEWLINE;
+    rust_project << "      ]" NEWLINE;  // end cfgs
+
+    rust_project << "    }";  // end crate
+  }
+  rust_project << NEWLINE "  ]" NEWLINE;  // end crate list
+  rust_project << "}" NEWLINE;
 }
 
 void RustProjectWriter::RenderJSON(const BuildSettings* build_settings,
                                    std::vector<const Target*>& all_targets,
                                    std::ostream& rust_project) {
-  TargetIdxMap lookup;
-  SysrootIdxMap sysroot_lookup;
-  uint32_t count = 0;
-  bool first = true;
-
-  rust_project << "{" NEWLINE;
-
-  rust_project << "  \"roots\": []," NEWLINE;
-  rust_project << "  \"crates\": [" NEWLINE;
+  TargetIndexMap lookup;
+  SysrootIndexMap sysroot_lookup;
+  CrateList crate_list;
 
   // All the crates defined in the project.
   for (const auto* target : all_targets) {
     if (!target->IsBinary() || !target->source_types_used().RustSourceUsed())
       continue;
 
-    AddTarget(target, &count, lookup, sysroot_lookup, build_settings,
-              rust_project, first);
-    first = false;
+    AddTarget(build_settings, target, lookup, sysroot_lookup, crate_list);
   }
 
-  rust_project << NEWLINE "  ]" NEWLINE;
-  rust_project << "}" NEWLINE;
+  WriteCrates(build_settings, crate_list, rust_project);
 }
diff --git a/src/gn/rust_project_writer_helpers.h b/src/gn/rust_project_writer_helpers.h
index a70b280..25340b3 100644
--- a/src/gn/rust_project_writer_helpers.h
+++ b/src/gn/rust_project_writer_helpers.h
@@ -7,37 +7,94 @@
 
 #include <fstream>
 #include <sstream>
+#include <string>
+#include <tuple>
 #include <unordered_map>
+#include <vector>
 
 #include "build_settings.h"
+#include "gn/source_file.h"
 #include "gn/target.h"
 
 // These are internal types and helper functions for RustProjectWriter that have
 // been extracted for easier testability.
 
+// Crate Index in the generated file
+using CrateIndex = size_t;
+
+using ConfigList = std::vector<std::string>;
+using Dependency = std::pair<CrateIndex, std::string>;
+using DependencyList = std::vector<Dependency>;
+
+// This class represents a crate to be serialized out as part of the
+// rust-project.json file.  This is used to separate the generating
+// of the data that needs to be in the file, from the file itself.
+class Crate {
+ public:
+  Crate(SourceFile root,
+        CrateIndex index,
+        std::string label,
+        std::string edition)
+      : root_(root), index_(index), label_(label), edition_(edition) {}
+
+  ~Crate() = default;
+
+  // Add a config item to the crate.
+  void AddConfigItem(std::string cfg_item) { configs_.push_back(cfg_item); }
+
+  // Add another crate as a dependency of this one.
+  void AddDependency(CrateIndex index, std::string name) {
+    deps_.push_back(std::make_pair(index, name));
+  }
+
+  // Returns the root file for the crate.
+  SourceFile& root() { return root_; }
+
+  // Returns the crate index.
+  CrateIndex index() { return index_; };
+
+  // Returns the displayable crate label.
+  const std::string& label() { return label_; }
+
+  // Returns the Rust Edition this crate uses.
+  const std::string& edition() { return edition_; }
+
+  // Return the set of config items for this crate.
+  ConfigList& configs() { return configs_; }
+
+  // Return the set of dependencies for this crate.
+  DependencyList& dependencies() { return deps_; }
+
+ private:
+  SourceFile root_;
+  CrateIndex index_;
+  std::string label_;
+  std::string edition_;
+  ConfigList configs_;
+  DependencyList deps_;
+};
+
+using CrateList = std::vector<Crate>;
+
 // Mapping of a sysroot crate (path) to it's index in the crates list.
-using SysrootCrateIdxMap = std::unordered_map<std::string_view, uint32_t>;
+using SysrootCrateIndexMap = std::unordered_map<std::string_view, CrateIndex>;
 
 // 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>;
+using SysrootIndexMap =
+    std::unordered_map<std::string_view, SysrootCrateIndexMap>;
 
 // 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 the given sysroot to the project, if it hasn't already been added.
+void AddSysroot(const BuildSettings* build_settings,
+                const std::string_view sysroot,
+                SysrootIndexMap& sysroot_lookup,
+                CrateList& crate_list);
 
-// 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);
+// Write the entire rust-project.json file contents into the given stream, based
+// on the the given crates list.
+void WriteCrates(const BuildSettings* build_settings,
+                 CrateList& crate_list,
+                 std::ostream& rust_project);
 
-#endif  // TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
\ No newline at end of file
+#endif  // TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
diff --git a/src/gn/rust_project_writer_helpers_unittest.cc b/src/gn/rust_project_writer_helpers_unittest.cc
index bb932ad..19501c4 100644
--- a/src/gn/rust_project_writer_helpers_unittest.cc
+++ b/src/gn/rust_project_writer_helpers_unittest.cc
@@ -5,6 +5,7 @@
 #include "gn/rust_project_writer_helpers.h"
 
 #include "base/strings/string_util.h"
+#include "gn/filesystem_utils.h"
 #include "gn/string_output_buffer.h"
 #include "gn/test_with_scheduler.h"
 #include "gn/test_with_scope.h"
@@ -13,219 +14,349 @@
 
 using RustProjectWriterHelper = TestWithScheduler;
 
-TEST_F(RustProjectWriterHelper, SysrootDepsAreCorrect) {
+TEST_F(RustProjectWriterHelper, WriteCrates) {
   TestWithScope setup;
 
-  SysrootIdxMap sysroot_lookup;
-  uint32_t current_crate_count = 2;
+  CrateList crates;
+  Crate dep = Crate(SourceFile("/root/tortoise/lib.rs"), 0, "//tortoise:bar", "2015");
+  Crate target = Crate(SourceFile("/root/hare/lib.rs"), 1, "//hare:bar", "2015");
+  target.AddDependency(0, "tortoise");
+  target.AddConfigItem("unix");
+  target.AddConfigItem("feature=\\\"test\\\"");
+
+  crates.push_back(dep);
+  crates.push_back(target);
 
   std::ostringstream stream;
+  WriteCrates(setup.build_settings(), crates, 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"
+      "    \"/root/tortoise/\",\n"
+      "    \"/root/hare/\"\n"
+      "  ],\n"
+      "  \"crates\": [\n"
+      "    {\n"
+      "      \"crate_id\": 0,\n"
+      "      \"root_module\": \"/root/tortoise/lib.rs\",\n"
+      "      \"label\": \"//tortoise:bar\",\n"
+      "      \"deps\": [\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "      ]\n"
+      "    },\n"
+      "    {\n"
+      "      \"crate_id\": 1,\n"
+      "      \"root_module\": \"/root/hare/lib.rs\",\n"
+      "      \"label\": \"//hare:bar\",\n"
+      "      \"deps\": [\n"
+      "        {\n"
+      "          \"crate\": 0,\n"
+      "          \"name\": \"tortoise\"\n"
+      "        }\n"
+      "      ],\n"
+      "      \"edition\": \"2015\",\n"
+      "      \"cfg\": [\n"
+      "        \"unix\",\n"
+      "        \"feature=\\\"test\\\"\"\n"
+      "      ]\n"
+      "    }\n"
+      "  ]\n"
+      "}\n";
 
-  AddSysroot("path", &current_crate_count, sysroot_lookup, stream, setup.build_settings(),
-             false);
+  EXPECT_EQ(expected_json, out);
+}
 
+TEST_F(RustProjectWriterHelper, SysrootDepsAreCorrect) {
+  TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("/root"));
+
+  SysrootIndexMap sysroot_lookup;
+  CrateList crates;
+
+  AddSysroot(setup.build_settings(), "sysroot", sysroot_lookup, crates);
+
+  std::ostringstream stream;
+  WriteCrates(setup.build_settings(), crates, stream);
   std::string out = stream.str();
 #if defined(OS_WIN)
   base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
 #endif
 
-const char expected_json[] = ",\n"
+const char expected_json[] =
+    "{\n"
+    "  \"roots\": [\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcore/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libpanic_abort/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libunwind/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libstd/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcollections/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liblibc/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libpanic_unwind/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libproc_macro/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_unicode/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libstd_unicode/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libtest/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc_jemalloc/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc_system/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcompiler_builtins/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libgetopts/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libbuild_helper/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_asan/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_lsan/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_msan/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_tsan/\",\n"
+    "    \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libsyntax/\"\n"
+    "  ],\n"
+    "  \"crates\": [\n"
     "    {\n"
-    "      \"crate_id\": 2,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libcore/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"crate_id\": 0,\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcore/lib.rs\",\n"
+    "      \"label\": \"core\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 1,\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc/lib.rs\",\n"
+    "      \"label\": \"alloc\",\n"
+    "      \"deps\": [\n"
+    "        {\n"
+    "          \"crate\": 0,\n"
+    "          \"name\": \"core\"\n"
+    "        }\n"
+    "      ],\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 2,\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libpanic_abort/lib.rs\",\n"
+    "      \"label\": \"panic_abort\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
     "    },\n"
     "    {\n"
     "      \"crate_id\": 3,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liballoc/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libunwind/lib.rs\",\n"
+    "      \"label\": \"unwind\",\n"
     "      \"deps\": [\n"
-    "        {\n"
-    "          \"crate\": 2,\n"
-    "          \"name\": \"core\"\n"
-    "        }\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libstd/lib.rs\",\n"
+    "      \"label\": \"std\",\n"
     "      \"deps\": [\n"
     "        {\n"
-    "          \"crate\": 3,\n"
+    "          \"crate\": 1,\n"
     "          \"name\": \"alloc\"\n"
     "        },\n"
     "        {\n"
-    "          \"crate\": 2,\n"
+    "          \"crate\": 0,\n"
     "          \"name\": \"core\"\n"
     "        },\n"
     "        {\n"
-    "          \"crate\": 4,\n"
+    "          \"crate\": 2,\n"
     "          \"name\": \"panic_abort\"\n"
     "        },\n"
     "        {\n"
-    "          \"crate\": 5,\n"
+    "          \"crate\": 3,\n"
     "          \"name\": \"unwind\"\n"
     "        }\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 5,\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcollections/lib.rs\",\n"
+    "      \"label\": \"collections\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
+    "    },\n"
+    "    {\n"
+    "      \"crate_id\": 6,\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liblibc/lib.rs\",\n"
+    "      \"label\": \"libc\",\n"
+    "      \"deps\": [\n"
+    "      ],\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
     "    },\n"
     "    {\n"
     "      \"crate_id\": 7,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libcollections/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libpanic_unwind/lib.rs\",\n"
+    "      \"label\": \"panic_unwind\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
     "    },\n"
     "    {\n"
     "      \"crate_id\": 8,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/liblibc/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libproc_macro/lib.rs\",\n"
+    "      \"label\": \"proc_macro\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_unicode/lib.rs\",\n"
+    "      \"label\": \"rustc_unicode\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libstd_unicode/lib.rs\",\n"
+    "      \"label\": \"std_unicode\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libtest/lib.rs\",\n"
+    "      \"label\": \"test\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc_jemalloc/lib.rs\",\n"
+    "      \"label\": \"alloc_jemalloc\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
     "    },\n"
     "    {\n"
     "      \"crate_id\": 13,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libtest/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/liballoc_system/lib.rs\",\n"
+    "      \"label\": \"alloc_system\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libcompiler_builtins/lib.rs\",\n"
+    "      \"label\": \"compiler_builtins\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libgetopts/lib.rs\",\n"
+    "      \"label\": \"getopts\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libbuild_helper/lib.rs\",\n"
+    "      \"label\": \"build_helper\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\n"
     "    },\n"
     "    {\n"
     "      \"crate_id\": 17,\n"
-    "      \"root_module\": \"out/Debug/path/lib/rustlib/src/rust/src/libgetopts/lib.rs\",\n"
-    "      \"edition\": \"2018\",\n"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_asan/lib.rs\",\n"
+    "      \"label\": \"rustc_asan\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_lsan/lib.rs\",\n"
+    "      \"label\": \"rustc_lsan\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_msan/lib.rs\",\n"
+    "      \"label\": \"rustc_msan\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/librustc_tsan/lib.rs\",\n"
+    "      \"label\": \"rustc_tsan\",\n"
     "      \"deps\": [\n"
     "      ],\n"
-    "      \"cfg\": []\n"
+    "      \"edition\": \"2018\",\n"
+    "      \"cfg\": [\n"
+    "      ]\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"
+    "      \"root_module\": \"/root/out/Debug/sysroot/lib/rustlib/src/rust/src/libsyntax/lib.rs\",\n"
+    "      \"label\": \"syntax\",\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"
-    "    }"
+    "      \"cfg\": [\n"
+    "      ]\n"
+    "    }\n"
+    "  ]\n"
+    "}\n";
 ;
   EXPECT_EQ(expected_json, out);
 }
diff --git a/src/gn/rust_project_writer_unittest.cc b/src/gn/rust_project_writer_unittest.cc
index fb7200e..a1910e7 100644
--- a/src/gn/rust_project_writer_unittest.cc
+++ b/src/gn/rust_project_writer_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "gn/rust_project_writer.h"
 #include "base/strings/string_util.h"
+#include "base/files/file_path.h"
+#include "gn/filesystem_utils.h"
 #include "gn/substitution_list.h"
 #include "gn/target.h"
 #include "gn/test_with_scheduler.h"
@@ -16,6 +18,7 @@
 TEST_F(RustProjectJSONWriter, OneRustTarget) {
   Err err;
   TestWithScope setup;
+  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));
 
   Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
   target.set_output_type(Target::RUST_LIBRARY);
@@ -39,11 +42,13 @@
 #endif
   const char expected_json[] =
       "{\n"
-      "  \"roots\": [],\n"
+      "  \"roots\": [\n"
+      "    \"path/foo/\"\n"
+      "  ],\n"
       "  \"crates\": [\n"
       "    {\n"
       "      \"crate_id\": 0,\n"
-      "      \"root_module\": \"foo/lib.rs\",\n"
+      "      \"root_module\": \"path/foo/lib.rs\",\n"
       "      \"label\": \"//foo:bar\",\n"
       "      \"deps\": [\n"
       "      ],\n"
@@ -94,7 +99,10 @@
 #endif
   const char expected_json[] =
       "{\n"
-      "  \"roots\": [],\n"
+      "  \"roots\": [\n"
+      "    \"tortoise/\",\n"
+      "    \"hare/\"\n"
+      "  ],\n"
       "  \"crates\": [\n"
       "    {\n"
       "      \"crate_id\": 0,\n"
@@ -173,7 +181,11 @@
 #endif
   const char expected_json[] =
       "{\n"
-      "  \"roots\": [],\n"
+      "  \"roots\": [\n"
+      "    \"tortoise/\",\n"
+      "    \"achilles/\",\n"
+      "    \"hare/\"\n"
+      "  ],\n"
       "  \"crates\": [\n"
       "    {\n"
       "      \"crate_id\": 0,\n"
@@ -280,7 +292,11 @@
 #endif
   const char expected_json[] =
       "{\n"
-      "  \"roots\": [],\n"
+      "  \"roots\": [\n"
+      "    \"tortoise/\",\n"
+      "    \"tortoise/macro/\",\n"
+      "    \"hare/\"\n"
+      "  ],\n"
       "  \"crates\": [\n"
       "    {\n"
       "      \"crate_id\": 0,\n"