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[];