rust-project.json support
This is an experimental format for describing the Rust build graph.
It is currently used by rust-analyzer, the offical Rust LSP server.
Change-Id: I63724a3f349c1494f9d0b412a697a42b5e3cddb2
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8040
Commit-Queue: Scott Graham <scottmg@chromium.org>
Reviewed-by: Petr Hosek <phosek@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 95d9b44..44d32c8 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -471,6 +471,7 @@
'src/gn/command_refs.cc',
'src/gn/commands.cc',
'src/gn/compile_commands_writer.cc',
+ 'src/gn/rust_project_writer.cc',
'src/gn/config.cc',
'src/gn/config_values.cc',
'src/gn/config_values_extractors.cc',
@@ -628,6 +629,7 @@
'src/gn/inherited_libraries_unittest.cc',
'src/gn/input_conversion_unittest.cc',
'src/gn/json_project_writer_unittest.cc',
+ 'src/gn/rust_project_writer_unittest.cc',
'src/gn/label_pattern_unittest.cc',
'src/gn/label_unittest.cc',
'src/gn/loader_unittest.cc',
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index 8bbe60d..7c59820 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -17,6 +17,7 @@
#include "gn/ninja_writer.h"
#include "gn/qt_creator_writer.h"
#include "gn/runtime_deps.h"
+#include "gn/rust_project_writer.h"
#include "gn/scheduler.h"
#include "gn/setup.h"
#include "gn/standard_out.h"
@@ -52,6 +53,7 @@
const char kSwitchJsonIdeScript[] = "json-ide-script";
const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
const char kSwitchExportCompileCommands[] = "export-compile-commands";
+const char kSwitchExportRustProject[] = "export-rust-project";
// Extracts extra parameters for XcodeWriter from command-line flags.
XcodeWriter::Options XcodeWriterOptionsFromCommandLine(
@@ -294,6 +296,25 @@
return false;
}
+bool RunRustProjectWriter(const BuildSettings* build_settings,
+ const Builder& builder,
+ Err* err) {
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
+ bool quiet = command_line->HasSwitch(switches::kQuiet);
+ base::ElapsedTimer timer;
+
+ std::string file_name = "rust-project.json";
+ bool res = RustProjectWriter::RunAndWriteFiles(build_settings, builder,
+ file_name, quiet, err);
+ if (res && !quiet) {
+ OutputString("Generating rust-project.json took " +
+ base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+ "ms\n");
+ }
+ return res;
+}
+
bool RunCompileCommandsWriter(const BuildSettings* build_settings,
const Builder& builder,
Err* err) {
@@ -435,6 +456,12 @@
Compilation Database
+ --export-rust-project
+ Produces a rust-project.json file in the root of the build directory
+ This is used for various tools in the Rust ecosystem allowing for the
+ replay of individual compilations independent of the build system.
+ This is an unstable format and likely to change without warning.
+
--export-compile-commands[=<target_name1,target_name2...>]
Produces a compile_commands.json file in the root of the build directory
containing an array of “command objects”, where each command object
@@ -526,6 +553,12 @@
return 1;
}
+ if (command_line->HasSwitch(kSwitchExportRustProject) &&
+ !RunRustProjectWriter(&setup->build_settings(), setup->builder(), &err)) {
+ err.PrintToStdout();
+ return 1;
+ }
+
TickDelta elapsed_time = timer.Elapsed();
if (!command_line->HasSwitch(switches::kQuiet)) {
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
new file mode 100644
index 0000000..a3a1dd4
--- /dev/null
+++ b/src/gn/rust_project_writer.cc
@@ -0,0 +1,343 @@
+// 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.h"
+
+#include <fstream>
+#include <sstream>
+#include <tuple>
+
+#include "base/json/string_escape.h"
+#include "gn/builder.h"
+#include "gn/filesystem_utils.h"
+#include "gn/ninja_target_command_util.h"
+#include "gn/rust_tool.h"
+#include "gn/source_file.h"
+#include "gn/tool.h"
+
+#if defined(OS_WINDOWS)
+#define NEWLINE "\r\n"
+#else
+#define NEWLINE "\n"
+#endif
+
+// Current structure of rust-project.json output file
+//
+// {
+// "roots": [] // always empty for GN. To be deprecated.
+// "crates": [
+// {
+// "atom_cfgs": [], // atom config options
+// "deps": [
+// {
+// "crate": 1, // index into crate array
+// "name": "alloc" // extern name of dependency
+// },
+// ],
+// "edition": "2018", // edition of crate
+// "key_value_cfgs": {
+// "rust_panic": "abort" // key value config options
+// },
+// "root_module": "absolute path to crate"
+// },
+// }
+//
+
+bool RustProjectWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+ const Builder& builder,
+ const std::string& file_name,
+ bool quiet,
+ Err* err) {
+ SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
+ Value(nullptr, file_name), err);
+ if (output_file.is_null())
+ return false;
+
+ base::FilePath output_path = build_settings->GetFullPath(output_file);
+
+ std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
+
+ std::ofstream json;
+ json.open(FilePathToUTF8(output_path).c_str(),
+ std::ios_base::out | std::ios_base::binary);
+ if (json.fail())
+ return false;
+
+ RenderJSON(build_settings, all_targets, json);
+
+ return true;
+}
+
+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>>;
+
+void WriteDeps(const Target* target,
+ TargetIdxMap& lookup,
+ SysrootIdxMap& sysroot_lookup,
+ std::ostream& rust_project) {
+ bool first = 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)
+ rust_project << ",";
+ rust_project << NEWLINE << " {" NEWLINE
+ << " \"crate\": " << std::to_string(std_idx->second)
+ << "," NEWLINE << " \"name\": \"std\"" NEWLINE
+ << " }";
+ first = false;
+ }
+ }
+
+ for (const auto& dep : target->rust_values().transitive_libs().GetOrdered()) {
+ auto idx = lookup[dep];
+ if (!first)
+ rust_project << ",";
+ rust_project << NEWLINE << " {" NEWLINE
+ << " \"crate\": " << std::to_string(idx)
+ << "," NEWLINE << " \"name\": \""
+ << dep->rust_values().crate_name() << "\"" NEWLINE
+ << " }";
+ first = 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",
+ "core",
+ "alloc",
+ "collections",
+ "libc",
+ "panic_unwind",
+ "proc_macro",
+ "rustc_unicode",
+ "std_unicode",
+ "test",
+ "alloc_jemalloc",
+ "alloc_system",
+ "compiler_builtins",
+ "getopts",
+ "panic_unwind",
+ "panic_abort",
+ "unwind",
+ "build_helper",
+ "rustc_asan",
+ "rustc_lsan",
+ "rustc_msan",
+ "rustc_tsan",
+ "syntax"};
+
+const std::string_view std_deps[] = {
+ "alloc",
+ "core",
+ "panic_abort",
+ "unwind",
+};
+
+void AddSysrootCrate(const std::string_view crate,
+ const std::string_view current_sysroot,
+ uint32_t* count,
+ SysrootIdxMap& sysroot_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;
+ }
+ }
+
+ if (!first)
+ rust_project << "," NEWLINE;
+ sysroot_lookup[current_sysroot].insert(std::make_pair(crate, *count));
+
+ 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)++;
+ if (crate == "std") {
+ first = true;
+ for (auto dep : std_deps) {
+ auto idx = sysroot_lookup[current_sysroot][dep];
+ if (!first) {
+ rust_project << ",";
+ }
+ first = false;
+ rust_project << NEWLINE << " {" NEWLINE
+ << " \"crate\": " << std::to_string(idx)
+ << "," NEWLINE << " \"name\": \"" << dep
+ << "\"" NEWLINE << " }";
+ }
+ }
+ rust_project << NEWLINE " ]," NEWLINE;
+
+ rust_project << " \"atom_cfgs\": []," NEWLINE
+ " \"key_value_cfgs\": {}" NEWLINE;
+ rust_project << " }";
+}
+
+void AddTarget(const Target* target,
+ uint32_t* count,
+ TargetIdxMap& lookup,
+ SysrootIdxMap& sysroot_lookup,
+ const BuildSettings* build_settings,
+ std::ostream& rust_project,
+ bool first) {
+ 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.
+ auto rust_tool =
+ 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;
+ }
+ }
+
+ // 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;
+ }
+
+ if (!first) {
+ rust_project << "," NEWLINE;
+ }
+
+ // Construct the crate info.
+ rust_project << " {" NEWLINE;
+ rust_project << " \"crate_id\": " << std::to_string(*count)
+ << "," NEWLINE;
+
+ // Add the target to the crate lookup.
+ lookup.insert(std::make_pair(target, *count));
+ (*count)++;
+
+ base::FilePath crate_root =
+ build_settings->GetFullPath(target->rust_values().crate_root());
+
+ rust_project << " \"root_module\": \"" << FilePathToUTF8(crate_root)
+ << "\"," NEWLINE;
+
+ WriteDeps(target, lookup, sysroot_lookup, rust_project);
+
+ std::string cfg_prefix("--cfg=");
+ std::string edition_prefix("--edition=");
+ std::vector<std::string> atoms;
+ std::vector<std::tuple<std::string, std::string>> kvs;
+
+ bool edition_set = false;
+ 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;
+ }
+ // Can't directly print cfgs since they come in any order.
+ // If they have an = they are a k/v cfg, otherwise an atom cfg.
+ if (!flag.compare(0, cfg_prefix.size(), cfg_prefix)) {
+ auto cfg = flag.substr(cfg_prefix.size());
+ auto idx = cfg.rfind("=");
+ if (idx == std::string::npos) {
+ atoms.push_back(cfg);
+ } else {
+ std::string key = cfg.substr(0, idx);
+ std::string value = cfg.substr(idx + 1);
+ kvs.push_back(std::make_pair(key, value));
+ }
+ }
+ }
+ }
+
+ if (!edition_set)
+ rust_project << " \"edition\": \"2015\"," NEWLINE;
+
+ rust_project << " \"atom_cfgs\": [";
+ bool first_atom = true;
+ for (const auto& cfg : atoms) {
+ if (!first_atom) {
+ rust_project << ",";
+ }
+ first_atom = false;
+ rust_project << NEWLINE;
+ rust_project << " \"" << cfg << "\"";
+ }
+ rust_project << NEWLINE;
+ rust_project << " ]," NEWLINE;
+
+ rust_project << " \"key_value_cfgs\": {";
+ bool first_kv = true;
+ for (const auto cfg : kvs) {
+ if (!first_kv) {
+ rust_project << ",";
+ }
+ first_kv = false;
+ rust_project << NEWLINE << " \"" << std::get<0>(cfg)
+ << "\" : " << std::get<1>(cfg);
+ }
+ rust_project << NEWLINE;
+ rust_project << " }" NEWLINE;
+ rust_project << " }";
+}
+
+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;
+
+ // 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;
+ }
+
+ rust_project << NEWLINE " ]" NEWLINE;
+ rust_project << "}" NEWLINE;
+}
diff --git a/src/gn/rust_project_writer.h b/src/gn/rust_project_writer.h
new file mode 100644
index 0000000..8140e06
--- /dev/null
+++ b/src/gn/rust_project_writer.h
@@ -0,0 +1,37 @@
+// 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_H_
+#define TOOLS_GN_RUST_PROJECT_WRITER_H_
+
+#include "gn/err.h"
+#include "gn/target.h"
+
+class Builder;
+class BuildSettings;
+
+// rust-project.json is an output format describing the rust build graph. It is
+// used by rust-analyzer (a LSP server), similiar to compile-commands.json.
+//
+// an example output is in rust_project_writer.cc
+class RustProjectWriter {
+ public:
+ // Write Rust build graph into a json file located by parameter file_name.
+ //
+ // Parameter quiet is not used.
+ static bool RunAndWriteFiles(const BuildSettings* build_setting,
+ const Builder& builder,
+ const std::string& file_name,
+ bool quiet,
+ Err* err);
+ static void RenderJSON(const BuildSettings* build_settings,
+ std::vector<const Target*>& all_targets,
+ std::ostream& rust_project);
+
+ private:
+ // This fuction visits the deps graph of a target in a DFS fashion.
+ static void VisitDeps(const Target* target, std::set<const Target*>* visited);
+};
+
+#endif // TOOLS_GN_RUST_PROJECT_WRITER_H_
diff --git a/src/gn/rust_project_writer_unittest.cc b/src/gn/rust_project_writer_unittest.cc
new file mode 100644
index 0000000..1d8d153
--- /dev/null
+++ b/src/gn/rust_project_writer_unittest.cc
@@ -0,0 +1,223 @@
+// 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.h"
+#include "base/strings/string_util.h"
+#include "gn/substitution_list.h"
+#include "gn/target.h"
+#include "gn/test_with_scheduler.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using RustProjectJSONWriter = TestWithScheduler;
+
+TEST_F(RustProjectJSONWriter, OneRustTarget) {
+ Err err;
+ TestWithScope setup;
+
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::RUST_LIBRARY);
+ target.visibility().SetPublic();
+ SourceFile lib("//foo/lib.rs");
+ target.sources().push_back(lib);
+ target.source_types_used().Set(SourceFile::SOURCE_RS);
+ target.rust_values().set_crate_root(lib);
+ target.rust_values().crate_name() = "foo";
+ 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\": \"foo/lib.rs\",\n"
+ " \"deps\": [\n"
+ " ],\n"
+ " \"edition\": \"2015\",\n"
+ " \"atom_cfgs\": [\n"
+ " ],\n"
+ " \"key_value_cfgs\": {\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ EXPECT_EQ(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, RustTargetDep) {
+ 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 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.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"
+ " \"deps\": [\n"
+ " ],\n"
+ " \"edition\": \"2015\",\n"
+ " \"atom_cfgs\": [\n"
+ " ],\n"
+ " \"key_value_cfgs\": {\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"crate_id\": 1,\n"
+ " \"root_module\": \"hare/lib.rs\",\n"
+ " \"deps\": [\n"
+ " {\n"
+ " \"crate\": 0,\n"
+ " \"name\": \"tortoise\"\n"
+ " }\n"
+ " ],\n"
+ " \"edition\": \"2015\",\n"
+ " \"atom_cfgs\": [\n"
+ " ],\n"
+ " \"key_value_cfgs\": {\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ EXPECT_EQ(expected_json, out);
+}
+
+TEST_F(RustProjectJSONWriter, RustTargetDepTwo) {
+ 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::RUST_LIBRARY);
+ dep2.visibility().SetPublic();
+ SourceFile alib("//achilles/lib.rs");
+ dep2.sources().push_back(alib);
+ dep2.source_types_used().Set(SourceFile::SOURCE_RS);
+ dep2.rust_values().set_crate_root(alib);
+ dep2.rust_values().crate_name() = "achilles";
+ dep2.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(&dep2));
+ 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"
+ " \"deps\": [\n"
+ " ],\n"
+ " \"edition\": \"2015\",\n"
+ " \"atom_cfgs\": [\n"
+ " ],\n"
+ " \"key_value_cfgs\": {\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"crate_id\": 1,\n"
+ " \"root_module\": \"achilles/lib.rs\",\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"
+ " \"deps\": [\n"
+ " {\n"
+ " \"crate\": 0,\n"
+ " \"name\": \"tortoise\"\n"
+ " },\n"
+ " {\n"
+ " \"crate\": 1,\n"
+ " \"name\": \"achilles\"\n"
+ " }\n"
+ " ],\n"
+ " \"edition\": \"2015\",\n"
+ " \"atom_cfgs\": [\n"
+ " ],\n"
+ " \"key_value_cfgs\": {\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+ EXPECT_EQ(expected_json, out);
+}
diff --git a/src/gn/rust_tool.cc b/src/gn/rust_tool.cc
index 6fcb697..0b8921f 100644
--- a/src/gn/rust_tool.cc
+++ b/src/gn/rust_tool.cc
@@ -42,6 +42,10 @@
SetToolComplete();
}
+std::string_view RustTool::GetSysroot() const {
+ return rust_sysroot_;
+}
+
bool RustTool::SetOutputExtension(const Value* value,
std::string* var,
Err* err) {
@@ -101,6 +105,11 @@
if (!ReadOutputsPatternList(scope, "outputs", &outputs_, err)) {
return false;
}
+
+ // Check for a sysroot. Sets an empty string when not explicitly set.
+ if (!ReadString(scope, "rust_sysroot", &rust_sysroot_, err)) {
+ return false;
+ }
return true;
}
diff --git a/src/gn/rust_tool.h b/src/gn/rust_tool.h
index 0b3eddb..6a4fdf7 100644
--- a/src/gn/rust_tool.h
+++ b/src/gn/rust_tool.h
@@ -41,7 +41,11 @@
RustTool* AsRust() override;
const RustTool* AsRust() const override;
+ std::string_view GetSysroot() const;
+
private:
+ std::string rust_sysroot_;
+
bool SetOutputExtension(const Value* value, std::string* var, Err* err);
bool ReadOutputsPatternList(Scope* scope,
const char* var,