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_);