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