Rust compilation tool and toolchain support
Adds the `rustc` tool and support to the toolchain, and also adds
support for that new tool into the testing infrastructure.
Change-Id: I47cae7ce3909ef197a3db81cb2f55ef3ee76055d
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4883
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/build/gen.py b/build/gen.py
index 00752ee..3c7c858 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -516,6 +516,7 @@
'tools/gn/qt_creator_writer.cc',
'tools/gn/runtime_deps.cc',
'tools/gn/rust_substitution_type.cc',
+ 'tools/gn/rust_tool.cc',
'tools/gn/rust_values.cc',
'tools/gn/rust_variables.cc',
'tools/gn/scheduler.cc',
diff --git a/tools/gn/function_toolchain.cc b/tools/gn/function_toolchain.cc
index 3d2a100..e5ee5bc 100644
--- a/tools/gn/function_toolchain.cc
+++ b/tools/gn/function_toolchain.cc
@@ -299,6 +299,9 @@
"copy_bundle_data": [iOS, macOS] Tool to copy files in a bundle.
"compile_xcassets": [iOS, macOS] Tool to compile asset catalogs.
+ Rust tools:
+ "rustc": Rust compiler and linker
+
Tool variables
command [string with substitutions]
@@ -359,6 +362,20 @@
Example: description = "Compiling {{source}}"
+ exe_output_extension [string, optional, rust tools only]
+ rlib_output_extension [string, optional, rust tools only]
+ dylib_output_extension [string, optional, rust tools only]
+ cdylib_output_extension [string, optional, rust tools only]
+ proc_macro_output_extension [string, optional, rust tools only]
+ Valid for: Rust tools
+
+ These specify the default tool output for each of the crate types.
+ The default is empty for executables, shared, and static libraries and
+ ".rlib" for rlibs. Note that the Rust compiler complains with an error
+ if external crates do not take the form `lib<name>.rlib` or
+ `lib<name>.<shared_extension>`, where `<shared_extension>` is `.so`,
+ `.dylib`, or `.dll` as appropriate for the platform.
+
lib_switch [string, optional, link tools only]
lib_dir_switch [string, optional, link tools only]
Valid for: Linker tools except "alink"
@@ -500,7 +517,8 @@
added to runtime deps (see "gn help runtime_deps"). By default (if
runtime_outputs is empty or unspecified), it will be the link_output.
-Expansions for tool variables
+)" // String break to prevent overflowing the 16K max VC string length.
+ R"(Expansions for tool variables
All paths are relative to the root build directory, which is the current
directory for running all tools. These expansions are available to all tools:
@@ -536,8 +554,7 @@
Example: "libfoo" for the target named "foo" and an output prefix for
the linker tool of "lib".
-)" // String break to prevent overflowing the 16K max VC string length.
- R"( Compiler tools have the notion of a single input and a single output, along
+ Compiler tools have the notion of a single input and a single output, along
with a set of compiler-specific flags. The following expansions are
available:
@@ -673,6 +690,39 @@
assets catalog compiler. Usually based on the target_name of
the create_bundle target.
+ Rust tools have the notion of a single input and a single output, along
+ with a set of compiler-specific flags. The following expansions are
+ available:
+
+ {{crate_name}}
+ Expands to the string representing the crate name of target under
+ compilation.
+
+ {{crate_type}}
+ Expands to the string representing the type of crate for the target
+ under compilation.
+
+ {{externs}}
+ Expands to the list of --extern flags needed to include addition Rust
+ libraries in this target. Includes any specified renamed dependencies.
+
+ {{rustc_output_extension}}
+ Expands to the output extension for this target's crate type.
+
+ {{rustc_output_prefix}}
+ Expands to the prefix for shared and static libraries. This should
+ generally be "lib". Empty for executable targets.
+
+ {{rustdeps}}
+ Expands to the list of -Ldependency=<path> strings needed to compile
+ this target.
+
+ {{rustenv}}
+ Expands to the list of environment variables.
+
+ {{rustflags}}
+ Expands to the list of strings representing Rust compiler flags.
+
Separate linking and dependencies for shared libraries
Shared libraries are special in that not all changes to them require that
diff --git a/tools/gn/function_toolchain_unittest.cc b/tools/gn/function_toolchain_unittest.cc
index e3aa598..ef301e7 100644
--- a/tools/gn/function_toolchain_unittest.cc
+++ b/tools/gn/function_toolchain_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "tools/gn/functions.h"
+#include "tools/gn/rust_tool.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/test_with_scheduler.h"
#include "tools/gn/test_with_scope.h"
@@ -58,3 +59,35 @@
ASSERT_TRUE(err.has_error()) << err.message();
}
}
+
+TEST_F(FunctionToolchain, Rust) {
+ TestWithScope setup;
+
+ // These runtime outputs are a subset of the outputs so are OK.
+ {
+ TestParseInput input(
+ R"(toolchain("rust") {
+ tool("rustc") {
+ command = "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin {{rustflags}} -o {{output}} {{externs}} {{source}}"
+ description = "RUST {{output}}"
+ }
+ })");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ // It should have generated a toolchain.
+ ASSERT_EQ(1u, setup.items().size());
+ const Toolchain* toolchain = setup.items()[0]->AsToolchain();
+ ASSERT_TRUE(toolchain);
+
+ const Tool* rust = toolchain->GetTool(RustTool::kRsToolRustc);
+ ASSERT_TRUE(rust);
+ ASSERT_EQ(rust->command().AsString(),
+ "{{rustenv}} rustc --crate-name {{crate_name}} --crate-type bin "
+ "{{rustflags}} -o {{output}} {{externs}} {{source}}");
+ ASSERT_EQ(rust->description().AsString(), "RUST {{output}}");
+ }
+}
\ No newline at end of file
diff --git a/tools/gn/rust_tool.cc b/tools/gn/rust_tool.cc
new file mode 100644
index 0000000..e4e127d
--- /dev/null
+++ b/tools/gn/rust_tool.cc
@@ -0,0 +1,156 @@
+// Copyright 2019 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 "tools/gn/rust_tool.h"
+
+#include "tools/gn/rust_substitution_type.h"
+#include "tools/gn/target.h"
+
+const char* RustTool::kRsToolRustc = "rustc";
+
+RustTool::RustTool(const char* n) : Tool(n), rlib_output_extension_(".rlib") {
+ CHECK(ValidateName(n));
+}
+
+RustTool::~RustTool() = default;
+
+RustTool* RustTool::AsRust() {
+ return this;
+}
+const RustTool* RustTool::AsRust() const {
+ return this;
+}
+
+bool RustTool::ValidateName(const char* name) const {
+ return name_ == kRsToolRustc;
+}
+
+void RustTool::SetComplete() {
+ SetToolComplete();
+}
+
+bool RustTool::SetOutputExtension(const Value* value,
+ std::string* var,
+ Err* err) {
+ DCHECK(!complete_);
+ if (!value)
+ return true; // Not present is fine.
+ if (!value->VerifyTypeIs(Value::STRING, err))
+ return false;
+ if (value->string_value().empty())
+ return true;
+
+ *var = std::move(value->string_value());
+ return true;
+}
+
+bool RustTool::ReadOutputExtensions(Scope* scope, Err* err) {
+ if (!SetOutputExtension(scope->GetValue("exe_output_extension", true),
+ &exe_output_extension_, err))
+ return false;
+ if (!SetOutputExtension(scope->GetValue("rlib_output_extension", true),
+ &rlib_output_extension_, err))
+ return false;
+ if (!SetOutputExtension(scope->GetValue("dylib_output_extension", true),
+ &dylib_output_extension_, err))
+ return false;
+ if (!SetOutputExtension(scope->GetValue("cdylib_output_extension", true),
+ &cdylib_output_extension_, err))
+ return false;
+ if (!SetOutputExtension(scope->GetValue("staticlib_output_extension", true),
+ &staticlib_output_extension_, err))
+ return false;
+ if (!SetOutputExtension(scope->GetValue("proc_macro_output_extension", true),
+ &proc_macro_output_extension_, err))
+ return false;
+ return true;
+}
+
+bool RustTool::ReadOutputsPatternList(Scope* scope,
+ const char* var,
+ SubstitutionList* field,
+ Err* err) {
+ DCHECK(!complete_);
+ const Value* value = scope->GetValue(var, true);
+ if (!value)
+ return true; // Not present is fine.
+ if (!value->VerifyTypeIs(Value::LIST, err))
+ return false;
+
+ SubstitutionList list;
+ if (!list.Parse(*value, err))
+ return false;
+
+ // Validate the right kinds of patterns are used.
+ if (list.list().empty()) {
+ *err = Err(defined_from(), "\"outputs\" must be specified for this tool.");
+ return false;
+ }
+
+ for (const auto& cur_type : list.required_types()) {
+ if (!IsValidRustSubstitution(cur_type)) {
+ *err = Err(*value, "Pattern not valid here.",
+ "You used the pattern " + std::string(cur_type->name) +
+ " which is not valid\nfor this variable.");
+ return false;
+ }
+ }
+
+ *field = std::move(list);
+ return true;
+}
+
+bool RustTool::InitTool(Scope* scope, Toolchain* toolchain, Err* err) {
+ // Initialize default vars.
+ if (!Tool::InitTool(scope, toolchain, err)) {
+ return false;
+ }
+
+ if (!ReadOutputExtensions(scope, err)) {
+ return false;
+ }
+
+ // All Rust tools should have outputs.
+ if (!ReadOutputsPatternList(scope, "outputs", &outputs_, err)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool RustTool::ValidateSubstitution(const Substitution* sub_type) const {
+ if (name_ == kRsToolRustc)
+ return IsValidRustSubstitution(sub_type);
+ NOTREACHED();
+ return false;
+}
+
+const std::string& RustTool::rustc_output_extension(
+ Target::OutputType type,
+ const RustValues::CrateType crate_type) const {
+ switch (crate_type) {
+ case RustValues::CRATE_AUTO: {
+ switch (type) {
+ case Target::EXECUTABLE:
+ return exe_output_extension_;
+ case Target::STATIC_LIBRARY:
+ return staticlib_output_extension_;
+ case Target::RUST_LIBRARY:
+ return rlib_output_extension_;
+ default:
+ NOTREACHED();
+ return exe_output_extension_;
+ }
+ }
+ case RustValues::CRATE_DYLIB:
+ return dylib_output_extension_;
+ case RustValues::CRATE_CDYLIB:
+ return cdylib_output_extension_;
+ case RustValues::CRATE_PROC_MACRO:
+ return proc_macro_output_extension_;
+ default:
+ NOTREACHED();
+ return exe_output_extension_;
+ }
+}
diff --git a/tools/gn/rust_tool.h b/tools/gn/rust_tool.h
new file mode 100644
index 0000000..682f74a
--- /dev/null
+++ b/tools/gn/rust_tool.h
@@ -0,0 +1,98 @@
+// Copyright 2019 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_TOOL_H_
+#define TOOLS_GN_RUST_TOOL_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "tools/gn/label.h"
+#include "tools/gn/label_ptr.h"
+#include "tools/gn/rust_values.h"
+#include "tools/gn/source_file.h"
+#include "tools/gn/substitution_list.h"
+#include "tools/gn/substitution_pattern.h"
+#include "tools/gn/target.h"
+#include "tools/gn/tool.h"
+
+class RustTool : public Tool {
+ public:
+ // Rust tools
+ static const char* kRsToolRustc;
+
+ explicit RustTool(const char* n);
+ ~RustTool();
+
+ // Manual RTTI and required functions ---------------------------------------
+
+ bool InitTool(Scope* block_scope, Toolchain* toolchain, Err* err);
+ bool ValidateName(const char* name) const override;
+ void SetComplete() override;
+ bool ValidateSubstitution(const Substitution* sub_type) const override;
+
+ RustTool* AsRust() override;
+ const RustTool* AsRust() const override;
+
+ void set_exe_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ exe_output_extension_ = std::move(ext);
+ }
+
+ void set_rlib_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ rlib_output_extension_ = std::move(ext);
+ }
+
+ void set_dylib_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ dylib_output_extension_ = std::move(ext);
+ }
+
+ void set_cdylib_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ cdylib_output_extension_ = std::move(ext);
+ }
+
+ void set_staticlib_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ staticlib_output_extension_ = std::move(ext);
+ }
+
+ void set_proc_macro_output_extension(std::string ext) {
+ DCHECK(!complete_);
+ DCHECK(ext.empty() || ext[0] == '.');
+ proc_macro_output_extension_ = std::move(ext);
+ }
+
+ // Will include a leading "." if nonempty.
+ const std::string& rustc_output_extension(
+ Target::OutputType type,
+ const RustValues::CrateType crate_type) const;
+
+ private:
+ bool SetOutputExtension(const Value* value, std::string* var, Err* err);
+ bool ReadOutputExtensions(Scope* scope, Err* err);
+ bool ReadOutputsPatternList(Scope* scope,
+ const char* var,
+ SubstitutionList* field,
+ Err* err);
+
+ std::string exe_output_extension_;
+ std::string rlib_output_extension_;
+ std::string dylib_output_extension_;
+ std::string cdylib_output_extension_;
+ std::string staticlib_output_extension_;
+ std::string proc_macro_output_extension_;
+
+ DISALLOW_COPY_AND_ASSIGN(RustTool);
+};
+
+#endif // TOOLS_GN_RUST_TOOL_H_
diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc
index 9dfb6c4..4ebbbfa 100644
--- a/tools/gn/test_with_scope.cc
+++ b/tools/gn/test_with_scope.cc
@@ -194,6 +194,24 @@
SetCommandForTool("touch {{output}}", compile_xcassets_tool.get());
toolchain->SetTool(std::move(compile_xcassets_tool));
+ // RUST
+ std::unique_ptr<Tool> rustc_tool = Tool::CreateTool(RustTool::kRsToolRustc);
+ SetCommandForTool(
+ "{{rustenv}} rustc --edition=2018 --crate-name {{crate_name}} {{source}} "
+ "--crate-type {{crate_type}} {{rustflags}} -o "
+ "{{target_out_dir}}/"
+ "{{rustc_output_prefix}}{{crate_name}}{{rustc_output_extension}} "
+ "{{rustdeps}} {{externs}}",
+ rustc_tool.get());
+ rustc_tool->AsRust()->set_dylib_output_extension(".so");
+ rustc_tool->AsRust()->set_cdylib_output_extension(".so");
+ rustc_tool->AsRust()->set_staticlib_output_extension(".a");
+ rustc_tool->AsRust()->set_proc_macro_output_extension(".so");
+ rustc_tool->AsRust()->set_outputs(SubstitutionList::MakeForTest(
+ "{{target_out_dir}}/"
+ "{{rustc_output_prefix}}{{crate_name}}{{rustc_output_extension}}"));
+ toolchain->SetTool(std::move(rustc_tool));
+
toolchain->ToolchainSetupComplete();
}
diff --git a/tools/gn/test_with_scope.h b/tools/gn/test_with_scope.h
index 7950259..897ef1b 100644
--- a/tools/gn/test_with_scope.h
+++ b/tools/gn/test_with_scope.h
@@ -15,6 +15,7 @@
#include "tools/gn/general_tool.h"
#include "tools/gn/input_file.h"
#include "tools/gn/parse_tree.h"
+#include "tools/gn/rust_tool.h"
#include "tools/gn/scope.h"
#include "tools/gn/scope_per_file_provider.h"
#include "tools/gn/settings.h"
diff --git a/tools/gn/tool.cc b/tools/gn/tool.cc
index b040ac2..72bce14 100644
--- a/tools/gn/tool.cc
+++ b/tools/gn/tool.cc
@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "tools/gn/tool.h"
+
#include "tools/gn/c_tool.h"
#include "tools/gn/general_tool.h"
+#include "tools/gn/rust_tool.h"
#include "tools/gn/target.h"
const char* Tool::kToolNone = "";
@@ -42,6 +44,13 @@
return nullptr;
}
+RustTool* Tool::AsRust() {
+ return nullptr;
+}
+const RustTool* Tool::AsRust() const {
+ return nullptr;
+}
+
bool Tool::IsPatternInOutputList(const SubstitutionList& output_list,
const SubstitutionPattern& pattern) const {
for (const auto& cur : output_list.list()) {
@@ -216,6 +225,11 @@
return tool;
return nullptr;
}
+ if (RustTool* rust_tool = tool->AsRust()) {
+ if (rust_tool->InitTool(scope, toolchain, err))
+ return tool;
+ return nullptr;
+ }
NOTREACHED();
*err = Err(function, "Unknown tool type.");
return nullptr;
@@ -223,6 +237,7 @@
// static
std::unique_ptr<Tool> Tool::CreateTool(const std::string& name) {
+ // C tools
if (name == CTool::kCToolCc)
return std::make_unique<CTool>(CTool::kCToolCc);
else if (name == CTool::kCToolCxx)
@@ -244,6 +259,7 @@
else if (name == CTool::kCToolLink)
return std::make_unique<CTool>(CTool::kCToolLink);
+ // General tools
else if (name == GeneralTool::kGeneralToolAction)
return std::make_unique<GeneralTool>(GeneralTool::kGeneralToolAction);
else if (name == GeneralTool::kGeneralToolStamp)
@@ -257,6 +273,10 @@
return std::make_unique<GeneralTool>(
GeneralTool::kGeneralToolCompileXCAssets);
+ // Rust tool
+ else if (name == RustTool::kRsToolRustc)
+ return std::make_unique<RustTool>(RustTool::kRsToolRustc);
+
return nullptr;
}
@@ -276,12 +296,13 @@
return CTool::kCToolAsm;
case SourceFile::SOURCE_RC:
return CTool::kCToolRc;
+ case SourceFile::SOURCE_RS:
+ return RustTool::kRsToolRustc;
case SourceFile::SOURCE_UNKNOWN:
case SourceFile::SOURCE_H:
case SourceFile::SOURCE_O:
case SourceFile::SOURCE_DEF:
case SourceFile::SOURCE_GO:
- case SourceFile::SOURCE_RS:
return kToolNone;
default:
NOTREACHED();
@@ -294,6 +315,8 @@
// The contents of this list might be suprising (i.e. stamp tool for copy
// rules). See the header for why.
// TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
+ if (target->source_types_used().RustSourceUsed())
+ return RustTool::kRsToolRustc;
switch (target->output_type()) {
case Target::GROUP:
return GeneralTool::kGeneralToolStamp;
diff --git a/tools/gn/tool.h b/tools/gn/tool.h
index a6f177d..f6bf515 100644
--- a/tools/gn/tool.h
+++ b/tools/gn/tool.h
@@ -23,6 +23,7 @@
class CTool;
class GeneralTool;
+class RustTool;
// To add a new Tool category, create a subclass implementing SetComplete()
// Add a new category to ToolCategories
@@ -60,6 +61,8 @@
virtual const CTool* AsC() const;
virtual GeneralTool* AsGeneral();
virtual const GeneralTool* AsGeneral() const;
+ virtual RustTool* AsRust();
+ virtual const RustTool* AsRust() const;
// Basic information ---------------------------------------------------------
diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc
index 0d6cbaf..906068d 100644
--- a/tools/gn/toolchain.cc
+++ b/tools/gn/toolchain.cc
@@ -73,6 +73,20 @@
return nullptr;
}
+RustTool* Toolchain::GetToolAsRust(const char* name) {
+ if (Tool* tool = GetTool(name)) {
+ return tool->AsRust();
+ }
+ return nullptr;
+}
+
+const RustTool* Toolchain::GetToolAsRust(const char* name) const {
+ if (const Tool* tool = GetTool(name)) {
+ return tool->AsRust();
+ }
+ return nullptr;
+}
+
void Toolchain::SetTool(std::unique_ptr<Tool> t) {
DCHECK(t->name() != Tool::kToolNone);
DCHECK(tools_.find(t->name()) == tools_.end());
@@ -101,6 +115,11 @@
return GetToolAsGeneral(Tool::GetToolTypeForSourceType(type));
}
+const RustTool* Toolchain::GetToolForSourceTypeAsRust(
+ SourceFile::Type type) const {
+ return GetToolAsRust(Tool::GetToolTypeForSourceType(type));
+}
+
const Tool* Toolchain::GetToolForTargetFinalOutput(const Target* target) const {
return GetTool(Tool::GetToolTypeForTargetFinalOutput(target));
}
@@ -114,3 +133,8 @@
const Target* target) const {
return GetToolAsGeneral(Tool::GetToolTypeForTargetFinalOutput(target));
}
+
+const RustTool* Toolchain::GetToolForTargetFinalOutputAsRust(
+ const Target* target) const {
+ return GetToolAsRust(Tool::GetToolTypeForTargetFinalOutput(target));
+}
diff --git a/tools/gn/toolchain.h b/tools/gn/toolchain.h
index cc93de5..afab7f3 100644
--- a/tools/gn/toolchain.h
+++ b/tools/gn/toolchain.h
@@ -60,6 +60,8 @@
const GeneralTool* GetToolAsGeneral(const char* name) const;
CTool* GetToolAsC(const char* name);
const CTool* GetToolAsC(const char* name) const;
+ RustTool* GetToolAsRust(const char* name);
+ const RustTool* GetToolAsRust(const char* name) const;
// Set a tool. When all tools are configured, you should call
// ToolchainSetupComplete().
@@ -90,6 +92,7 @@
const Tool* GetToolForSourceType(SourceFile::Type type) const;
const CTool* GetToolForSourceTypeAsC(SourceFile::Type type) const;
const GeneralTool* GetToolForSourceTypeAsGeneral(SourceFile::Type type) const;
+ const RustTool* GetToolForSourceTypeAsRust(SourceFile::Type type) const;
// Returns the tool that produces the final output for the given target type.
// This isn't necessarily the tool you would expect. For copy target, this
@@ -99,6 +102,7 @@
const CTool* GetToolForTargetFinalOutputAsC(const Target* target) const;
const GeneralTool* GetToolForTargetFinalOutputAsGeneral(
const Target* target) const;
+ const RustTool* GetToolForTargetFinalOutputAsRust(const Target* target) const;
const SubstitutionBits& substitution_bits() const {
DCHECK(setup_complete_);