Provide a way to pass extern Rust library We can use `libs` and `lib_dirs` to pass external libraries to C/C++ targets (and even C/C++ dependencies of Rust targets), but the same mechanism cannot be used to pass external Rust libraries to Rust targets because --externs name=path/to/lib.rlib needs both a name and a path. This change implements `externs` which behaves similarly to `libs` but takes a scope instead and allows passing name/path pairs which are passed to the Rust compiler. Change-Id: Iaee0bf462dcbaecc0171cd124a23492451ddf6f1 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6960 Commit-Queue: Petr Hosek <phosek@google.com> Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md index 90cc029..295f0ca 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -115,6 +115,7 @@ * [defines: [string list] C preprocessor defines.](#var_defines) * [depfile: [string] File name for input dependencies for actions.](#var_depfile) * [deps: [label list] Private linked dependencies.](#var_deps) + * [externs: [scope] Set of Rust crate-dependency pairs.](#var_externs) * [friend: [label pattern list] Allow targets to include private headers.](#var_friend) * [include_dirs: [directory list] Additional include directories.](#var_include_dirs) * [inputs: [file list] Additional compile-time dependencies.](#var_inputs) @@ -5178,6 +5179,30 @@ See also "public_deps". ``` +### <a name="var_externs"></a>**externs**: [scope] Set of Rust crate-dependency pairs. + +``` + A list, each value being a scope indicating a pair of crate name and the path + to the Rust library. + + These libraries will be passed as `--extern crate_name=path` to compiler + invocation containing the current target. +``` + +#### **Examples** + +``` + executable("foo") { + sources = [ "main.rs" ] + externs = [{ + crate_name = "bar", + path = "path/to/bar.rlib" + }] + } + + This target would compile the `foo` crate with the following `extern` flag: + `--extern bar=path/to/bar.rlib`. +``` ### <a name="var_friend"></a>**friend**: Allow targets to include private headers. ```
diff --git a/src/gn/config_values.h b/src/gn/config_values.h index 003159b..0b4ca18 100644 --- a/src/gn/config_values.h +++ b/src/gn/config_values.h
@@ -61,6 +61,11 @@ const std::vector<LibFile>& libs() const { return libs_; } std::vector<LibFile>& libs() { return libs_; } + const std::vector<std::pair<std::string, LibFile>>& externs() const { + return externs_; + } + std::vector<std::pair<std::string, LibFile>>& externs() { return externs_; } + bool has_precompiled_headers() const { return !precompiled_header_.empty() || !precompiled_source_.is_null(); } @@ -85,6 +90,7 @@ std::vector<LibFile> libs_; std::vector<std::string> rustflags_; std::vector<std::string> rustenv_; + std::vector<std::pair<std::string, LibFile>> externs_; // If you add a new one, be sure to update AppendValues(). std::string precompiled_header_;
diff --git a/src/gn/config_values_generator.cc b/src/gn/config_values_generator.cc index e6c7c36..6e0df36 100644 --- a/src/gn/config_values_generator.cc +++ b/src/gn/config_values_generator.cc
@@ -89,12 +89,19 @@ } // Libs - const Value* libs_value = scope_->GetValue("libs", true); + const Value* libs_value = scope_->GetValue(variables::kLibs, true); if (libs_value) { ExtractListOfLibs(scope_->settings()->build_settings(), *libs_value, input_dir_, &config_values_->libs(), err_); } + // Externs + const Value* externs_value = scope_->GetValue(variables::kExterns, true); + if (externs_value) { + ExtractListOfExterns(scope_->settings()->build_settings(), *externs_value, + input_dir_, &config_values_->externs(), err_); + } + // Precompiled headers. const Value* precompiled_header_value = scope_->GetValue(variables::kPrecompiledHeader, true);
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc index 8e84d38..5cd9774 100644 --- a/src/gn/desc_builder.cc +++ b/src/gn/desc_builder.cc
@@ -472,6 +472,17 @@ // Libs and lib_dirs are handled specially below. + if (what(variables::kExterns)) { + base::DictionaryValue externs; + for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) { + const ConfigValues& cur = iter.cur(); + for (const auto& e : cur.externs()) { + externs.SetKey(e.first, base::Value(e.second.value())); + } + } + res->SetKey(variables::kExterns, std::move(externs)); + } + FillInPrecompiledHeader(res.get(), target_->config_values()); }
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc index d15fa48..729bfc7 100644 --- a/src/gn/ninja_rust_binary_target_writer.cc +++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -153,6 +153,17 @@ deps.push_back(linkable_dep->dependency_output_file()); } + // Rust libraries specified by paths. + for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) { + const ConfigValues& cur = iter.cur(); + for (const auto& e : cur.externs()) { + if (e.second.is_source_file()) { + deps.push_back(OutputFile(settings_->build_settings(), + e.second.source_file())); + } + } + } + std::vector<OutputFile> tool_outputs; SubstitutionWriter::ApplyListToLinkerAsOutputFile( target_, tool_, tool_->outputs(), &tool_outputs); @@ -186,29 +197,38 @@ void NinjaRustBinaryTargetWriter::WriteExterns( const std::vector<const Target*>& deps) { - std::vector<const Target*> externs; + out_ << " externs ="; + for (const Target* target : deps) { if (target->output_type() == Target::RUST_LIBRARY || target->output_type() == Target::RUST_PROC_MACRO) { - externs.push_back(target); + out_ << " --extern "; + const auto& renamed_dep = + target_->rust_values().aliased_deps().find(target->label()); + if (renamed_dep != target_->rust_values().aliased_deps().end()) { + out_ << renamed_dep->second << "="; + } else { + out_ << std::string(target->rust_values().crate_name()) << "="; + } + path_output_.WriteFile(out_, target->dependency_output_file()); } } - if (externs.empty()) - return; - out_ << " externs ="; - for (const Target* ex : externs) { - out_ << " --extern "; - const auto& renamed_dep = - target_->rust_values().aliased_deps().find(ex->label()); - if (renamed_dep != target_->rust_values().aliased_deps().end()) { - out_ << renamed_dep->second << "="; - } else { - out_ << std::string(ex->rust_values().crate_name()) << "="; + EscapeOptions extern_escape_opts; + extern_escape_opts.mode = ESCAPE_NINJA_COMMAND; + + for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) { + const ConfigValues& cur = iter.cur(); + for (const auto& e : cur.externs()) { + out_ << " --extern " << std::string(e.first) << "="; + if (e.second.is_source_file()) { + path_output_.WriteFile(out_, e.second.source_file()); + } else { + EscapeStringToStream(out_, e.second.value(), extern_escape_opts); + } } - - path_output_.WriteFile(out_, ex->dependency_output_file()); } + out_ << std::endl; }
diff --git a/src/gn/ninja_rust_binary_target_writer_unittest.cc b/src/gn/ninja_rust_binary_target_writer_unittest.cc index 781ee7b..d581b21 100644 --- a/src/gn/ninja_rust_binary_target_writer_unittest.cc +++ b/src/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -91,6 +91,7 @@ "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input3.rs " "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || " "obj/foo/sources.stamp\n" + " externs =\n" " rustdeps =\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -131,6 +132,7 @@ "\n" "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs\n" + " externs =\n" " rustdeps =\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -335,6 +337,7 @@ "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " "../../foo/main.rs obj/foo/libstatic.a\n" + " externs =\n" " rustdeps = -Lnative=obj/foo -lstatic\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -388,6 +391,7 @@ "build ./foo_bar.exe: rust_bin ../../foo/main.rs | ../../foo/input3.rs " "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || " "obj/foo/sources.stamp\n" + " externs =\n" " rustdeps =\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -431,6 +435,7 @@ "\n" "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/input.rs " "../../foo/main.rs\n" + " externs =\n" " rustdeps = -Lnative=../../baz -lquux\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -472,6 +477,7 @@ "\n" "build obj/bar/libmymacro.so: rust_macro ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs\n" + " externs =\n" " rustdeps =\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -549,6 +555,7 @@ "\n" "build obj/bar/libmylib.rlib: rust_rlib ../../bar/lib.rs | " "../../bar/mylib.rs ../../bar/lib.rs\n" + " externs =\n" " rustdeps =\n"; std::string out_str = out.str(); EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; @@ -598,3 +605,48 @@ EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; } } + +TEST_F(NinjaRustBinaryTargetWriterTest, Externs) { + Err err; + TestWithScope setup; + + Target target(setup.settings(), Label(SourceDir("//foo/"), "bar")); + target.set_output_type(Target::EXECUTABLE); + target.visibility().SetPublic(); + SourceFile main("//foo/main.rs"); + target.sources().push_back(SourceFile("//foo/source.rs")); + target.sources().push_back(main); + target.source_types_used().Set(SourceFile::SOURCE_RS); + target.rust_values().set_crate_root(main); + target.rust_values().crate_name() = "foo_bar"; + target.config_values().externs().push_back( + std::pair("lib1", LibFile(SourceFile("//foo/lib1.rlib")))); + target.config_values().externs().push_back( + std::pair("lib2", LibFile("lib2.rlib"))); + target.SetToolchain(setup.toolchain()); + ASSERT_TRUE(target.OnResolved(&err)); + + { + std::ostringstream out; + NinjaRustBinaryTargetWriter writer(&target, out); + writer.Run(); + + const char expected[] = + "crate_name = foo_bar\n" + "crate_type = bin\n" + "output_extension = \n" + "output_dir = \n" + "rustflags =\n" + "rustenv =\n" + "root_out_dir = .\n" + "target_out_dir = obj/foo\n" + "target_output_name = bar\n" + "\n" + "build ./foo_bar: rust_bin ../../foo/main.rs | ../../foo/source.rs " + "../../foo/main.rs ../../foo/lib1.rlib\n" + " externs = --extern lib1=../../foo/lib1.rlib --extern lib2=lib2.rlib\n" + " rustdeps =\n"; + std::string out_str = out.str(); + EXPECT_EQ(expected, out_str) << expected << "\n" << out_str; + } +}
diff --git a/src/gn/value_extractors.cc b/src/gn/value_extractors.cc index 5023a59..baf116c 100644 --- a/src/gn/value_extractors.cc +++ b/src/gn/value_extractors.cc
@@ -105,6 +105,44 @@ const SourceDir& current_dir; }; +struct ExternConverter { + ExternConverter(const BuildSettings* build_settings_in, + const SourceDir& current_dir_in) + : build_settings(build_settings_in), current_dir(current_dir_in) {} + bool operator()(const Value& v, std::pair<std::string, LibFile>* out, + Err* err) const { + if (!v.VerifyTypeIs(Value::SCOPE, err)) + return false; + Scope::KeyValueMap scope; + v.scope_value()->GetCurrentScopeValues(&scope); + std::string cratename; + if (auto it = scope.find("crate_name"); it != scope.end()) { + if (!it->second.VerifyTypeIs(Value::STRING, err)) + return false; + cratename = it->second.string_value(); + } else { + return false; + } + LibFile path; + if (auto it = scope.find("path"); it != scope.end()) { + if (!it->second.VerifyTypeIs(Value::STRING, err)) + return false; + if (it->second.string_value().find('/') == std::string::npos) { + path = LibFile(it->second.string_value()); + } else { + path = LibFile(current_dir.ResolveRelativeFile( + it->second, err, build_settings->root_path_utf8())); + } + } else { + return false; + } + *out = std::pair(cratename, path); + return !err->has_error(); + } + const BuildSettings* build_settings; + const SourceDir& current_dir; +}; + // Fills in a label. template <typename T> struct LabelResolver { @@ -247,3 +285,12 @@ return ListValueExtractor(value, patterns, err, LabelPatternResolver(current_dir)); } + +bool ExtractListOfExterns(const BuildSettings* build_settings, + const Value& value, + const SourceDir& current_dir, + std::vector<std::pair<std::string, LibFile>>* externs, + Err* err) { + return ListValueExtractor(value, externs, err, + ExternConverter(build_settings, current_dir)); +}
diff --git a/src/gn/value_extractors.h b/src/gn/value_extractors.h index dd07471..0518147 100644 --- a/src/gn/value_extractors.h +++ b/src/gn/value_extractors.h
@@ -86,4 +86,10 @@ std::vector<LabelPattern>* patterns, Err* err); +bool ExtractListOfExterns(const BuildSettings* build_settings, + const Value& value, + const SourceDir& current_dir, + std::vector<std::pair<std::string, LibFile>>* externs, + Err* err); + #endif // TOOLS_GN_VALUE_EXTRACTORS_H_
diff --git a/src/gn/variables.cc b/src/gn/variables.cc index 104c290..f6ce4dc 100644 --- a/src/gn/variables.cc +++ b/src/gn/variables.cc
@@ -1095,6 +1095,32 @@ See also "public_deps". )"; +const char kExterns[] = "externs"; +const char kExterns_HelpShort[] = + "externs: [scope] Set of Rust crate-dependency pairs."; +const char kExterns_Help[] = + R"(externs: [scope] Set of Rust crate-dependency pairs. + + A list, each value being a scope indicating a pair of crate name and the path + to the Rust library. + + These libraries will be passed as `--extern crate_name=path` to compiler + invocation containing the current target. + +Examples + + executable("foo") { + sources = [ "main.rs" ] + externs = [{ + crate_name = "bar", + path = "path/to/bar.rlib" + }] + } + + This target would compile the `foo` crate with the following `extern` flag: + `--extern bar=path/to/bar.rlib`. +)"; + const char kFriend[] = "friend"; const char kFriend_HelpShort[] = "friend: [label pattern list] Allow targets to include private headers."; @@ -2176,6 +2202,7 @@ INSERT_VARIABLE(Defines) INSERT_VARIABLE(Depfile) INSERT_VARIABLE(Deps) + INSERT_VARIABLE(Externs) INSERT_VARIABLE(Friend) INSERT_VARIABLE(IncludeDirs) INSERT_VARIABLE(Inputs)
diff --git a/src/gn/variables.h b/src/gn/variables.h index 0de220d..93a97a6 100644 --- a/src/gn/variables.h +++ b/src/gn/variables.h
@@ -194,6 +194,10 @@ extern const char kDeps_HelpShort[]; extern const char kDeps_Help[]; +extern const char kExterns[]; +extern const char kExterns_HelpShort[]; +extern const char kExterns_Help[]; + extern const char kFriend[]; extern const char kFriend_HelpShort[]; extern const char kFriend_Help[];