Ninja rules for Rust

Adds in support for emitting Ninja build rules for Rust targets.

If Rust source files are present, the NinjaBinaryTargetWriter will
dispatch the NinjaRustBinaryTargetWriter to produce the relevant rules.

Change-Id: If110e3820447bcf41c03473183f3b7920d19204b
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4885
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 bda1653..22b6a64 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -499,6 +499,7 @@
         'tools/gn/ninja_create_bundle_target_writer.cc',
         'tools/gn/ninja_generated_file_target_writer.cc',
         'tools/gn/ninja_group_target_writer.cc',
+        'tools/gn/ninja_rust_binary_target_writer.cc',
         'tools/gn/ninja_target_command_util.cc',
         'tools/gn/ninja_target_writer.cc',
         'tools/gn/ninja_toolchain_writer.cc',
@@ -606,6 +607,7 @@
         'tools/gn/ninja_bundle_data_target_writer_unittest.cc',
         'tools/gn/ninja_copy_target_writer_unittest.cc',
         'tools/gn/ninja_create_bundle_target_writer_unittest.cc',
+        'tools/gn/ninja_rust_binary_target_writer_unittest.cc',
         'tools/gn/ninja_generated_file_target_writer_unittest.cc',
         'tools/gn/ninja_group_target_writer_unittest.cc',
         'tools/gn/ninja_target_writer_unittest.cc',
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index c1209ed..1c0cdd2 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -4,24 +4,16 @@
 
 #include "tools/gn/ninja_binary_target_writer.h"
 
-#include <stddef.h>
-#include <string.h>
-
-#include <cstring>
-#include <set>
 #include <sstream>
-#include <unordered_set>
 
 #include "base/strings/string_util.h"
 #include "tools/gn/config_values_extractors.h"
 #include "tools/gn/deps_iterator.h"
-#include "tools/gn/err.h"
-#include "tools/gn/escape.h"
 #include "tools/gn/filesystem_utils.h"
+#include "tools/gn/general_tool.h"
 #include "tools/gn/ninja_c_binary_target_writer.h"
-#include "tools/gn/ninja_target_command_util.h"
+#include "tools/gn/ninja_rust_binary_target_writer.h"
 #include "tools/gn/ninja_utils.h"
-#include "tools/gn/scheduler.h"
 #include "tools/gn/settings.h"
 #include "tools/gn/string_utils.h"
 #include "tools/gn/substitution_writer.h"
@@ -35,6 +27,177 @@
 NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default;
 
 void NinjaBinaryTargetWriter::Run() {
+  if (target_->source_types_used().RustSourceUsed()) {
+    NinjaRustBinaryTargetWriter writer(target_, out_);
+    writer.Run();
+    return;
+  }
+
   NinjaCBinaryTargetWriter writer(target_, out_);
   writer.Run();
 }
+
+OutputFile NinjaBinaryTargetWriter::WriteInputsStampAndGetDep() const {
+  CHECK(target_->toolchain()) << "Toolchain not set on target "
+                              << target_->label().GetUserVisibleName(true);
+
+  UniqueVector<const SourceFile*> inputs;
+  for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
+    for (const auto& input : iter.cur().inputs()) {
+      inputs.push_back(&input);
+    }
+  }
+
+  if (inputs.size() == 0)
+    return OutputFile();  // No inputs
+
+  // If we only have one input, return it directly instead of writing a stamp
+  // file for it.
+  if (inputs.size() == 1)
+    return OutputFile(settings_->build_settings(), *inputs[0]);
+
+  // Make a stamp file.
+  OutputFile stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
+  stamp_file.value().append(target_->label().name());
+  stamp_file.value().append(".inputs.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << GeneralTool::kGeneralToolStamp;
+
+  // File inputs.
+  for (const auto* input : inputs) {
+    out_ << " ";
+    path_output_.WriteFile(out_, *input);
+  }
+
+  out_ << std::endl;
+  return stamp_file;
+}
+
+void NinjaBinaryTargetWriter::WriteSourceSetStamp(
+    const std::vector<OutputFile>& object_files) {
+  // The stamp rule for source sets is generally not used, since targets that
+  // depend on this will reference the object files directly. However, writing
+  // this rule allows the user to type the name of the target and get a build
+  // which can be convenient for development.
+  UniqueVector<OutputFile> extra_object_files;
+  UniqueVector<const Target*> linkable_deps;
+  UniqueVector<const Target*> non_linkable_deps;
+  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
+
+  // The classifier should never put extra object files in a source sets: any
+  // source sets that we depend on should appear in our non-linkable deps
+  // instead.
+  DCHECK(extra_object_files.empty());
+
+  std::vector<OutputFile> order_only_deps;
+  for (auto* dep : non_linkable_deps)
+    order_only_deps.push_back(dep->dependency_output_file());
+
+  WriteStampForTarget(object_files, order_only_deps);
+}
+
+void NinjaBinaryTargetWriter::GetDeps(
+    UniqueVector<OutputFile>* extra_object_files,
+    UniqueVector<const Target*>* linkable_deps,
+    UniqueVector<const Target*>* non_linkable_deps) const {
+  // Normal public/private deps.
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    ClassifyDependency(pair.ptr, extra_object_files, linkable_deps,
+                       non_linkable_deps);
+  }
+
+  // Inherited libraries.
+  for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
+    ClassifyDependency(inherited_target, extra_object_files, linkable_deps,
+                       non_linkable_deps);
+  }
+
+  // Data deps.
+  for (const auto& data_dep_pair : target_->data_deps())
+    non_linkable_deps->push_back(data_dep_pair.ptr);
+}
+
+void NinjaBinaryTargetWriter::ClassifyDependency(
+    const Target* dep,
+    UniqueVector<OutputFile>* extra_object_files,
+    UniqueVector<const Target*>* linkable_deps,
+    UniqueVector<const Target*>* non_linkable_deps) const {
+  // Only the following types of outputs have libraries linked into them:
+  //  EXECUTABLE
+  //  SHARED_LIBRARY
+  //  _complete_ STATIC_LIBRARY
+  //
+  // Child deps of intermediate static libraries get pushed up the
+  // dependency tree until one of these is reached, and source sets
+  // don't link at all.
+  bool can_link_libs = target_->IsFinal();
+
+  if (dep->output_type() == Target::SOURCE_SET ||
+      // If a complete static library depends on an incomplete static library,
+      // manually link in the object files of the dependent library as if it
+      // were a source set. This avoids problems with braindead tools such as
+      // ar which don't properly link dependent static libraries.
+      (target_->complete_static_lib() &&
+       (dep->output_type() == Target::STATIC_LIBRARY &&
+        !dep->complete_static_lib()))) {
+    // Source sets have their object files linked into final targets
+    // (shared libraries, executables, loadable modules, and complete static
+    // libraries). Intermediate static libraries and other source sets
+    // just forward the dependency, otherwise the files in the source
+    // set can easily get linked more than once which will cause
+    // multiple definition errors.
+    if (can_link_libs)
+      AddSourceSetFiles(dep, extra_object_files);
+
+    // Add the source set itself as a non-linkable dependency on the current
+    // target. This will make sure that anything the source set's stamp file
+    // depends on (like data deps) are also built before the current target
+    // can be complete. Otherwise, these will be skipped since this target
+    // will depend only on the source set's object files.
+    non_linkable_deps->push_back(dep);
+  } else if (target_->complete_static_lib() && dep->IsFinal()) {
+    non_linkable_deps->push_back(dep);
+  } else if (can_link_libs && dep->IsLinkable()) {
+    linkable_deps->push_back(dep);
+  } else {
+    non_linkable_deps->push_back(dep);
+  }
+}
+
+void NinjaBinaryTargetWriter::AddSourceSetFiles(
+    const Target* source_set,
+    UniqueVector<OutputFile>* obj_files) const {
+  // Just add all sources to the list.
+  for (const auto& source : source_set->sources()) {
+    obj_files->push_back(OutputFile(settings_->build_settings(), source));
+  }
+}
+
+void NinjaBinaryTargetWriter::WriteCompilerBuildLine(
+    const SourceFile& source,
+    const std::vector<OutputFile>& extra_deps,
+    const std::vector<OutputFile>& order_only_deps,
+    const char* tool_name,
+    const std::vector<OutputFile>& outputs) {
+  out_ << "build";
+  path_output_.WriteFiles(out_, outputs);
+
+  out_ << ": " << rule_prefix_ << tool_name;
+  out_ << " ";
+  path_output_.WriteFile(out_, source);
+
+  if (!extra_deps.empty()) {
+    out_ << " |";
+    path_output_.WriteFiles(out_, extra_deps);
+  }
+
+  if (!order_only_deps.empty()) {
+    out_ << " ||";
+    path_output_.WriteFiles(out_, order_only_deps);
+  }
+  out_ << std::endl;
+}
\ No newline at end of file
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index f0611d7..3274867 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -24,11 +24,46 @@
   void Run() override;
 
  protected:
-  typedef std::set<OutputFile> OutputFileSet;
+  // Writes to the output stream a stamp rule for inputs, and
+  // returns the file to be appended to source rules that encodes the
+  // implicit dependencies for the current target. The returned OutputFile
+  // will be empty if there are no inputs.
+  OutputFile WriteInputsStampAndGetDep() const;
+
+  // Writes the stamp line for a source set. These are not linked.
+  void WriteSourceSetStamp(const std::vector<OutputFile>& object_files);
+
+  // Gets all target dependencies and classifies them, as well as accumulates
+  // object files from source sets we need to link.
+  void GetDeps(UniqueVector<OutputFile>* extra_object_files,
+               UniqueVector<const Target*>* linkable_deps,
+               UniqueVector<const Target*>* non_linkable_deps) const;
+
+  // Classifies the dependency as linkable or nonlinkable with the current
+  // target, adding it to the appropriate vector. If the dependency is a source
+  // set we should link in, the source set's object files will be appended to
+  // |extra_object_files|.
+  void ClassifyDependency(const Target* dep,
+                          UniqueVector<OutputFile>* extra_object_files,
+                          UniqueVector<const Target*>* linkable_deps,
+                          UniqueVector<const Target*>* non_linkable_deps) const;
+
+  OutputFile WriteStampAndGetDep(const UniqueVector<const SourceFile*>& files,
+                                 const std::string& stamp_ext) const;
+
+  void WriteCompilerBuildLine(const SourceFile& source,
+                              const std::vector<OutputFile>& extra_deps,
+                              const std::vector<OutputFile>& order_only_deps,
+                              const char* tool_name,
+                              const std::vector<OutputFile>& outputs);
+
+  virtual void AddSourceSetFiles(const Target* source_set,
+                                 UniqueVector<OutputFile>* obj_files) const;
 
   // Cached version of the prefix used for rule types for this toolchain.
   std::string rule_prefix_;
 
+ private:
   DISALLOW_COPY_AND_ASSIGN(NinjaBinaryTargetWriter);
 };
 
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
index 041c0a1..f4590ad 100644
--- a/tools/gn/ninja_c_binary_target_writer.cc
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -52,56 +52,6 @@
   return "";
 }
 
-// Appends the object files generated by the given source set to the given
-// output vector.
-void AddSourceSetObjectFiles(const Target* source_set,
-                             UniqueVector<OutputFile>* obj_files) {
-  std::vector<OutputFile> tool_outputs;  // Prevent allocation in loop.
-
-  // Compute object files for all sources. Only link the first output from
-  // the tool if there are more than one.
-  for (const auto& source : source_set->sources()) {
-    const char* tool_name = Tool::kToolNone;
-    if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
-      obj_files->push_back(tool_outputs[0]);
-  }
-
-  // Add MSVC precompiled header object files. GCC .gch files are not object
-  // files so they are omitted.
-  if (source_set->config_values().has_precompiled_headers()) {
-    if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) {
-      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
-      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
-        GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
-        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
-      }
-    }
-    if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) {
-      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
-      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
-        GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
-        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
-      }
-    }
-    if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) {
-      const CTool* tool =
-          source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
-      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
-        GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
-        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
-      }
-    }
-    if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) {
-      const CTool* tool =
-          source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
-      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
-        GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs);
-        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
-      }
-    }
-  }
-}
-
 }  // namespace
 
 NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target,
@@ -188,7 +138,7 @@
     // Verify that the function that separately computes a source set's object
     // files match the object files just computed.
     UniqueVector<OutputFile> computed_obj;
-    AddSourceSetObjectFiles(target_, &computed_obj);
+    AddSourceSetFiles(target_, &computed_obj);
     DCHECK_EQ(obj_files.size(), computed_obj.size());
     for (const auto& obj : obj_files)
       DCHECK_NE(static_cast<size_t>(-1), computed_obj.IndexOf(obj));
@@ -261,46 +211,6 @@
   WriteSharedVars(subst);
 }
 
-OutputFile NinjaCBinaryTargetWriter::WriteInputsStampAndGetDep() const {
-  CHECK(target_->toolchain()) << "Toolchain not set on target "
-                              << target_->label().GetUserVisibleName(true);
-
-  std::vector<const SourceFile*> inputs;
-  for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
-    for (const auto& input : iter.cur().inputs()) {
-      inputs.push_back(&input);
-    }
-  }
-
-  if (inputs.size() == 0)
-    return OutputFile();  // No inputs
-
-  // If we only have one input, return it directly instead of writing a stamp
-  // file for it.
-  if (inputs.size() == 1)
-    return OutputFile(settings_->build_settings(), *inputs[0]);
-
-  // Make a stamp file.
-  OutputFile input_stamp_file =
-      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
-  input_stamp_file.value().append(target_->label().name());
-  input_stamp_file.value().append(".inputs.stamp");
-
-  out_ << "build ";
-  path_output_.WriteFile(out_, input_stamp_file);
-  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
-       << GeneralTool::kGeneralToolStamp;
-
-  // File inputs.
-  for (const auto* input : inputs) {
-    out_ << " ";
-    path_output_.WriteFile(out_, *input);
-  }
-
-  out_ << "\n";
-  return input_stamp_file;
-}
-
 void NinjaCBinaryTargetWriter::WritePCHCommands(
     const OutputFile& input_dep,
     const std::vector<OutputFile>& order_only_deps,
@@ -516,31 +426,6 @@
   out_ << std::endl;
 }
 
-void NinjaCBinaryTargetWriter::WriteCompilerBuildLine(
-    const SourceFile& source,
-    const std::vector<OutputFile>& extra_deps,
-    const std::vector<OutputFile>& order_only_deps,
-    const char* tool_name,
-    const std::vector<OutputFile>& outputs) {
-  out_ << "build";
-  path_output_.WriteFiles(out_, outputs);
-
-  out_ << ": " << rule_prefix_ << tool_name;
-  out_ << " ";
-  path_output_.WriteFile(out_, source);
-
-  if (!extra_deps.empty()) {
-    out_ << " |";
-    path_output_.WriteFiles(out_, extra_deps);
-  }
-
-  if (!order_only_deps.empty()) {
-    out_ << " ||";
-    path_output_.WriteFiles(out_, order_only_deps);
-  }
-  out_ << std::endl;
-}
-
 void NinjaCBinaryTargetWriter::WriteLinkerStuff(
     const std::vector<OutputFile>& object_files,
     const std::vector<SourceFile>& other_files,
@@ -735,97 +620,6 @@
   out_ << std::endl;
 }
 
-void NinjaCBinaryTargetWriter::WriteSourceSetStamp(
-    const std::vector<OutputFile>& object_files) {
-  // The stamp rule for source sets is generally not used, since targets that
-  // depend on this will reference the object files directly. However, writing
-  // this rule allows the user to type the name of the target and get a build
-  // which can be convenient for development.
-  UniqueVector<OutputFile> extra_object_files;
-  UniqueVector<const Target*> linkable_deps;
-  UniqueVector<const Target*> non_linkable_deps;
-  GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps);
-
-  // The classifier should never put extra object files in a source set:
-  // any source sets that we depend on should appear in our non-linkable
-  // deps instead.
-  DCHECK(extra_object_files.empty());
-
-  std::vector<OutputFile> order_only_deps;
-  for (auto* dep : non_linkable_deps)
-    order_only_deps.push_back(dep->dependency_output_file());
-
-  WriteStampForTarget(object_files, order_only_deps);
-}
-
-void NinjaCBinaryTargetWriter::GetDeps(
-    UniqueVector<OutputFile>* extra_object_files,
-    UniqueVector<const Target*>* linkable_deps,
-    UniqueVector<const Target*>* non_linkable_deps) const {
-  // Normal public/private deps.
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
-    ClassifyDependency(pair.ptr, extra_object_files, linkable_deps,
-                       non_linkable_deps);
-  }
-
-  // Inherited libraries.
-  for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
-    ClassifyDependency(inherited_target, extra_object_files, linkable_deps,
-                       non_linkable_deps);
-  }
-
-  // Data deps.
-  for (const auto& data_dep_pair : target_->data_deps())
-    non_linkable_deps->push_back(data_dep_pair.ptr);
-}
-
-void NinjaCBinaryTargetWriter::ClassifyDependency(
-    const Target* dep,
-    UniqueVector<OutputFile>* extra_object_files,
-    UniqueVector<const Target*>* linkable_deps,
-    UniqueVector<const Target*>* non_linkable_deps) const {
-  // Only the following types of outputs have libraries linked into them:
-  //  EXECUTABLE
-  //  SHARED_LIBRARY
-  //  _complete_ STATIC_LIBRARY
-  //
-  // Child deps of intermediate static libraries get pushed up the
-  // dependency tree until one of these is reached, and source sets
-  // don't link at all.
-  bool can_link_libs = target_->IsFinal();
-
-  if (dep->output_type() == Target::SOURCE_SET ||
-      // If a complete static library depends on an incomplete static library,
-      // manually link in the object files of the dependent library as if it
-      // were a source set. This avoids problems with braindead tools such as
-      // ar which don't properly link dependent static libraries.
-      (target_->complete_static_lib() &&
-       dep->output_type() == Target::STATIC_LIBRARY &&
-       !dep->complete_static_lib())) {
-    // Source sets have their object files linked into final targets
-    // (shared libraries, executables, loadable modules, and complete static
-    // libraries). Intermediate static libraries and other source sets
-    // just forward the dependency, otherwise the files in the source
-    // set can easily get linked more than once which will cause
-    // multiple definition errors.
-    if (can_link_libs)
-      AddSourceSetObjectFiles(dep, extra_object_files);
-
-    // Add the source set itself as a non-linkable dependency on the current
-    // target. This will make sure that anything the source set's stamp file
-    // depends on (like data deps) are also built before the current target
-    // can be complete. Otherwise, these will be skipped since this target
-    // will depend only on the source set's object files.
-    non_linkable_deps->push_back(dep);
-  } else if (target_->complete_static_lib() && dep->IsFinal()) {
-    non_linkable_deps->push_back(dep);
-  } else if (can_link_libs && dep->IsLinkable()) {
-    linkable_deps->push_back(dep);
-  } else {
-    non_linkable_deps->push_back(dep);
-  }
-}
-
 void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies(
     const UniqueVector<const Target*>& non_linkable_deps) {
   if (!non_linkable_deps.empty()) {
@@ -867,3 +661,54 @@
   }
   return true;
 }
+
+// Appends the object files generated by the given source set to the given
+// output vector.
+void NinjaCBinaryTargetWriter::AddSourceSetFiles(
+    const Target* source_set,
+    UniqueVector<OutputFile>* obj_files) const {
+  std::vector<OutputFile> tool_outputs;  // Prevent allocation in loop.
+
+  // Compute object files for all sources. Only link the first output from
+  // the tool if there are more than one.
+  for (const auto& source : source_set->sources()) {
+    const char* tool_name = Tool::kToolNone;
+    if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
+      obj_files->push_back(tool_outputs[0]);
+  }
+
+  // Add MSVC precompiled header object files. GCC .gch files are not object
+  // files so they are omitted.
+  if (source_set->config_values().has_precompiled_headers()) {
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) {
+      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) {
+      const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) {
+      const CTool* tool =
+          source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+    if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) {
+      const CTool* tool =
+          source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+      if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
+        GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs);
+        obj_files->Append(tool_outputs.begin(), tool_outputs.end());
+      }
+    }
+  }
+}
diff --git a/tools/gn/ninja_c_binary_target_writer.h b/tools/gn/ninja_c_binary_target_writer.h
index d236dd9..8dacf8e 100644
--- a/tools/gn/ninja_c_binary_target_writer.h
+++ b/tools/gn/ninja_c_binary_target_writer.h
@@ -22,18 +22,17 @@
 
   void Run() override;
 
+ protected:
+  // Adds source_set files to the list of object files.
+  void AddSourceSetFiles(const Target* source_set,
+                         UniqueVector<OutputFile>* obj_files) const override;
+
  private:
   typedef std::set<OutputFile> OutputFileSet;
 
   // Writes all flags for the compiler: includes, defines, cflags, etc.
   void WriteCompilerVars();
 
-  // Writes to the output stream a stamp rule for inputs, and
-  // returns the file to be appended to source rules that encodes the
-  // implicit dependencies for the current target. The returned OutputFile
-  // will be empty if there are no inputs.
-  OutputFile WriteInputsStampAndGetDep() const;
-
   // Writes build lines required for precompiled headers. Any generated
   // object files will be appended to the |object_files|. Any generated
   // non-object files (for instance, .gch files from a GCC toolchain, are
@@ -80,13 +79,6 @@
                     std::vector<OutputFile>* object_files,
                     std::vector<SourceFile>* other_files);
 
-  // Writes a build line.
-  void WriteCompilerBuildLine(const SourceFile& source,
-                              const std::vector<OutputFile>& extra_deps,
-                              const std::vector<OutputFile>& order_only_deps,
-                              const char* tool_name,
-                              const std::vector<OutputFile>& outputs);
-
   void WriteLinkerStuff(const std::vector<OutputFile>& object_files,
                         const std::vector<SourceFile>& other_files,
                         const OutputFile& input_dep);
@@ -95,24 +87,6 @@
   void WriteOutputSubstitutions();
   void WriteSolibs(const std::vector<OutputFile>& solibs);
 
-  // Writes the stamp line for a source set. These are not linked.
-  void WriteSourceSetStamp(const std::vector<OutputFile>& object_files);
-
-  // Gets all target dependencies and classifies them, as well as accumulates
-  // object files from source sets we need to link.
-  void GetDeps(UniqueVector<OutputFile>* extra_object_files,
-               UniqueVector<const Target*>* linkable_deps,
-               UniqueVector<const Target*>* non_linkable_deps) const;
-
-  // Classifies the dependency as linkable or nonlinkable with the current
-  // target, adding it to the appropriate vector. If the dependency is a source
-  // set we should link in, the source set's object files will be appended to
-  // |extra_object_files|.
-  void ClassifyDependency(const Target* dep,
-                          UniqueVector<OutputFile>* extra_object_files,
-                          UniqueVector<const Target*>* linkable_deps,
-                          UniqueVector<const Target*>* non_linkable_deps) const;
-
   // Writes the implicit dependencies for the link or stamp line. This is
   // the "||" and everything following it on the ninja line.
   //
diff --git a/tools/gn/ninja_rust_binary_target_writer.cc b/tools/gn/ninja_rust_binary_target_writer.cc
new file mode 100644
index 0000000..869cac8
--- /dev/null
+++ b/tools/gn/ninja_rust_binary_target_writer.cc
@@ -0,0 +1,210 @@
+// 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/ninja_rust_binary_target_writer.h"
+
+#include <sstream>
+
+#include "tools/gn/deps_iterator.h"
+#include "tools/gn/general_tool.h"
+#include "tools/gn/ninja_target_command_util.h"
+#include "tools/gn/ninja_utils.h"
+#include "tools/gn/rust_substitution_type.h"
+#include "tools/gn/substitution_writer.h"
+#include "tools/gn/target.h"
+
+namespace {
+
+// Returns the proper escape options for writing compiler and linker flags.
+EscapeOptions GetFlagOptions() {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  return opts;
+}
+
+void WriteVar(const char* name,
+              const std::string& value,
+              EscapeOptions opts,
+              std::ostream& out) {
+  out << name << " = ";
+  EscapeStringToStream(out, value, opts);
+  out << std::endl;
+}
+
+void WriteCrateVars(const Target* target,
+                    const Tool* tool,
+                    EscapeOptions opts,
+                    std::ostream& out) {
+  WriteVar(kRustSubstitutionCrateName.ninja_name,
+           target->rust_values().crate_name(), opts, out);
+
+  std::string crate_type;
+  switch (target->output_type()) {
+    case Target::EXECUTABLE:
+      crate_type = "bin";
+      break;
+    case Target::STATIC_LIBRARY:
+      crate_type = "staticlib";
+      break;
+    case Target::RUST_LIBRARY:
+      crate_type = "rlib";
+      break;
+    case Target::SHARED_LIBRARY:
+      switch (target->rust_values().crate_type()) {
+        case RustValues::CRATE_DYLIB:
+          crate_type = "dylib";
+          break;
+        case RustValues::CRATE_CDYLIB:
+          crate_type = "cdylib";
+          break;
+        case RustValues::CRATE_PROC_MACRO:
+          crate_type = "proc-macro";
+          break;
+        default:
+          NOTREACHED();
+      }
+    default:
+      NOTREACHED();
+  }
+  WriteVar(kRustSubstitutionCrateType.ninja_name, crate_type, opts, out);
+
+  if (!target->output_extension_set()) {
+    DCHECK(tool->AsRust());
+    WriteVar(kRustSubstitutionOutputExtension.ninja_name,
+             tool->AsRust()->rustc_output_extension(
+                 target->output_type(), target->rust_values().crate_type()),
+             opts, out);
+  } else if (target->output_extension().empty()) {
+    WriteVar(kRustSubstitutionOutputExtension.ninja_name, "", opts, out);
+  } else {
+    WriteVar(kRustSubstitutionOutputExtension.ninja_name,
+             std::string(".") + target->output_extension(), opts, out);
+  }
+
+  if (target->output_type() == Target::RUST_LIBRARY ||
+      target->output_type() == Target::SHARED_LIBRARY)
+    WriteVar(kRustSubstitutionOutputPrefix.ninja_name, "lib", opts, out);
+}
+
+}  // namespace
+
+NinjaRustBinaryTargetWriter::NinjaRustBinaryTargetWriter(const Target* target,
+                                                         std::ostream& out)
+    : NinjaBinaryTargetWriter(target, out),
+      tool_(target->toolchain()->GetToolForTargetFinalOutputAsRust(target)) {}
+
+NinjaRustBinaryTargetWriter::~NinjaRustBinaryTargetWriter() = default;
+
+// TODO(juliehockett): add inherited library support? and IsLinkable support?
+// for c-cross-compat
+void NinjaRustBinaryTargetWriter::Run() {
+  OutputFile input_dep = WriteInputsStampAndGetDep();
+
+  // The input dependencies will be an order-only dependency. This will cause
+  // Ninja to make sure the inputs are up to date before compiling this source,
+  // but changes in the inputs deps won't cause the file to be recompiled. See
+  // the comment on NinjaCBinaryTargetWriter::Run for more detailed explanation.
+  size_t num_stamp_uses = target_->sources().size();
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+
+  // Public rust_library deps go in a --extern rlibs, public non-rust deps go in
+  // -Ldependency rustdeps, and non-public source_sets get passed in as normal
+  // source files
+  UniqueVector<OutputFile> deps;
+  AddSourceSetFiles(target_, &deps);
+  if (target_->output_type() == Target::SOURCE_SET) {
+    WriteSharedVars(target_->toolchain()->substitution_bits());
+    WriteSourceSetStamp(deps.vector());
+  } else {
+    WriteCompilerVars();
+    UniqueVector<const Target*> linkable_deps;
+    UniqueVector<const Target*> non_linkable_deps;
+    GetDeps(&deps, &linkable_deps, &non_linkable_deps);
+
+    if (!input_dep.value().empty())
+      order_only_deps.push_back(input_dep);
+
+    std::vector<OutputFile> rustdeps;
+    for (const auto* non_linkable_dep : non_linkable_deps) {
+      order_only_deps.push_back(non_linkable_dep->dependency_output_file());
+    }
+
+    for (const auto* linkable_dep : linkable_deps) {
+      rustdeps.push_back(linkable_dep->dependency_output_file());
+      deps.push_back(linkable_dep->dependency_output_file());
+    }
+
+    std::vector<OutputFile> tool_outputs;
+    SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+        target_, tool_, tool_->outputs(), &tool_outputs);
+    WriteCompilerBuildLine(target_->rust_values().crate_root(), deps.vector(),
+                           order_only_deps, tool_->name(), tool_outputs);
+    WriteExterns();
+    WriteRustdeps(rustdeps);
+    WriteEdition();
+  }
+}
+
+void NinjaRustBinaryTargetWriter::WriteCompilerVars() {
+  const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
+  EscapeOptions opts = GetFlagOptions();
+  WriteCrateVars(target_, tool_, opts, out_);
+
+  WriteOneFlag(target_, &kRustSubstitutionRustFlags, false,
+               RustTool::kRsToolRustc, &ConfigValues::rustflags, opts,
+               path_output_, out_);
+
+  WriteOneFlag(target_, &kRustSubstitutionRustEnv, false,
+               RustTool::kRsToolRustc, &ConfigValues::rustenv, opts,
+               path_output_, out_);
+
+  WriteSharedVars(subst);
+}
+
+void NinjaRustBinaryTargetWriter::WriteExterns() {
+  std::vector<const Target*> externs;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->output_type() == Target::RUST_LIBRARY) {
+      externs.push_back(pair.ptr);
+    }
+  }
+  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()) << "=";
+    }
+
+    path_output_.WriteFile(out_, ex->dependency_output_file());
+  }
+  out_ << std::endl;
+}
+
+void NinjaRustBinaryTargetWriter::WriteRustdeps(
+    std::vector<OutputFile>& rustdeps) {
+  if (rustdeps.empty())
+    return;
+  out_ << "  rustdeps =";
+  for (const auto& rustdep : rustdeps) {
+    out_ << " -Ldependency=";
+    path_output_.WriteDir(
+        out_, rustdep.AsSourceFile(settings_->build_settings()).GetDir(),
+        PathOutput::DIR_NO_LAST_SLASH);
+  }
+  out_ << std::endl;
+}
+
+void NinjaRustBinaryTargetWriter::WriteEdition() {
+  DCHECK(!target_->rust_values().edition().empty());
+  out_ << "  edition = " << target_->rust_values().edition() << std::endl;
+}
\ No newline at end of file
diff --git a/tools/gn/ninja_rust_binary_target_writer.h b/tools/gn/ninja_rust_binary_target_writer.h
new file mode 100644
index 0000000..ab738e2
--- /dev/null
+++ b/tools/gn/ninja_rust_binary_target_writer.h
@@ -0,0 +1,36 @@
+// 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_NINJA_RUST_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_RUST_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "tools/gn/ninja_binary_target_writer.h"
+#include "tools/gn/rust_tool.h"
+
+struct EscapeOptions;
+
+// Writes a .ninja file for a binary target type (an executable, a shared
+// library, or a static library).
+class NinjaRustBinaryTargetWriter : public NinjaBinaryTargetWriter {
+ public:
+  NinjaRustBinaryTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaRustBinaryTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  void WriteCompilerVars();
+  void WriteSources(const OutputFile& input_dep,
+                    const std::vector<OutputFile>& order_only_deps);
+  void WriteExterns();
+  void WriteRustdeps(std::vector<OutputFile>& rustdeps);
+  void WriteEdition();
+
+  const RustTool* tool_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaRustBinaryTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_RUST_BINARY_TARGET_WRITER_H_
diff --git a/tools/gn/ninja_rust_binary_target_writer_unittest.cc b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
new file mode 100644
index 0000000..9d8aee1
--- /dev/null
+++ b/tools/gn/ninja_rust_binary_target_writer_unittest.cc
@@ -0,0 +1,247 @@
+// 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/ninja_rust_binary_target_writer.h"
+
+#include "tools/gn/config.h"
+#include "tools/gn/scheduler.h"
+#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
+#include "tools/gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+using NinjaRustBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustSourceSet) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::SOURCE_SET);
+  target.visibility().SetPublic();
+  target.sources().push_back(SourceFile("//foo/input1.rs"));
+  target.sources().push_back(SourceFile("//foo/main.rs"));
+  target.source_types_used().Set(SourceFile::SOURCE_RS);
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Source set itself.
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp ../../foo/input1.rs "
+        "../../foo/main.rs\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RustExecutable) {
+  Err err;
+  TestWithScope setup;
+
+  Target source_set(setup.settings(), Label(SourceDir("//foo/"), "sources"));
+  source_set.set_output_type(Target::SOURCE_SET);
+  source_set.visibility().SetPublic();
+  source_set.sources().push_back(SourceFile("//foo/input1.rs"));
+  source_set.sources().push_back(SourceFile("//foo/input2.rs"));
+  source_set.source_types_used().Set(SourceFile::SOURCE_RS);
+  source_set.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(source_set.OnResolved(&err));
+
+  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/input3.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.rust_values().edition() = "2018";
+  target.private_deps().push_back(LabelTargetPair(&source_set));
+  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"
+        "rustc_output_extension = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/foo_bar: rustc ../../foo/main.rs | ../../foo/input3.rs "
+        "../../foo/main.rs ../../foo/input1.rs ../../foo/input2.rs || "
+        "obj/foo/sources.stamp\n"
+        "  edition = 2018\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RlibDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target rlib(setup.settings(), Label(SourceDir("//bar/"), "mylib"));
+  rlib.set_output_type(Target::RUST_LIBRARY);
+  rlib.visibility().SetPublic();
+  SourceFile barlib("//bar/lib.rs");
+  rlib.sources().push_back(SourceFile("//bar/mylib.rs"));
+  rlib.sources().push_back(barlib);
+  rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  rlib.rust_values().set_crate_root(barlib);
+  rlib.rust_values().crate_name() = "mylib";
+  rlib.rust_values().edition() = "2018";
+  rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(rlib.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaRustBinaryTargetWriter writer(&rlib, out);
+    writer.Run();
+
+    const char expected[] =
+        "crate_name = mylib\n"
+        "crate_type = rlib\n"
+        "rustc_output_extension = .rlib\n"
+        "rustc_output_prefix = lib\n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/bar\n"
+        "target_output_name = mylib\n"
+        "\n"
+        "build obj/bar/libmylib.rlib: rustc ../../bar/lib.rs | "
+        "../../bar/mylib.rs ../../bar/lib.rs\n"
+        "  edition = 2018\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << out_str;
+  }
+
+  Target another_rlib(setup.settings(), Label(SourceDir("//foo/"), "direct"));
+  another_rlib.set_output_type(Target::RUST_LIBRARY);
+  another_rlib.visibility().SetPublic();
+  SourceFile lib("//foo/main.rs");
+  another_rlib.sources().push_back(SourceFile("//foo/direct.rs"));
+  another_rlib.sources().push_back(lib);
+  another_rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  another_rlib.rust_values().set_crate_root(lib);
+  another_rlib.rust_values().crate_name() = "direct";
+  another_rlib.rust_values().edition() = "2018";
+  another_rlib.SetToolchain(setup.toolchain());
+  another_rlib.public_deps().push_back(LabelTargetPair(&rlib));
+  ASSERT_TRUE(another_rlib.OnResolved(&err));
+
+  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.rust_values().edition() = "2018";
+  target.private_deps().push_back(LabelTargetPair(&another_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"
+        "rustc_output_extension = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/foo_bar: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/foo/libdirect.rlib obj/bar/libmylib.rlib\n"
+        "  externs = --extern direct=obj/foo/libdirect.rlib\n"
+        "  rustdeps = -Ldependency=obj/foo -Ldependency=obj/bar\n"
+        "  edition = 2018\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
+
+TEST_F(NinjaRustBinaryTargetWriterTest, RenamedDeps) {
+  Err err;
+  TestWithScope setup;
+
+  Target another_rlib(setup.settings(), Label(SourceDir("//foo/"), "direct"));
+  another_rlib.set_output_type(Target::RUST_LIBRARY);
+  another_rlib.visibility().SetPublic();
+  SourceFile lib("//foo/lib.rs");
+  another_rlib.sources().push_back(SourceFile("//foo/direct.rs"));
+  another_rlib.sources().push_back(lib);
+  another_rlib.source_types_used().Set(SourceFile::SOURCE_RS);
+  another_rlib.rust_values().set_crate_root(lib);
+  another_rlib.rust_values().crate_name() = "direct";
+  another_rlib.rust_values().edition() = "2018";
+  another_rlib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(another_rlib.OnResolved(&err));
+
+  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.rust_values().aliased_deps()[another_rlib.label()] = "direct_renamed";
+  target.rust_values().edition() = "2018";
+  target.private_deps().push_back(LabelTargetPair(&another_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"
+        "rustc_output_extension = \n"
+        "rustflags =\n"
+        "rustenv =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/foo_bar: rustc ../../foo/main.rs | ../../foo/source.rs "
+        "../../foo/main.rs obj/foo/libdirect.rlib\n"
+        "  externs = --extern direct_renamed=obj/foo/libdirect.rlib\n"
+        "  rustdeps = -Ldependency=obj/foo\n"
+        "  edition = 2018\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index d48ffd9..60de120 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -710,6 +710,11 @@
           SubstitutionWriter::ApplyListToLinkerAsOutputFile(
               this, tool, tool->runtime_outputs(), &runtime_outputs_);
         }
+      } else if (const RustTool* rstool = tool->AsRust()) {
+        // Default behavior, use the first output file for both.
+        link_output_file_ = dependency_output_file_ =
+            SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
+                this, tool, tool->outputs().list()[0]);
       }
       break;
     case UNKNOWN: