Move C logic to child class of NinjaBinaryTargetWriter

Change-Id: I0faf5027c790189993ab3bf4561c757c298674e1
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4463
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 75210d7..eeb7cb5 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -491,6 +491,7 @@
         'tools/gn/ninja_binary_target_writer.cc',
         'tools/gn/ninja_build_writer.cc',
         'tools/gn/ninja_bundle_data_target_writer.cc',
+        'tools/gn/ninja_c_binary_target_writer.cc',
         'tools/gn/ninja_copy_target_writer.cc',
         'tools/gn/ninja_create_bundle_target_writer.cc',
         'tools/gn/ninja_generated_file_target_writer.cc',
@@ -591,6 +592,7 @@
         'tools/gn/metadata_walk_unittest.cc',
         'tools/gn/ninja_action_target_writer_unittest.cc',
         'tools/gn/ninja_binary_target_writer_unittest.cc',
+        'tools/gn/ninja_c_binary_target_writer_unittest.cc',
         'tools/gn/ninja_build_writer_unittest.cc',
         'tools/gn/ninja_bundle_data_target_writer_unittest.cc',
         'tools/gn/ninja_copy_target_writer_unittest.cc',
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 9f0e949..4cda834 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -18,7 +18,7 @@
 #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_utils.h"
 #include "tools/gn/scheduler.h"
@@ -28,865 +28,25 @@
 #include "tools/gn/substitution_writer.h"
 #include "tools/gn/target.h"
 
-// Represents a set of tool types. Must be first since it is also shared by
-// some helper functions in the anonymous namespace below.
-class NinjaBinaryTargetWriter::SourceFileTypeSet {
- public:
-  SourceFileTypeSet() {
-    memset(flags_, 0, sizeof(bool) * static_cast<int>(SOURCE_NUMTYPES));
-  }
-
-  void Set(SourceFileType type) { flags_[static_cast<int>(type)] = true; }
-  bool Get(SourceFileType type) const { return flags_[static_cast<int>(type)]; }
-
- private:
-  bool flags_[static_cast<int>(SOURCE_NUMTYPES)];
-};
-
-namespace {
-
-// Returns the proper escape options for writing compiler and linker flags.
-EscapeOptions GetFlagOptions() {
-  EscapeOptions opts;
-  opts.mode = ESCAPE_NINJA_COMMAND;
-  return opts;
+bool NinjaBinaryTargetWriter::SourceFileTypeSet::CSourceUsed() {
+  return Get(SOURCE_CPP) || Get(SOURCE_H) || Get(SOURCE_C) || Get(SOURCE_M) ||
+         Get(SOURCE_MM) || Get(SOURCE_RC) || Get(SOURCE_S);
 }
 
-// Returns the language-specific lang recognized by gcc’s -x flag for
-// precompiled header files.
-const char* GetPCHLangForToolType(const char* name) {
-  if (name == CTool::kCToolCc)
-    return "c-header";
-  if (name == CTool::kCToolCxx)
-    return "c++-header";
-  if (name == CTool::kCToolObjC)
-    return "objective-c-header";
-  if (name == CTool::kCToolObjCxx)
-    return "objective-c++-header";
-  NOTREACHED() << "Not a valid PCH tool type: " << name;
-  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.
-  NinjaBinaryTargetWriter::SourceFileTypeSet used_types;
-
-  // 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]);
-
-    used_types.Set(GetSourceFileType(source));
-  }
-
-  // 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 (used_types.Get(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 (used_types.Get(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 (used_types.Get(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 (used_types.Get(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
-
 NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
                                                  std::ostream& out)
     : NinjaTargetWriter(target, out),
-      tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)),
       rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) {}
 
 NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default;
 
 void NinjaBinaryTargetWriter::Run() {
-  // Figure out what source types are needed.
   SourceFileTypeSet used_types;
   for (const auto& source : target_->sources())
     used_types.Set(GetSourceFileType(source));
 
-  WriteCompilerVars(used_types);
-
-  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.
-  //
-  // This is important to prevent changes in unrelated actions that are
-  // upstream of this target from causing everything to be recompiled.
-  //
-  // Why can we get away with this rather than using implicit deps ("|", which
-  // will force rebuilds when the inputs change)? For source code, the
-  // computed dependencies of all headers will be computed by the compiler,
-  // which will cause source rebuilds if any "real" upstream dependencies
-  // change.
-  //
-  // If a .cc file is generated by an input dependency, Ninja will see the
-  // input to the build rule doesn't exist, and that it is an output from a
-  // previous step, and build the previous step first. This is a "real"
-  // dependency and doesn't need | or || to express.
-  //
-  // The only case where this rule matters is for the first build where no .d
-  // files exist, and Ninja doesn't know what that source file depends on. In
-  // this case it's sufficient to ensure that the upstream dependencies are
-  // built first. This is exactly what Ninja's order-only dependencies
-  // expresses.
-  //
-  // The order only deps are referenced by each source file compile,
-  // but also by PCH compiles.  The latter are annoying to count, so omit
-  // them here.  This means that binary targets with a single source file
-  // that also use PCH files won't have a stamp file even though having
-  // one would make output ninja file size a bit lower. That's ok, binary
-  // targets with a single source are rare.
-  size_t num_stamp_uses = target_->sources().size();
-  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
-      std::vector<const Target*>(), num_stamp_uses);
-
-  // For GCC builds, the .gch files are not object files, but still need to be
-  // added as explicit dependencies below. The .gch output files are placed in
-  // |pch_other_files|. This is to prevent linking against them.
-  std::vector<OutputFile> pch_obj_files;
-  std::vector<OutputFile> pch_other_files;
-  WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
-                   &pch_other_files);
-  std::vector<OutputFile>* pch_files =
-      !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
-
-  // Treat all pch output files as explicit dependencies of all
-  // compiles that support them. Some notes:
-  //
-  //  - On Windows, the .pch file is the input to the compile, not the
-  //    precompiled header's corresponding object file that we're using here.
-  //    But Ninja's depslog doesn't support multiple outputs from the
-  //    precompiled header compile step (it outputs both the .pch file and a
-  //    corresponding .obj file). So we consistently list the .obj file and the
-  //    .pch file we really need comes along with it.
-  //
-  //  - GCC .gch files are not object files, therefore they are not added to the
-  //    object file list.
-  std::vector<OutputFile> obj_files;
-  std::vector<SourceFile> other_files;
-  WriteSources(*pch_files, input_dep, order_only_deps, &obj_files,
-               &other_files);
-
-  // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
-  obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
-  if (!CheckForDuplicateObjectFiles(obj_files))
-    return;
-
-  if (target_->output_type() == Target::SOURCE_SET) {
-    WriteSourceSetStamp(obj_files);
-#ifndef NDEBUG
-    // 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);
-    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));
-#endif
-  } else {
-    WriteLinkerStuff(obj_files, other_files, input_dep);
+  if (used_types.CSourceUsed()) {
+    NinjaCBinaryTargetWriter writer(target_, out_);
+    writer.Run();
   }
 }
-
-void NinjaBinaryTargetWriter::WriteCompilerVars(
-    const SourceFileTypeSet& used_types) {
-  const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
-
-  // Defines.
-  if (subst.used[SUBSTITUTION_DEFINES]) {
-    out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
-    RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
-                                               DefineWriter(), out_);
-    out_ << std::endl;
-  }
-
-  // Include directories.
-  if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
-    out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
-    PathOutput include_path_output(
-        path_output_.current_dir(),
-        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
-    RecursiveTargetConfigToStream<SourceDir>(
-        target_, &ConfigValues::include_dirs,
-        IncludeWriter(include_path_output), out_);
-    out_ << std::endl;
-  }
-
-  bool has_precompiled_headers =
-      target_->config_values().has_precompiled_headers();
-
-  EscapeOptions opts = GetFlagOptions();
-  if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) {
-    WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Tool::kToolNone,
-                 &ConfigValues::asmflags, opts, path_output_, out_);
-  }
-  if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) ||
-      used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
-                 &ConfigValues::cflags, opts, path_output_, out_);
-  }
-  if (used_types.Get(SOURCE_C)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
-                 CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
-                 out_);
-  }
-  if (used_types.Get(SOURCE_CPP)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
-                 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
-                 out_);
-  }
-  if (used_types.Get(SOURCE_M)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
-                 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
-                 path_output_, out_);
-  }
-  if (used_types.Get(SOURCE_MM)) {
-    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
-                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
-                 path_output_, out_);
-  }
-
-  WriteSharedVars(subst);
-}
-
-OutputFile NinjaBinaryTargetWriter::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 NinjaBinaryTargetWriter::WritePCHCommands(
-    const SourceFileTypeSet& used_types,
-    const OutputFile& input_dep,
-    const std::vector<OutputFile>& order_only_deps,
-    std::vector<OutputFile>* object_files,
-    std::vector<OutputFile>* other_files) {
-  if (!target_->config_values().has_precompiled_headers())
-    return;
-
-  const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
-  if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
-      used_types.Get(SOURCE_C)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_C, CTool::kCToolCc,
-                    tool_c->precompiled_header_type(), input_dep,
-                    order_only_deps, object_files, other_files);
-  }
-  const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
-  if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
-      used_types.Get(SOURCE_CPP)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_CC, CTool::kCToolCxx,
-                    tool_cxx->precompiled_header_type(), input_dep,
-                    order_only_deps, object_files, other_files);
-  }
-
-  const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
-  if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
-      used_types.Get(SOURCE_M)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, CTool::kCToolObjC,
-                    tool_objc->precompiled_header_type(), input_dep,
-                    order_only_deps, object_files, other_files);
-  }
-
-  const CTool* tool_objcxx =
-      target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
-  if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
-      used_types.Get(SOURCE_MM)) {
-    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, CTool::kCToolObjCxx,
-                    tool_objcxx->precompiled_header_type(), input_dep,
-                    order_only_deps, object_files, other_files);
-  }
-}
-
-void NinjaBinaryTargetWriter::WritePCHCommand(
-    SubstitutionType flag_type,
-    const char* tool_name,
-    CTool::PrecompiledHeaderType header_type,
-    const OutputFile& input_dep,
-    const std::vector<OutputFile>& order_only_deps,
-    std::vector<OutputFile>* object_files,
-    std::vector<OutputFile>* other_files) {
-  switch (header_type) {
-    case CTool::PCH_MSVC:
-      WriteWindowsPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
-                             object_files);
-      break;
-    case CTool::PCH_GCC:
-      WriteGCCPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
-                         other_files);
-      break;
-    case CTool::PCH_NONE:
-      NOTREACHED() << "Cannot write a PCH command with no PCH header type";
-      break;
-  }
-}
-
-void NinjaBinaryTargetWriter::WriteGCCPCHCommand(
-    SubstitutionType flag_type,
-    const char* tool_name,
-    const OutputFile& input_dep,
-    const std::vector<OutputFile>& order_only_deps,
-    std::vector<OutputFile>* gch_files) {
-  // Compute the pch output file (it will be language-specific).
-  std::vector<OutputFile> outputs;
-  GetPCHOutputFiles(target_, tool_name, &outputs);
-  if (outputs.empty())
-    return;
-
-  gch_files->insert(gch_files->end(), outputs.begin(), outputs.end());
-
-  std::vector<OutputFile> extra_deps;
-  if (!input_dep.value().empty())
-    extra_deps.push_back(input_dep);
-
-  // Build line to compile the file.
-  WriteCompilerBuildLine(target_->config_values().precompiled_source(),
-                         extra_deps, order_only_deps, tool_name, outputs);
-
-  // This build line needs a custom language-specific flags value. Rule-specific
-  // variables are just indented underneath the rule line.
-  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
-
-  // Each substitution flag is overwritten in the target rule to replace the
-  // implicitly generated -include flag with the -x <header lang> flag required
-  // for .gch targets.
-  EscapeOptions opts = GetFlagOptions();
-  if (tool_name == CTool::kCToolCc) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
-                                         out_);
-  } else if (tool_name == CTool::kCToolCxx) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
-                                         opts, out_);
-  } else if (tool_name == CTool::kCToolObjC) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
-                                         opts, out_);
-  } else if (tool_name == CTool::kCToolObjCxx) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
-                                         opts, out_);
-  }
-
-  // Append the command to specify the language of the .gch file.
-  out_ << " -x " << GetPCHLangForToolType(tool_name);
-
-  // Write two blank lines to help separate the PCH build lines from the
-  // regular source build lines.
-  out_ << std::endl << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteWindowsPCHCommand(
-    SubstitutionType flag_type,
-    const char* tool_name,
-    const OutputFile& input_dep,
-    const std::vector<OutputFile>& order_only_deps,
-    std::vector<OutputFile>* object_files) {
-  // Compute the pch output file (it will be language-specific).
-  std::vector<OutputFile> outputs;
-  GetPCHOutputFiles(target_, tool_name, &outputs);
-  if (outputs.empty())
-    return;
-
-  object_files->insert(object_files->end(), outputs.begin(), outputs.end());
-
-  std::vector<OutputFile> extra_deps;
-  if (!input_dep.value().empty())
-    extra_deps.push_back(input_dep);
-
-  // Build line to compile the file.
-  WriteCompilerBuildLine(target_->config_values().precompiled_source(),
-                         extra_deps, order_only_deps, tool_name, outputs);
-
-  // This build line needs a custom language-specific flags value. Rule-specific
-  // variables are just indented underneath the rule line.
-  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
-
-  // Append the command to generate the .pch file.
-  // This adds the value to the existing flag instead of overwriting it.
-  out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}";
-  out_ << " /Yc" << target_->config_values().precompiled_header();
-
-  // Write two blank lines to help separate the PCH build lines from the
-  // regular source build lines.
-  out_ << std::endl << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteSources(
-    const std::vector<OutputFile>& pch_deps,
-    const OutputFile& input_dep,
-    const std::vector<OutputFile>& order_only_deps,
-    std::vector<OutputFile>* object_files,
-    std::vector<SourceFile>* other_files) {
-  object_files->reserve(object_files->size() + target_->sources().size());
-
-  std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop.
-  std::vector<OutputFile> deps;
-  for (const auto& source : target_->sources()) {
-    // Clear the vector but maintain the max capacity to prevent reallocations.
-    deps.resize(0);
-    const char* tool_name = Tool::kToolNone;
-    if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
-      if (GetSourceFileType(source) == SOURCE_DEF)
-        other_files->push_back(source);
-      continue;  // No output for this source.
-    }
-
-    if (!input_dep.value().empty())
-      deps.push_back(input_dep);
-
-    if (tool_name != Tool::kToolNone) {
-      // Only include PCH deps that correspond to the tool type, for instance,
-      // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep
-      // for the output of a C tool type.
-      //
-      // This makes the assumption that pch_deps only contains pch output files
-      // with the naming scheme specified in GetWindowsPCHObjectExtension or
-      // GetGCCPCHOutputExtension.
-      const CTool* tool = target_->toolchain()->GetToolAsC(tool_name);
-      if (tool->precompiled_header_type() != CTool::PCH_NONE) {
-        for (const auto& dep : pch_deps) {
-          const std::string& output_value = dep.value();
-          size_t extension_offset = FindExtensionOffset(output_value);
-          if (extension_offset == std::string::npos)
-            continue;
-          std::string output_extension;
-          if (tool->precompiled_header_type() == CTool::PCH_MSVC) {
-            output_extension = GetWindowsPCHObjectExtension(
-                tool_name, output_value.substr(extension_offset - 1));
-          } else if (tool->precompiled_header_type() == CTool::PCH_GCC) {
-            output_extension = GetGCCPCHOutputExtension(tool_name);
-          }
-          if (output_value.compare(
-                  output_value.size() - output_extension.size(),
-                  output_extension.size(), output_extension) == 0) {
-            deps.push_back(dep);
-          }
-        }
-      }
-      WriteCompilerBuildLine(source, deps, order_only_deps, tool_name,
-                             tool_outputs);
-    }
-
-    // It's theoretically possible for a compiler to produce more than one
-    // output, but we'll only link to the first output.
-    object_files->push_back(tool_outputs[0]);
-  }
-  out_ << std::endl;
-}
-
-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;
-}
-
-void NinjaBinaryTargetWriter::WriteLinkerStuff(
-    const std::vector<OutputFile>& object_files,
-    const std::vector<SourceFile>& other_files,
-    const OutputFile& input_dep) {
-  std::vector<OutputFile> output_files;
-  SubstitutionWriter::ApplyListToLinkerAsOutputFile(
-      target_, tool_, tool_->outputs(), &output_files);
-
-  out_ << "build";
-  path_output_.WriteFiles(out_, output_files);
-
-  out_ << ": " << rule_prefix_
-       << Tool::GetToolTypeForTargetFinalOutput(target_);
-
-  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);
-
-  // Object files.
-  path_output_.WriteFiles(out_, object_files);
-  path_output_.WriteFiles(out_, extra_object_files);
-
-  // Dependencies.
-  std::vector<OutputFile> implicit_deps;
-  std::vector<OutputFile> solibs;
-  for (const Target* cur : linkable_deps) {
-    // All linkable deps should have a link output file.
-    DCHECK(!cur->link_output_file().value().empty())
-        << "No link output file for "
-        << target_->label().GetUserVisibleName(false);
-
-    if (cur->dependency_output_file().value() !=
-        cur->link_output_file().value()) {
-      // This is a shared library with separate link and deps files. Save for
-      // later.
-      implicit_deps.push_back(cur->dependency_output_file());
-      solibs.push_back(cur->link_output_file());
-    } else {
-      // Normal case, just link to this target.
-      out_ << " ";
-      path_output_.WriteFile(out_, cur->link_output_file());
-    }
-  }
-
-  const SourceFile* optional_def_file = nullptr;
-  if (!other_files.empty()) {
-    for (const SourceFile& src_file : other_files) {
-      if (GetSourceFileType(src_file) == SOURCE_DEF) {
-        optional_def_file = &src_file;
-        implicit_deps.push_back(
-            OutputFile(settings_->build_settings(), src_file));
-        break;  // Only one def file is allowed.
-      }
-    }
-  }
-
-  // Libraries specified by paths.
-  const OrderedSet<LibFile>& libs = target_->all_libs();
-  for (size_t i = 0; i < libs.size(); i++) {
-    if (libs[i].is_source_file()) {
-      implicit_deps.push_back(
-          OutputFile(settings_->build_settings(), libs[i].source_file()));
-    }
-  }
-
-  // The input dependency is only needed if there are no object files, as the
-  // dependency is normally provided transitively by the source files.
-  if (!input_dep.value().empty() && object_files.empty())
-    implicit_deps.push_back(input_dep);
-
-  // Append implicit dependencies collected above.
-  if (!implicit_deps.empty()) {
-    out_ << " |";
-    path_output_.WriteFiles(out_, implicit_deps);
-  }
-
-  // Append data dependencies as order-only dependencies.
-  //
-  // This will include data dependencies and input dependencies (like when
-  // this target depends on an action). Having the data dependencies in this
-  // list ensures that the data is available at runtime when the user builds
-  // this target.
-  //
-  // The action dependencies are not strictly necessary in this case. They
-  // should also have been collected via the input deps stamp that each source
-  // file has for an order-only dependency, and since this target depends on
-  // the sources, there is already an implicit order-only dependency. However,
-  // it's extra work to separate these out and there's no disadvantage to
-  // listing them again.
-  WriteOrderOnlyDependencies(non_linkable_deps);
-
-  // End of the link "build" line.
-  out_ << std::endl;
-
-  // The remaining things go in the inner scope of the link line.
-  if (target_->output_type() == Target::EXECUTABLE ||
-      target_->output_type() == Target::SHARED_LIBRARY ||
-      target_->output_type() == Target::LOADABLE_MODULE) {
-    WriteLinkerFlags(optional_def_file);
-    WriteLibs();
-  } else if (target_->output_type() == Target::STATIC_LIBRARY) {
-    out_ << "  arflags =";
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
-                                         GetFlagOptions(), out_);
-    out_ << std::endl;
-  }
-  WriteOutputSubstitutions();
-  WriteSolibs(solibs);
-}
-
-void NinjaBinaryTargetWriter::WriteLinkerFlags(
-    const SourceFile* optional_def_file) {
-  out_ << "  ldflags =";
-
-  // First the ldflags from the target and its config.
-  RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
-                                       GetFlagOptions(), out_);
-
-  // Followed by library search paths that have been recursively pushed
-  // through the dependency tree.
-  const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
-  if (!all_lib_dirs.empty()) {
-    // Since we're passing these on the command line to the linker and not
-    // to Ninja, we need to do shell escaping.
-    PathOutput lib_path_output(path_output_.current_dir(),
-                               settings_->build_settings()->root_path_utf8(),
-                               ESCAPE_NINJA_COMMAND);
-    for (size_t i = 0; i < all_lib_dirs.size(); i++) {
-      out_ << " " << tool_->lib_dir_switch();
-      lib_path_output.WriteDir(out_, all_lib_dirs[i],
-                               PathOutput::DIR_NO_LAST_SLASH);
-    }
-  }
-
-  if (optional_def_file) {
-    out_ << " /DEF:";
-    path_output_.WriteFile(out_, *optional_def_file);
-  }
-
-  out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteLibs() {
-  out_ << "  libs =";
-
-  // Libraries that have been recursively pushed through the dependency tree.
-  EscapeOptions lib_escape_opts;
-  lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
-  const OrderedSet<LibFile> all_libs = target_->all_libs();
-  const std::string framework_ending(".framework");
-  for (size_t i = 0; i < all_libs.size(); i++) {
-    const LibFile& lib_file = all_libs[i];
-    const std::string& lib_value = lib_file.value();
-    if (lib_file.is_source_file()) {
-      out_ << " ";
-      path_output_.WriteFile(out_, lib_file.source_file());
-    } else if (base::EndsWith(lib_value, framework_ending,
-                              base::CompareCase::INSENSITIVE_ASCII)) {
-      // Special-case libraries ending in ".framework" to support Mac: Add the
-      // -framework switch and don't add the extension to the output.
-      out_ << " -framework ";
-      EscapeStringToStream(
-          out_, lib_value.substr(0, lib_value.size() - framework_ending.size()),
-          lib_escape_opts);
-    } else {
-      out_ << " " << tool_->lib_switch();
-      EscapeStringToStream(out_, lib_value, lib_escape_opts);
-    }
-  }
-  out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteOutputSubstitutions() {
-  out_ << "  output_extension = "
-       << SubstitutionWriter::GetLinkerSubstitution(
-              target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
-  out_ << std::endl;
-  out_ << "  output_dir = "
-       << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
-                                                    SUBSTITUTION_OUTPUT_DIR);
-  out_ << std::endl;
-}
-
-void NinjaBinaryTargetWriter::WriteSolibs(
-    const std::vector<OutputFile>& solibs) {
-  if (solibs.empty())
-    return;
-
-  out_ << "  solibs =";
-  path_output_.WriteFiles(out_, solibs);
-  out_ << std::endl;
-}
-
-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 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 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)
-      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 NinjaBinaryTargetWriter::WriteOrderOnlyDependencies(
-    const UniqueVector<const Target*>& non_linkable_deps) {
-  if (!non_linkable_deps.empty()) {
-    out_ << " ||";
-
-    // Non-linkable targets.
-    for (auto* non_linkable_dep : non_linkable_deps) {
-      out_ << " ";
-      path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
-    }
-  }
-}
-
-bool NinjaBinaryTargetWriter::CheckForDuplicateObjectFiles(
-    const std::vector<OutputFile>& files) const {
-  std::unordered_set<std::string> set;
-  for (const auto& file : files) {
-    if (!set.insert(file.value()).second) {
-      Err err(
-          target_->defined_from(), "Duplicate object file",
-          "The target " + target_->label().GetUserVisibleName(false) +
-              "\ngenerates two object files with the same name:\n  " +
-              file.value() +
-              "\n"
-              "\n"
-              "It could be you accidentally have a file listed twice in the\n"
-              "sources. Or, depending on how your toolchain maps sources to\n"
-              "object files, two source files with the same name in different\n"
-              "directories could map to the same object file.\n"
-              "\n"
-              "In the latter case, either rename one of the files or move one "
-              "of\n"
-              "the sources to a separate source_set to avoid them both being "
-              "in\n"
-              "the same target.");
-      g_scheduler->FailWithError(err);
-      return false;
-    }
-  }
-  return true;
-}
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h
index 8a31219..d450ce2 100644
--- a/tools/gn/ninja_binary_target_writer.h
+++ b/tools/gn/ninja_binary_target_writer.h
@@ -19,119 +19,32 @@
 // library, or a static library).
 class NinjaBinaryTargetWriter : public NinjaTargetWriter {
  public:
-  class SourceFileTypeSet;
+  // Represents a set of tool types.
+  class SourceFileTypeSet {
+   public:
+    SourceFileTypeSet() {
+      memset(flags_, 0, sizeof(bool) * static_cast<int>(SOURCE_NUMTYPES));
+    }
+
+    void Set(SourceFileType type) { flags_[static_cast<int>(type)] = true; }
+    bool Get(SourceFileType type) const {
+      return flags_[static_cast<int>(type)];
+    }
+
+    bool CSourceUsed();
+
+   private:
+    bool flags_[static_cast<int>(SOURCE_NUMTYPES)];
+  };
 
   NinjaBinaryTargetWriter(const Target* target, std::ostream& out);
   ~NinjaBinaryTargetWriter() override;
 
   void Run() override;
 
- private:
+ protected:
   typedef std::set<OutputFile> OutputFileSet;
 
-  // Writes all flags for the compiler: includes, defines, cflags, etc.
-  void WriteCompilerVars(const SourceFileTypeSet& used_types);
-
-  // 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
-  // appended to |other_files|).
-  //
-  // input_dep is the stamp file collecting the dependencies required before
-  // compiling this target. It will be empty if there are no input deps.
-  void WritePCHCommands(const SourceFileTypeSet& used_types,
-                        const OutputFile& input_dep,
-                        const std::vector<OutputFile>& order_only_deps,
-                        std::vector<OutputFile>* object_files,
-                        std::vector<OutputFile>* other_files);
-
-  // Writes a .pch compile build line for a language type.
-  void WritePCHCommand(SubstitutionType flag_type,
-                       const char* tool_name,
-                       CTool::PrecompiledHeaderType header_type,
-                       const OutputFile& input_dep,
-                       const std::vector<OutputFile>& order_only_deps,
-                       std::vector<OutputFile>* object_files,
-                       std::vector<OutputFile>* other_files);
-
-  void WriteGCCPCHCommand(SubstitutionType flag_type,
-                          const char* tool_name,
-                          const OutputFile& input_dep,
-                          const std::vector<OutputFile>& order_only_deps,
-                          std::vector<OutputFile>* gch_files);
-
-  void WriteWindowsPCHCommand(SubstitutionType flag_type,
-                              const char* tool_name,
-                              const OutputFile& input_dep,
-                              const std::vector<OutputFile>& order_only_deps,
-                              std::vector<OutputFile>* object_files);
-
-  // pch_deps are additional dependencies to run before the rule. They are
-  // expected to abide by the naming conventions specified by GetPCHOutputFiles.
-  //
-  // order_only_dep are the dependencies that must be run before doing any
-  // compiles.
-  //
-  // The files produced by the compiler will be added to two output vectors.
-  void WriteSources(const std::vector<OutputFile>& pch_deps,
-                    const OutputFile& input_dep,
-                    const std::vector<OutputFile>& order_only_deps,
-                    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);
-  void WriteLinkerFlags(const SourceFile* optional_def_file);
-  void WriteLibs();
-  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.
-  //
-  // The order-only dependencies are the non-linkable deps passed in as an
-  // argument, plus the data file depdencies in the target.
-  void WriteOrderOnlyDependencies(
-      const UniqueVector<const Target*>& non_linkable_deps);
-
-  // Checks for duplicates in the given list of output files. If any duplicates
-  // are found, throws an error and return false.
-  bool CheckForDuplicateObjectFiles(const std::vector<OutputFile>& files) const;
-
-  const CTool* tool_;
-
   // Cached version of the prefix used for rule types for this toolchain.
   std::string rule_prefix_;
 
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index f066536..092f637 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -4,22 +4,13 @@
 
 #include "tools/gn/ninja_binary_target_writer.h"
 
-#include <memory>
-#include <sstream>
-#include <utility>
-
-#include "tools/gn/config.h"
-#include "tools/gn/ninja_target_command_util.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 NinjaBinaryTargetWriterTest = TestWithScheduler;
 
-TEST_F(NinjaBinaryTargetWriterTest, SourceSet) {
+TEST_F(NinjaBinaryTargetWriterTest, CSources) {
   Err err;
   TestWithScope setup;
 
@@ -35,156 +26,6 @@
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
-  // Source set itself.
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = bar\n"
-        "\n"
-        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
-        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-
-  // A shared library that depends on the source set.
-  Target shlib_target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
-  shlib_target.set_output_type(Target::SHARED_LIBRARY);
-  shlib_target.public_deps().push_back(LabelTargetPair(&target));
-  shlib_target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(shlib_target.OnResolved(&err));
-
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&shlib_target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libshlib\n"
-        "\n"
-        "\n"
-        // Ordering of the obj files here should come out in the order
-        // specified, with the target's first, followed by the source set's, in
-        // order.
-        "build ./libshlib.so: solink obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
-        "|| obj/foo/bar.stamp\n"
-        "  ldflags =\n"
-        "  libs =\n"
-        "  output_extension = .so\n"
-        "  output_dir = \n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-
-  // A static library that depends on the source set (should not link it).
-  Target stlib_target(setup.settings(), Label(SourceDir("//foo/"), "stlib"));
-  stlib_target.set_output_type(Target::STATIC_LIBRARY);
-  stlib_target.public_deps().push_back(LabelTargetPair(&target));
-  stlib_target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(stlib_target.OnResolved(&err));
-
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&stlib_target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libstlib\n"
-        "\n"
-        "\n"
-        // There are no sources so there are no params to alink. (In practice
-        // this will probably fail in the archive tool.)
-        "build obj/foo/libstlib.a: alink || obj/foo/bar.stamp\n"
-        "  arflags =\n"
-        "  output_extension = \n"
-        "  output_dir = \n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-
-  // Make the static library 'complete', which means it should be linked.
-  stlib_target.set_complete_static_lib(true);
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&stlib_target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libstlib\n"
-        "\n"
-        "\n"
-        // Ordering of the obj files here should come out in the order
-        // specified, with the target's first, followed by the source set's, in
-        // order.
-        "build obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
-        "|| obj/foo/bar.stamp\n"
-        "  arflags =\n"
-        "  output_extension = \n"
-        "  output_dir = \n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, EscapeDefines) {
-  TestWithScope setup;
-  Err err;
-
-  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
-  target.config_values().defines().push_back("BOOL_DEF");
-  target.config_values().defines().push_back("INT_DEF=123");
-  target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expectedSubstr[] =
-#if defined(OS_WIN)
-      "defines = -DBOOL_DEF -DINT_DEF=123 \"-DSTR_DEF=\\\"ABCD-1\\\"\"";
-#else
-      "defines = -DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\"ABCD-1\\\"";
-#endif
-  std::string out_str = out.str();
-  EXPECT_TRUE(out_str.find(out_str) != std::string::npos);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, StaticLibrary) {
-  TestWithScope setup;
-  Err err;
-
-  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
-  target.sources().push_back(SourceFile("//foo/input1.cc"));
-  target.config_values().arflags().push_back("--asdf");
-  ASSERT_TRUE(target.OnResolved(&err));
-
   std::ostringstream out;
   NinjaBinaryTargetWriter writer(&target, out);
   writer.Run();
@@ -196,964 +37,13 @@
       "cflags_cc =\n"
       "root_out_dir = .\n"
       "target_out_dir = obj/foo\n"
-      "target_output_name = libbar\n"
+      "target_output_name = bar\n"
       "\n"
-      "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
       "\n"
-      "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o\n"
-      "  arflags = --asdf\n"
-      "  output_extension = \n"
-      "  output_dir = \n";
+      "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+      "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
-
-TEST_F(NinjaBinaryTargetWriterTest, CompleteStaticLibrary) {
-  TestWithScope setup;
-  Err err;
-
-  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
-  target.sources().push_back(SourceFile("//foo/input1.cc"));
-  target.config_values().arflags().push_back("--asdf");
-  target.set_complete_static_lib(true);
-
-  TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
-  baz.sources().push_back(SourceFile("//foo/input2.cc"));
-
-  target.public_deps().push_back(LabelTargetPair(&baz));
-
-  ASSERT_TRUE(target.OnResolved(&err));
-  ASSERT_TRUE(baz.OnResolved(&err));
-
-  // A complete static library that depends on an incomplete static library
-  // should link in the dependent object files as if the dependent target
-  // were a source set.
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libbar\n"
-        "\n"
-        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
-        "\n"
-        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
-        "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
-        "  arflags = --asdf\n"
-        "  output_extension = \n"
-        "  output_dir = \n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-
-  // Make the dependent static library complete.
-  baz.set_complete_static_lib(true);
-
-  // Dependent complete static libraries should not be linked directly.
-  {
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libbar\n"
-        "\n"
-        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
-        "\n"
-        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
-        "|| obj/foo/libbaz.a\n"
-        "  arflags = --asdf\n"
-        "  output_extension = \n"
-        "  output_dir = \n";
-    std::string out_str = out.str();
-    EXPECT_EQ(expected, out_str);
-  }
-}
-
-// This tests that output extension and output dir overrides apply, and input
-// dependencies are applied.
-TEST_F(NinjaBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
-  Err err;
-  TestWithScope setup;
-
-  // An action for our library to depend on.
-  Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
-  action.set_output_type(Target::ACTION_FOREACH);
-  action.visibility().SetPublic();
-  action.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(action.OnResolved(&err));
-
-  // A shared library w/ the output_extension set to a custom value.
-  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
-  target.set_output_type(Target::SHARED_LIBRARY);
-  target.set_output_extension(std::string("so.6"));
-  target.set_output_dir(SourceDir("//out/Debug/foo/"));
-  target.sources().push_back(SourceFile("//foo/input1.cc"));
-  target.sources().push_back(SourceFile("//foo/input2.cc"));
-  target.public_deps().push_back(LabelTargetPair(&action));
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = libshlib\n"
-      "\n"
-      "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
-      " || obj/foo/action.stamp\n"
-      "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
-      " || obj/foo/action.stamp\n"
-      "\n"
-      "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
-      // The order-only dependency here is stricly unnecessary since the
-      // sources list this as an order-only dep. See discussion in the code
-      // that writes this.
-      "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = .so.6\n"
-      "  output_dir = foo\n";
-
-  std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
-  Err err;
-  TestWithScope setup;
-
-  SourceFile generated_file("//out/Debug/generated.cc");
-
-  // An action does code generation.
-  Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
-  action.set_output_type(Target::ACTION);
-  action.visibility().SetPublic();
-  action.SetToolchain(setup.toolchain());
-  action.set_output_dir(SourceDir("//out/Debug/foo/"));
-  action.action_values().outputs() =
-      SubstitutionList::MakeForTest("//out/Debug/generated.cc");
-  ASSERT_TRUE(action.OnResolved(&err));
-
-  // A source set compiling geneated code, this target does not publicize any
-  // headers.
-  Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
-  gen_obj.set_output_type(Target::SOURCE_SET);
-  gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
-  gen_obj.sources().push_back(generated_file);
-  gen_obj.visibility().SetPublic();
-  gen_obj.private_deps().push_back(LabelTargetPair(&action));
-  gen_obj.set_all_headers_public(false);
-  gen_obj.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(gen_obj.OnResolved(&err));
-
-  std::ostringstream obj_out;
-  NinjaBinaryTargetWriter obj_writer(&gen_obj, obj_out);
-  obj_writer.Run();
-
-  const char obj_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = gen_obj\n"
-      "\n"
-      "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
-      " || obj/foo/generate.stamp\n"
-      "\n"
-      "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
-      // The order-only dependency here is strictly unnecessary since the
-      // sources list this as an order-only dep.
-      " || obj/foo/generate.stamp\n";
-
-  std::string obj_str = obj_out.str();
-  EXPECT_EQ(obj_expected, obj_str);
-
-  // A shared library depends on gen_obj, having corresponding header for
-  // generated obj.
-  Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
-  gen_lib.set_output_type(Target::SHARED_LIBRARY);
-  gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
-  gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
-  gen_lib.visibility().SetPublic();
-  gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
-  gen_lib.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(gen_lib.OnResolved(&err));
-
-  std::ostringstream lib_out;
-  NinjaBinaryTargetWriter lib_writer(&gen_lib, lib_out);
-  lib_writer.Run();
-
-  const char lib_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = libgen_lib\n"
-      "\n"
-      "\n"
-      "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
-      // The order-only dependency here is strictly unnecessary since
-      // obj/out/Debug/gen_obj.generated.o has dependency to
-      // obj/foo/gen_obj.stamp
-      " || obj/foo/gen_obj.stamp\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = .so\n"
-      "  output_dir = foo\n";
-
-  std::string lib_str = lib_out.str();
-  EXPECT_EQ(lib_expected, lib_str);
-
-  // An executable depends on gen_lib.
-  Target executable(setup.settings(),
-                    Label(SourceDir("//foo/"), "final_target"));
-  executable.set_output_type(Target::EXECUTABLE);
-  executable.set_output_dir(SourceDir("//out/Debug/foo/"));
-  executable.sources().push_back(SourceFile("//foo/main.cc"));
-  executable.private_deps().push_back(LabelTargetPair(&gen_lib));
-  executable.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
-
-  std::ostringstream final_out;
-  NinjaBinaryTargetWriter final_writer(&executable, final_out);
-  final_writer.Run();
-
-  // There is no order only dependency to action target.
-  const char final_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = final_target\n"
-      "\n"
-      "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
-      "\n"
-      "build ./final_target: link obj/foo/final_target.main.o"
-      " ./libgen_lib.so\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = \n"
-      "  output_dir = foo\n";
-
-  std::string final_str = final_out.str();
-  EXPECT_EQ(final_expected, final_str);
-}
-
-// Tests libs are applied.
-TEST_F(NinjaBinaryTargetWriterTest, LibsAndLibDirs) {
-  Err err;
-  TestWithScope setup;
-
-  // A shared library w/ libs and lib_dirs.
-  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
-  target.set_output_type(Target::SHARED_LIBRARY);
-  target.config_values().libs().push_back(LibFile(SourceFile("//foo/lib1.a")));
-  target.config_values().libs().push_back(LibFile("foo"));
-  target.config_values().lib_dirs().push_back(SourceDir("//foo/bar/"));
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = libshlib\n"
-      "\n"
-      "\n"
-      "build ./libshlib.so: solink | ../../foo/lib1.a\n"
-      "  ldflags = -L../../foo/bar\n"
-      "  libs = ../../foo/lib1.a -lfoo\n"
-      "  output_extension = .so\n"
-      "  output_dir = \n";
-
-  std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, EmptyOutputExtension) {
-  Err err;
-  TestWithScope setup;
-
-  // This test is the same as OutputExtensionAndInputDeps, except that we call
-  // set_output_extension("") and ensure that we get an empty one and override
-  // the output prefix so that the name matches the target exactly.
-  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
-  target.set_output_type(Target::SHARED_LIBRARY);
-  target.set_output_prefix_override(true);
-  target.set_output_extension(std::string());
-  target.sources().push_back(SourceFile("//foo/input1.cc"));
-  target.sources().push_back(SourceFile("//foo/input2.cc"));
-
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = shlib\n"
-      "\n"
-      "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
-      "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
-      "\n"
-      "build ./shlib: solink obj/foo/shlib.input1.o "
-      "obj/foo/shlib.input2.o\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = \n"
-      "  output_dir = \n";
-
-  std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str);
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, SourceSetDataDeps) {
-  Err err;
-  TestWithScope setup;
-
-  // This target is a data (runtime) dependency of the intermediate target.
-  Target data(setup.settings(), Label(SourceDir("//foo/"), "data_target"));
-  data.set_output_type(Target::EXECUTABLE);
-  data.visibility().SetPublic();
-  data.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(data.OnResolved(&err));
-
-  // Intermediate source set target.
-  Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
-  inter.set_output_type(Target::SOURCE_SET);
-  inter.visibility().SetPublic();
-  inter.data_deps().push_back(LabelTargetPair(&data));
-  inter.SetToolchain(setup.toolchain());
-  inter.sources().push_back(SourceFile("//foo/inter.cc"));
-  ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
-
-  // Write out the intermediate target.
-  std::ostringstream inter_out;
-  NinjaBinaryTargetWriter inter_writer(&inter, inter_out);
-  inter_writer.Run();
-
-  // The intermediate source set will be a stamp file that depends on the
-  // object files, and will have an order-only dependency on its data dep and
-  // data file.
-  const char inter_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = inter\n"
-      "\n"
-      "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
-      "\n"
-      "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
-      "./data_target\n";
-  EXPECT_EQ(inter_expected, inter_out.str());
-
-  // Final target.
-  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
-  exe.set_output_type(Target::EXECUTABLE);
-  exe.public_deps().push_back(LabelTargetPair(&inter));
-  exe.SetToolchain(setup.toolchain());
-  exe.sources().push_back(SourceFile("//foo/final.cc"));
-  ASSERT_TRUE(exe.OnResolved(&err));
-
-  std::ostringstream final_out;
-  NinjaBinaryTargetWriter final_writer(&exe, final_out);
-  final_writer.Run();
-
-  // The final output depends on both object files (one from the final target,
-  // one from the source set) and has an order-only dependency on the source
-  // set's stamp file and the final target's data file. The source set stamp
-  // dependency will create an implicit order-only dependency on the data
-  // target.
-  const char final_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = exe\n"
-      "\n"
-      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
-      "\n"
-      "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
-      "obj/foo/inter.stamp\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = \n"
-      "  output_dir = \n";
-  EXPECT_EQ(final_expected, final_out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
-  Err err;
-  TestWithScope setup;
-
-  Target shared_lib(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  shared_lib.set_output_type(Target::SHARED_LIBRARY);
-  shared_lib.SetToolchain(setup.toolchain());
-  shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
-  shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
-  ASSERT_TRUE(shared_lib.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&shared_lib, out);
-  writer.Run();
-
-  const char expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = libbar\n"
-      "\n"
-      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
-      "\n"
-      "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
-      "  ldflags = /DEF:../../foo/bar.def\n"
-      "  libs =\n"
-      "  output_extension = .so\n"
-      "  output_dir = \n";
-  EXPECT_EQ(expected, out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, LoadableModule) {
-  Err err;
-  TestWithScope setup;
-
-  Target loadable_module(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-  loadable_module.set_output_type(Target::LOADABLE_MODULE);
-  loadable_module.visibility().SetPublic();
-  loadable_module.SetToolchain(setup.toolchain());
-  loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
-  ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&loadable_module, out);
-  writer.Run();
-
-  const char loadable_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = libbar\n"
-      "\n"
-      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
-      "\n"
-      "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = .so\n"
-      "  output_dir = \n";
-  EXPECT_EQ(loadable_expected, out.str());
-
-  // Final target.
-  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
-  exe.set_output_type(Target::EXECUTABLE);
-  exe.public_deps().push_back(LabelTargetPair(&loadable_module));
-  exe.SetToolchain(setup.toolchain());
-  exe.sources().push_back(SourceFile("//foo/final.cc"));
-  ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
-
-  std::ostringstream final_out;
-  NinjaBinaryTargetWriter final_writer(&exe, final_out);
-  final_writer.Run();
-
-  // The final output depends on the loadable module so should have an
-  // order-only dependency on the loadable modules's output file.
-  const char final_expected[] =
-      "defines =\n"
-      "include_dirs =\n"
-      "cflags =\n"
-      "cflags_cc =\n"
-      "root_out_dir = .\n"
-      "target_out_dir = obj/foo\n"
-      "target_output_name = exe\n"
-      "\n"
-      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
-      "\n"
-      "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
-      "  ldflags =\n"
-      "  libs =\n"
-      "  output_extension = \n"
-      "  output_dir = \n";
-  EXPECT_EQ(final_expected, final_out.str());
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, WinPrecompiledHeaders) {
-  Err err;
-
-  // This setup's toolchain does not have precompiled headers defined.
-  TestWithScope setup;
-
-  // A precompiled header toolchain.
-  Settings pch_settings(setup.build_settings(), "withpch/");
-  Toolchain pch_toolchain(&pch_settings,
-                          Label(SourceDir("//toolchain/"), "withpch"));
-  pch_settings.set_toolchain_label(pch_toolchain.label());
-  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
-
-  // Declare a C++ compiler that supports PCH.
-  std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
-  CTool* cxx_tool = cxx->AsC();
-  TestWithScope::SetCommandForTool(
-      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
-      "-o {{output}}",
-      cxx_tool);
-  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
-  cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
-  pch_toolchain.SetTool(std::move(cxx));
-
-  // Add a C compiler as well.
-  std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
-  CTool* cc_tool = cc->AsC();
-  TestWithScope::SetCommandForTool(
-      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
-      "-o {{output}}",
-      cc_tool);
-  cc_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
-  cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
-  pch_toolchain.SetTool(std::move(cc));
-  pch_toolchain.ToolchainSetupComplete();
-
-  // This target doesn't specify precompiled headers.
-  {
-    Target no_pch_target(&pch_settings,
-                         Label(SourceDir("//foo/"), "no_pch_target"));
-    no_pch_target.set_output_type(Target::SOURCE_SET);
-    no_pch_target.visibility().SetPublic();
-    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
-    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
-    no_pch_target.config_values().cflags_c().push_back("-std=c99");
-    no_pch_target.SetToolchain(&pch_toolchain);
-    ASSERT_TRUE(no_pch_target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&no_pch_target, out);
-    writer.Run();
-
-    const char no_pch_expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_c = -std=c99\n"
-        "cflags_cc =\n"
-        "target_output_name = no_pch_target\n"
-        "\n"
-        "build withpch/obj/foo/no_pch_target.input1.o: "
-        "withpch_cxx ../../foo/input1.cc\n"
-        "build withpch/obj/foo/no_pch_target.input2.o: "
-        "withpch_cc ../../foo/input2.c\n"
-        "\n"
-        "build withpch/obj/foo/no_pch_target.stamp: "
-        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
-        "withpch/obj/foo/no_pch_target.input2.o\n";
-    EXPECT_EQ(no_pch_expected, out.str());
-  }
-
-  // This target specifies PCH.
-  {
-    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
-    pch_target.config_values().set_precompiled_header("build/precompile.h");
-    pch_target.config_values().set_precompiled_source(
-        SourceFile("//build/precompile.cc"));
-    pch_target.set_output_type(Target::SOURCE_SET);
-    pch_target.visibility().SetPublic();
-    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
-    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
-    pch_target.SetToolchain(&pch_toolchain);
-    ASSERT_TRUE(pch_target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&pch_target, out);
-    writer.Run();
-
-    const char pch_win_expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        // It should output language-specific pch files.
-        "cflags_c = /Fpwithpch/obj/foo/pch_target_c.pch "
-        "/Yubuild/precompile.h\n"
-        "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch "
-        "/Yubuild/precompile.h\n"
-        "target_output_name = pch_target\n"
-        "\n"
-        // Compile the precompiled source files with /Yc.
-        "build withpch/obj/build/pch_target.precompile.c.o: "
-        "withpch_cc ../../build/precompile.cc\n"
-        "  cflags_c = ${cflags_c} /Ycbuild/precompile.h\n"
-        "\n"
-        "build withpch/obj/build/pch_target.precompile.cc.o: "
-        "withpch_cxx ../../build/precompile.cc\n"
-        "  cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n"
-        "\n"
-        "build withpch/obj/foo/pch_target.input1.o: "
-        "withpch_cxx ../../foo/input1.cc | "
-        // Explicit dependency on the PCH build step.
-        "withpch/obj/build/pch_target.precompile.cc.o\n"
-        "build withpch/obj/foo/pch_target.input2.o: "
-        "withpch_cc ../../foo/input2.c | "
-        // Explicit dependency on the PCH build step.
-        "withpch/obj/build/pch_target.precompile.c.o\n"
-        "\n"
-        "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
-        "withpch/obj/foo/pch_target.input1.o "
-        "withpch/obj/foo/pch_target.input2.o "
-        // The precompiled object files were added to the outputs.
-        "withpch/obj/build/pch_target.precompile.c.o "
-        "withpch/obj/build/pch_target.precompile.cc.o\n";
-    EXPECT_EQ(pch_win_expected, out.str()) << pch_win_expected << "--BREAK--" << out.str();
-  }
-}
-
-TEST_F(NinjaBinaryTargetWriterTest, GCCPrecompiledHeaders) {
-  Err err;
-
-  // This setup's toolchain does not have precompiled headers defined.
-  TestWithScope setup;
-
-  // A precompiled header toolchain.
-  Settings pch_settings(setup.build_settings(), "withpch/");
-  Toolchain pch_toolchain(&pch_settings,
-                          Label(SourceDir("//toolchain/"), "withpch"));
-  pch_settings.set_toolchain_label(pch_toolchain.label());
-  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
-
-  // Declare a C++ compiler that supports PCH.
-  std::unique_ptr<Tool> cxx = Tool::CreateTool(CTool::kCToolCxx);
-  CTool* cxx_tool = cxx->AsC();
-  TestWithScope::SetCommandForTool(
-      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
-      "-o {{output}}",
-      cxx_tool);
-  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
-  cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
-  pch_toolchain.SetTool(std::move(cxx));
-  pch_toolchain.ToolchainSetupComplete();
-
-  // Add a C compiler as well.
-  std::unique_ptr<Tool> cc = Tool::CreateTool(CTool::kCToolCc);
-  CTool* cc_tool = cc->AsC();
-  TestWithScope::SetCommandForTool(
-      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
-      "-o {{output}}",
-      cc_tool);
-  cc_tool->set_outputs(SubstitutionList::MakeForTest(
-      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
-  cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
-  pch_toolchain.SetTool(std::move(cc));
-  pch_toolchain.ToolchainSetupComplete();
-
-  // This target doesn't specify precompiled headers.
-  {
-    Target no_pch_target(&pch_settings,
-                         Label(SourceDir("//foo/"), "no_pch_target"));
-    no_pch_target.set_output_type(Target::SOURCE_SET);
-    no_pch_target.visibility().SetPublic();
-    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
-    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
-    no_pch_target.config_values().cflags_c().push_back("-std=c99");
-    no_pch_target.SetToolchain(&pch_toolchain);
-    ASSERT_TRUE(no_pch_target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&no_pch_target, out);
-    writer.Run();
-
-    const char no_pch_expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_c = -std=c99\n"
-        "cflags_cc =\n"
-        "target_output_name = no_pch_target\n"
-        "\n"
-        "build withpch/obj/foo/no_pch_target.input1.o: "
-        "withpch_cxx ../../foo/input1.cc\n"
-        "build withpch/obj/foo/no_pch_target.input2.o: "
-        "withpch_cc ../../foo/input2.c\n"
-        "\n"
-        "build withpch/obj/foo/no_pch_target.stamp: "
-        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
-        "withpch/obj/foo/no_pch_target.input2.o\n";
-    EXPECT_EQ(no_pch_expected, out.str());
-  }
-
-  // This target specifies PCH.
-  {
-    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
-    pch_target.config_values().set_precompiled_source(
-        SourceFile("//build/precompile.h"));
-    pch_target.config_values().cflags_c().push_back("-std=c99");
-    pch_target.set_output_type(Target::SOURCE_SET);
-    pch_target.visibility().SetPublic();
-    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
-    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
-    pch_target.SetToolchain(&pch_toolchain);
-    ASSERT_TRUE(pch_target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&pch_target, out);
-    writer.Run();
-
-    const char pch_gcc_expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_c = -std=c99 "
-        "-include withpch/obj/build/pch_target.precompile.h-c\n"
-        "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n"
-        "target_output_name = pch_target\n"
-        "\n"
-        // Compile the precompiled sources with -x <lang>.
-        "build withpch/obj/build/pch_target.precompile.h-c.gch: "
-        "withpch_cc ../../build/precompile.h\n"
-        "  cflags_c = -std=c99 -x c-header\n"
-        "\n"
-        "build withpch/obj/build/pch_target.precompile.h-cc.gch: "
-        "withpch_cxx ../../build/precompile.h\n"
-        "  cflags_cc = -x c++-header\n"
-        "\n"
-        "build withpch/obj/foo/pch_target.input1.o: "
-        "withpch_cxx ../../foo/input1.cc | "
-        // Explicit dependency on the PCH build step.
-        "withpch/obj/build/pch_target.precompile.h-cc.gch\n"
-        "build withpch/obj/foo/pch_target.input2.o: "
-        "withpch_cc ../../foo/input2.c | "
-        // Explicit dependency on the PCH build step.
-        "withpch/obj/build/pch_target.precompile.h-c.gch\n"
-        "\n"
-        "build withpch/obj/foo/pch_target.stamp: "
-        "withpch_stamp withpch/obj/foo/pch_target.input1.o "
-        "withpch/obj/foo/pch_target.input2.o\n";
-    EXPECT_EQ(pch_gcc_expected, out.str());
-  }
-}
-
-// Should throw an error with the scheduler if a duplicate object file exists.
-// This is dependent on the toolchain's object file mapping.
-TEST_F(NinjaBinaryTargetWriterTest, DupeObjFileError) {
-  TestWithScope setup;
-  TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
-  target.sources().push_back(SourceFile("//a.cc"));
-  target.sources().push_back(SourceFile("//a.cc"));
-
-  EXPECT_FALSE(scheduler().is_failed());
-
-  scheduler().SuppressOutputForTesting(true);
-
-  std::ostringstream out;
-  NinjaBinaryTargetWriter writer(&target, out);
-  writer.Run();
-
-  scheduler().SuppressOutputForTesting(false);
-
-  // Should have issued an error.
-  EXPECT_TRUE(scheduler().is_failed());
-}
-
-// This tests that output extension and output dir overrides apply, and input
-// dependencies are applied.
-TEST_F(NinjaBinaryTargetWriterTest, InputFiles) {
-  Err err;
-  TestWithScope setup;
-
-  // This target has one input.
-  {
-    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.cc"));
-    target.sources().push_back(SourceFile("//foo/input2.cc"));
-    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
-    target.SetToolchain(setup.toolchain());
-    ASSERT_TRUE(target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = bar\n"
-        "\n"
-        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
-        " | ../../foo/input.data\n"
-        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
-        " | ../../foo/input.data\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o\n";
-
-    EXPECT_EQ(expected, out.str());
-  }
-
-  // This target has one input but no source files.
-  {
-    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
-    target.set_output_type(Target::SHARED_LIBRARY);
-    target.visibility().SetPublic();
-    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
-    target.SetToolchain(setup.toolchain());
-    ASSERT_TRUE(target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = libbar\n"
-        "\n"
-        "\n"
-        "build ./libbar.so: solink | ../../foo/input.data\n"
-        "  ldflags =\n"
-        "  libs =\n"
-        "  output_extension = .so\n"
-        "  output_dir = \n";
-
-    EXPECT_EQ(expected, out.str());
-  }
-
-  // This target has multiple inputs.
-  {
-    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.cc"));
-    target.sources().push_back(SourceFile("//foo/input2.cc"));
-    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
-    target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
-    target.SetToolchain(setup.toolchain());
-    ASSERT_TRUE(target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = bar\n"
-        "\n"
-        "build obj/foo/bar.inputs.stamp: stamp"
-        " ../../foo/input1.data ../../foo/input2.data\n"
-        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
-        " | obj/foo/bar.inputs.stamp\n"
-        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
-        " | obj/foo/bar.inputs.stamp\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o\n";
-
-    EXPECT_EQ(expected, out.str());
-  }
-
-  // This target has one input itself, one from an immediate config, and one
-  // from a config tacked on to said config.
-  {
-    Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
-    far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
-    ASSERT_TRUE(far_config.OnResolved(&err));
-
-    Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
-    config.own_values().inputs().push_back(SourceFile("//foo/input2.data"));
-    config.configs().push_back(LabelConfigPair(&far_config));
-    ASSERT_TRUE(config.OnResolved(&err));
-
-    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.cc"));
-    target.sources().push_back(SourceFile("//foo/input2.cc"));
-    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
-    target.configs().push_back(LabelConfigPair(&config));
-    target.SetToolchain(setup.toolchain());
-    ASSERT_TRUE(target.OnResolved(&err));
-
-    std::ostringstream out;
-    NinjaBinaryTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected[] =
-        "defines =\n"
-        "include_dirs =\n"
-        "cflags =\n"
-        "cflags_cc =\n"
-        "root_out_dir = .\n"
-        "target_out_dir = obj/foo\n"
-        "target_output_name = bar\n"
-        "\n"
-        "build obj/foo/bar.inputs.stamp: stamp"
-        " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
-        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
-        " | obj/foo/bar.inputs.stamp\n"
-        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
-        " | obj/foo/bar.inputs.stamp\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
-        "obj/foo/bar.input2.o\n";
-
-    EXPECT_EQ(expected, out.str());
-  }
-}
diff --git a/tools/gn/ninja_c_binary_target_writer.cc b/tools/gn/ninja_c_binary_target_writer.cc
new file mode 100644
index 0000000..b96e724
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer.cc
@@ -0,0 +1,875 @@
+// 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_c_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/general_tool.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/ninja_target_command_util.h"
+#include "tools/gn/ninja_utils.h"
+#include "tools/gn/scheduler.h"
+#include "tools/gn/settings.h"
+#include "tools/gn/source_file_type.h"
+#include "tools/gn/string_utils.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;
+}
+
+// Returns the language-specific lang recognized by gcc’s -x flag for
+// precompiled header files.
+const char* GetPCHLangForToolType(const char* name) {
+  if (name == CTool::kCToolCc)
+      return "c-header";
+  if (name == CTool::kCToolCxx)
+      return "c++-header";
+  if (name == CTool::kCToolObjC)
+      return "objective-c-header";
+  if (name == CTool::kCToolObjCxx)
+      return "objective-c++-header";
+      NOTREACHED() << "Not a valid PCH tool type: " << name;
+      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.
+  NinjaBinaryTargetWriter::SourceFileTypeSet used_types;
+
+  // 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]);
+
+    used_types.Set(GetSourceFileType(source));
+  }
+
+  // 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 (used_types.Get(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 (used_types.Get(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 (used_types.Get(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 (used_types.Get(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,
+                                                   std::ostream& out)
+    : NinjaBinaryTargetWriter(target, out),
+      tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)) {}
+
+NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default;
+
+void NinjaCBinaryTargetWriter::Run() {
+  // Figure out what source types are needed.
+  SourceFileTypeSet used_types;
+  for (const auto& source : target_->sources())
+    used_types.Set(GetSourceFileType(source));
+
+  WriteCompilerVars(used_types);
+
+  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.
+  //
+  // This is important to prevent changes in unrelated actions that are
+  // upstream of this target from causing everything to be recompiled.
+  //
+  // Why can we get away with this rather than using implicit deps ("|", which
+  // will force rebuilds when the inputs change)? For source code, the
+  // computed dependencies of all headers will be computed by the compiler,
+  // which will cause source rebuilds if any "real" upstream dependencies
+  // change.
+  //
+  // If a .cc file is generated by an input dependency, Ninja will see the
+  // input to the build rule doesn't exist, and that it is an output from a
+  // previous step, and build the previous step first. This is a "real"
+  // dependency and doesn't need | or || to express.
+  //
+  // The only case where this rule matters is for the first build where no .d
+  // files exist, and Ninja doesn't know what that source file depends on. In
+  // this case it's sufficient to ensure that the upstream dependencies are
+  // built first. This is exactly what Ninja's order-only dependencies
+  // expresses.
+  //
+  // The order only deps are referenced by each source file compile,
+  // but also by PCH compiles.  The latter are annoying to count, so omit
+  // them here.  This means that binary targets with a single source file
+  // that also use PCH files won't have a stamp file even though having
+  // one would make output ninja file size a bit lower. That's ok, binary
+  // targets with a single source are rare.
+  size_t num_stamp_uses = target_->sources().size();
+  std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
+      std::vector<const Target*>(), num_stamp_uses);
+
+  // For GCC builds, the .gch files are not object files, but still need to be
+  // added as explicit dependencies below. The .gch output files are placed in
+  // |pch_other_files|. This is to prevent linking against them.
+  std::vector<OutputFile> pch_obj_files;
+  std::vector<OutputFile> pch_other_files;
+  WritePCHCommands(used_types, input_dep, order_only_deps, &pch_obj_files,
+                   &pch_other_files);
+  std::vector<OutputFile>* pch_files =
+      !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files;
+
+  // Treat all pch output files as explicit dependencies of all
+  // compiles that support them. Some notes:
+  //
+  //  - On Windows, the .pch file is the input to the compile, not the
+  //    precompiled header's corresponding object file that we're using here.
+  //    But Ninja's depslog doesn't support multiple outputs from the
+  //    precompiled header compile step (it outputs both the .pch file and a
+  //    corresponding .obj file). So we consistently list the .obj file and the
+  //    .pch file we really need comes along with it.
+  //
+  //  - GCC .gch files are not object files, therefore they are not added to the
+  //    object file list.
+  std::vector<OutputFile> obj_files;
+  std::vector<SourceFile> other_files;
+  WriteSources(*pch_files, input_dep, order_only_deps, &obj_files,
+               &other_files);
+
+  // Link all MSVC pch object files. The vector will be empty on GCC toolchains.
+  obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end());
+  if (!CheckForDuplicateObjectFiles(obj_files))
+    return;
+
+  if (target_->output_type() == Target::SOURCE_SET) {
+    WriteSourceSetStamp(obj_files);
+#ifndef NDEBUG
+    // 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);
+    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));
+#endif
+  } else {
+    WriteLinkerStuff(obj_files, other_files, input_dep);
+  }
+}
+
+void NinjaCBinaryTargetWriter::WriteCompilerVars(
+    const SourceFileTypeSet& used_types) {
+  const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
+
+  // Defines.
+  if (subst.used[SUBSTITUTION_DEFINES]) {
+    out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " =";
+    RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
+                                               DefineWriter(), out_);
+    out_ << std::endl;
+  }
+
+  // Include directories.
+  if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
+    out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
+    PathOutput include_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        target_, &ConfigValues::include_dirs,
+        IncludeWriter(include_path_output), out_);
+    out_ << std::endl;
+  }
+
+  bool has_precompiled_headers =
+      target_->config_values().has_precompiled_headers();
+
+  EscapeOptions opts = GetFlagOptions();
+  if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) {
+    WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Tool::kToolNone,
+                 &ConfigValues::asmflags, opts, path_output_, out_);
+  }
+  if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) ||
+      used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) {
+    WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Tool::kToolNone,
+                 &ConfigValues::cflags, opts, path_output_, out_);
+  }
+  if (used_types.Get(SOURCE_C)) {
+    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers,
+                 CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
+                 out_);
+  }
+  if (used_types.Get(SOURCE_CPP)) {
+    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers,
+                 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
+                 out_);
+  }
+  if (used_types.Get(SOURCE_M)) {
+    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers,
+                 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
+                 path_output_, out_);
+  }
+  if (used_types.Get(SOURCE_MM)) {
+    WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers,
+                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
+                 path_output_, out_);
+  }
+
+  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 SourceFileTypeSet& used_types,
+    const OutputFile& input_dep,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files,
+    std::vector<OutputFile>* other_files) {
+  if (!target_->config_values().has_precompiled_headers())
+    return;
+
+  const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc);
+  if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE &&
+      used_types.Get(SOURCE_C)) {
+    WritePCHCommand(SUBSTITUTION_CFLAGS_C, CTool::kCToolCc,
+                    tool_c->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
+  }
+  const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx);
+  if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE &&
+      used_types.Get(SOURCE_CPP)) {
+    WritePCHCommand(SUBSTITUTION_CFLAGS_CC, CTool::kCToolCxx,
+                    tool_cxx->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
+  }
+
+  const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC);
+  if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC &&
+      used_types.Get(SOURCE_M)) {
+    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJC, CTool::kCToolObjC,
+                    tool_objc->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
+  }
+
+  const CTool* tool_objcxx =
+      target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
+  if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC &&
+      used_types.Get(SOURCE_MM)) {
+    WritePCHCommand(SUBSTITUTION_CFLAGS_OBJCC, CTool::kCToolObjCxx,
+                    tool_objcxx->precompiled_header_type(), input_dep,
+                    order_only_deps, object_files, other_files);
+  }
+}
+
+void NinjaCBinaryTargetWriter::WritePCHCommand(
+    SubstitutionType flag_type,
+    const char* tool_name,
+    CTool::PrecompiledHeaderType header_type,
+    const OutputFile& input_dep,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files,
+    std::vector<OutputFile>* other_files) {
+  switch (header_type) {
+    case CTool::PCH_MSVC:
+      WriteWindowsPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
+                             object_files);
+      break;
+    case CTool::PCH_GCC:
+      WriteGCCPCHCommand(flag_type, tool_name, input_dep, order_only_deps,
+                         other_files);
+      break;
+    case CTool::PCH_NONE:
+      NOTREACHED() << "Cannot write a PCH command with no PCH header type";
+      break;
+  }
+}
+
+void NinjaCBinaryTargetWriter::WriteGCCPCHCommand(
+    SubstitutionType flag_type,
+    const char* tool_name,
+    const OutputFile& input_dep,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* gch_files) {
+  // Compute the pch output file (it will be language-specific).
+  std::vector<OutputFile> outputs;
+  GetPCHOutputFiles(target_, tool_name, &outputs);
+  if (outputs.empty())
+    return;
+
+  gch_files->insert(gch_files->end(), outputs.begin(), outputs.end());
+
+  std::vector<OutputFile> extra_deps;
+  if (!input_dep.value().empty())
+    extra_deps.push_back(input_dep);
+
+  // Build line to compile the file.
+  WriteCompilerBuildLine(target_->config_values().precompiled_source(),
+                         extra_deps, order_only_deps, tool_name, outputs);
+
+  // This build line needs a custom language-specific flags value. Rule-specific
+  // variables are just indented underneath the rule line.
+  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
+
+  // Each substitution flag is overwritten in the target rule to replace the
+  // implicitly generated -include flag with the -x <header lang> flag required
+  // for .gch targets.
+  EscapeOptions opts = GetFlagOptions();
+  if (tool_name == CTool::kCToolCc) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
+                                         out_);
+  } else if (tool_name == CTool::kCToolCxx) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
+                                         opts, out_);
+  } else if (tool_name == CTool::kCToolObjC) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
+                                         opts, out_);
+  } else if (tool_name == CTool::kCToolObjCxx) {
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
+                                         opts, out_);
+  }
+
+  // Append the command to specify the language of the .gch file.
+  out_ << " -x " << GetPCHLangForToolType(tool_name);
+
+  // Write two blank lines to help separate the PCH build lines from the
+  // regular source build lines.
+  out_ << std::endl << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand(
+    SubstitutionType flag_type,
+    const char* tool_name,
+    const OutputFile& input_dep,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files) {
+  // Compute the pch output file (it will be language-specific).
+  std::vector<OutputFile> outputs;
+  GetPCHOutputFiles(target_, tool_name, &outputs);
+  if (outputs.empty())
+    return;
+
+  object_files->insert(object_files->end(), outputs.begin(), outputs.end());
+
+  std::vector<OutputFile> extra_deps;
+  if (!input_dep.value().empty())
+    extra_deps.push_back(input_dep);
+
+  // Build line to compile the file.
+  WriteCompilerBuildLine(target_->config_values().precompiled_source(),
+                         extra_deps, order_only_deps, tool_name, outputs);
+
+  // This build line needs a custom language-specific flags value. Rule-specific
+  // variables are just indented underneath the rule line.
+  out_ << "  " << kSubstitutionNinjaNames[flag_type] << " =";
+
+  // Append the command to generate the .pch file.
+  // This adds the value to the existing flag instead of overwriting it.
+  out_ << " ${" << kSubstitutionNinjaNames[flag_type] << "}";
+  out_ << " /Yc" << target_->config_values().precompiled_header();
+
+  // Write two blank lines to help separate the PCH build lines from the
+  // regular source build lines.
+  out_ << std::endl << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSources(
+    const std::vector<OutputFile>& pch_deps,
+    const OutputFile& input_dep,
+    const std::vector<OutputFile>& order_only_deps,
+    std::vector<OutputFile>* object_files,
+    std::vector<SourceFile>* other_files) {
+  object_files->reserve(object_files->size() + target_->sources().size());
+
+  std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop.
+  std::vector<OutputFile> deps;
+  for (const auto& source : target_->sources()) {
+    // Clear the vector but maintain the max capacity to prevent reallocations.
+    deps.resize(0);
+    const char* tool_name = Tool::kToolNone;
+    if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) {
+      if (GetSourceFileType(source) == SOURCE_DEF)
+        other_files->push_back(source);
+      continue;  // No output for this source.
+    }
+
+    if (!input_dep.value().empty())
+      deps.push_back(input_dep);
+
+    if (tool_name != Tool::kToolNone) {
+      // Only include PCH deps that correspond to the tool type, for instance,
+      // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep
+      // for the output of a C tool type.
+      //
+      // This makes the assumption that pch_deps only contains pch output files
+      // with the naming scheme specified in GetWindowsPCHObjectExtension or
+      // GetGCCPCHOutputExtension.
+      const CTool* tool = target_->toolchain()->GetToolAsC(tool_name);
+      if (tool->precompiled_header_type() != CTool::PCH_NONE) {
+        for (const auto& dep : pch_deps) {
+          const std::string& output_value = dep.value();
+          size_t extension_offset = FindExtensionOffset(output_value);
+          if (extension_offset == std::string::npos)
+            continue;
+          std::string output_extension;
+          if (tool->precompiled_header_type() == CTool::PCH_MSVC) {
+            output_extension = GetWindowsPCHObjectExtension(
+                tool_name, output_value.substr(extension_offset - 1));
+          } else if (tool->precompiled_header_type() == CTool::PCH_GCC) {
+            output_extension = GetGCCPCHOutputExtension(tool_name);
+          }
+          if (output_value.compare(
+                  output_value.size() - output_extension.size(),
+                  output_extension.size(), output_extension) == 0) {
+            deps.push_back(dep);
+          }
+        }
+      }
+      WriteCompilerBuildLine(source, deps, order_only_deps, tool_name,
+                             tool_outputs);
+    }
+
+    // It's theoretically possible for a compiler to produce more than one
+    // output, but we'll only link to the first output.
+    object_files->push_back(tool_outputs[0]);
+  }
+  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,
+    const OutputFile& input_dep) {
+  std::vector<OutputFile> output_files;
+  SubstitutionWriter::ApplyListToLinkerAsOutputFile(
+      target_, tool_, tool_->outputs(), &output_files);
+
+  out_ << "build";
+  path_output_.WriteFiles(out_, output_files);
+
+  out_ << ": " << rule_prefix_
+       << Tool::GetToolTypeForTargetFinalOutput(target_);
+
+  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);
+
+  // Object files.
+  path_output_.WriteFiles(out_, object_files);
+  path_output_.WriteFiles(out_, extra_object_files);
+
+  // Dependencies.
+  std::vector<OutputFile> implicit_deps;
+  std::vector<OutputFile> solibs;
+  for (const Target* cur : linkable_deps) {
+    // All linkable deps should have a link output file.
+    DCHECK(!cur->link_output_file().value().empty())
+        << "No link output file for "
+        << target_->label().GetUserVisibleName(false);
+
+    if (cur->dependency_output_file().value() !=
+        cur->link_output_file().value()) {
+      // This is a shared library with separate link and deps files. Save for
+      // later.
+      implicit_deps.push_back(cur->dependency_output_file());
+      solibs.push_back(cur->link_output_file());
+    } else {
+      // Normal case, just link to this target.
+      out_ << " ";
+      path_output_.WriteFile(out_, cur->link_output_file());
+    }
+  }
+
+  const SourceFile* optional_def_file = nullptr;
+  if (!other_files.empty()) {
+    for (const SourceFile& src_file : other_files) {
+      if (GetSourceFileType(src_file) == SOURCE_DEF) {
+        optional_def_file = &src_file;
+        implicit_deps.push_back(
+            OutputFile(settings_->build_settings(), src_file));
+        break;  // Only one def file is allowed.
+      }
+    }
+  }
+
+  // Libraries specified by paths.
+  const OrderedSet<LibFile>& libs = target_->all_libs();
+  for (size_t i = 0; i < libs.size(); i++) {
+    if (libs[i].is_source_file()) {
+      implicit_deps.push_back(
+          OutputFile(settings_->build_settings(), libs[i].source_file()));
+    }
+  }
+
+  // The input dependency is only needed if there are no object files, as the
+  // dependency is normally provided transitively by the source files.
+  if (!input_dep.value().empty() && object_files.empty())
+    implicit_deps.push_back(input_dep);
+
+  // Append implicit dependencies collected above.
+  if (!implicit_deps.empty()) {
+    out_ << " |";
+    path_output_.WriteFiles(out_, implicit_deps);
+  }
+
+  // Append data dependencies as order-only dependencies.
+  //
+  // This will include data dependencies and input dependencies (like when
+  // this target depends on an action). Having the data dependencies in this
+  // list ensures that the data is available at runtime when the user builds
+  // this target.
+  //
+  // The action dependencies are not strictly necessary in this case. They
+  // should also have been collected via the input deps stamp that each source
+  // file has for an order-only dependency, and since this target depends on
+  // the sources, there is already an implicit order-only dependency. However,
+  // it's extra work to separate these out and there's no disadvantage to
+  // listing them again.
+  WriteOrderOnlyDependencies(non_linkable_deps);
+
+  // End of the link "build" line.
+  out_ << std::endl;
+
+  // The remaining things go in the inner scope of the link line.
+  if (target_->output_type() == Target::EXECUTABLE ||
+      target_->output_type() == Target::SHARED_LIBRARY ||
+      target_->output_type() == Target::LOADABLE_MODULE) {
+    WriteLinkerFlags(optional_def_file);
+    WriteLibs();
+  } else if (target_->output_type() == Target::STATIC_LIBRARY) {
+    out_ << "  arflags =";
+    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
+                                         GetFlagOptions(), out_);
+    out_ << std::endl;
+  }
+  WriteOutputSubstitutions();
+  WriteSolibs(solibs);
+}
+
+void NinjaCBinaryTargetWriter::WriteLinkerFlags(
+    const SourceFile* optional_def_file) {
+  out_ << "  ldflags =";
+
+  // First the ldflags from the target and its config.
+  RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
+                                       GetFlagOptions(), out_);
+
+  // Followed by library search paths that have been recursively pushed
+  // through the dependency tree.
+  const OrderedSet<SourceDir> all_lib_dirs = target_->all_lib_dirs();
+  if (!all_lib_dirs.empty()) {
+    // Since we're passing these on the command line to the linker and not
+    // to Ninja, we need to do shell escaping.
+    PathOutput lib_path_output(path_output_.current_dir(),
+                               settings_->build_settings()->root_path_utf8(),
+                               ESCAPE_NINJA_COMMAND);
+    for (size_t i = 0; i < all_lib_dirs.size(); i++) {
+      out_ << " " << tool_->lib_dir_switch();
+      lib_path_output.WriteDir(out_, all_lib_dirs[i],
+                               PathOutput::DIR_NO_LAST_SLASH);
+    }
+  }
+
+  if (optional_def_file) {
+    out_ << " /DEF:";
+    path_output_.WriteFile(out_, *optional_def_file);
+  }
+
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteLibs() {
+  out_ << "  libs =";
+
+  // Libraries that have been recursively pushed through the dependency tree.
+  EscapeOptions lib_escape_opts;
+  lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
+  const OrderedSet<LibFile> all_libs = target_->all_libs();
+  const std::string framework_ending(".framework");
+  for (size_t i = 0; i < all_libs.size(); i++) {
+    const LibFile& lib_file = all_libs[i];
+    const std::string& lib_value = lib_file.value();
+    if (lib_file.is_source_file()) {
+      out_ << " ";
+      path_output_.WriteFile(out_, lib_file.source_file());
+    } else if (base::EndsWith(lib_value, framework_ending,
+                              base::CompareCase::INSENSITIVE_ASCII)) {
+      // Special-case libraries ending in ".framework" to support Mac: Add the
+      // -framework switch and don't add the extension to the output.
+      out_ << " -framework ";
+      EscapeStringToStream(
+          out_, lib_value.substr(0, lib_value.size() - framework_ending.size()),
+          lib_escape_opts);
+    } else {
+      out_ << " " << tool_->lib_switch();
+      EscapeStringToStream(out_, lib_value, lib_escape_opts);
+    }
+  }
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() {
+  out_ << "  output_extension = "
+       << SubstitutionWriter::GetLinkerSubstitution(
+              target_, tool_, SUBSTITUTION_OUTPUT_EXTENSION);
+  out_ << std::endl;
+  out_ << "  output_dir = "
+       << SubstitutionWriter::GetLinkerSubstitution(target_, tool_,
+                                                    SUBSTITUTION_OUTPUT_DIR);
+  out_ << std::endl;
+}
+
+void NinjaCBinaryTargetWriter::WriteSolibs(
+    const std::vector<OutputFile>& solibs) {
+  if (solibs.empty())
+    return;
+
+  out_ << "  solibs =";
+  path_output_.WriteFiles(out_, solibs);
+  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()) {
+    out_ << " ||";
+
+    // Non-linkable targets.
+    for (auto* non_linkable_dep : non_linkable_deps) {
+      out_ << " ";
+      path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file());
+    }
+  }
+}
+
+bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles(
+    const std::vector<OutputFile>& files) const {
+  std::unordered_set<std::string> set;
+  for (const auto& file : files) {
+    if (!set.insert(file.value()).second) {
+      Err err(
+          target_->defined_from(), "Duplicate object file",
+          "The target " + target_->label().GetUserVisibleName(false) +
+              "\ngenerates two object files with the same name:\n  " +
+              file.value() +
+              "\n"
+              "\n"
+              "It could be you accidentally have a file listed twice in the\n"
+              "sources. Or, depending on how your toolchain maps sources to\n"
+              "object files, two source files with the same name in different\n"
+              "directories could map to the same object file.\n"
+              "\n"
+              "In the latter case, either rename one of the files or move one "
+              "of\n"
+              "the sources to a separate source_set to avoid them both being "
+              "in\n"
+              "the same target.");
+      g_scheduler->FailWithError(err);
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/tools/gn/ninja_c_binary_target_writer.h b/tools/gn/ninja_c_binary_target_writer.h
new file mode 100644
index 0000000..4fb89d9
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer.h
@@ -0,0 +1,134 @@
+// 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_C_BINARY_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "tools/gn/config_values.h"
+#include "tools/gn/ninja_binary_target_writer.h"
+#include "tools/gn/toolchain.h"
+#include "tools/gn/unique_vector.h"
+
+struct EscapeOptions;
+
+// Writes a .ninja file for a binary target type (an executable, a shared
+// library, or a static library).
+class NinjaCBinaryTargetWriter : public NinjaBinaryTargetWriter {
+ public:
+  NinjaCBinaryTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaCBinaryTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  typedef std::set<OutputFile> OutputFileSet;
+
+  // Writes all flags for the compiler: includes, defines, cflags, etc.
+  void WriteCompilerVars(const SourceFileTypeSet& used_types);
+
+  // 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
+  // appended to |other_files|).
+  //
+  // input_dep is the stamp file collecting the dependencies required before
+  // compiling this target. It will be empty if there are no input deps.
+  void WritePCHCommands(const SourceFileTypeSet& used_types,
+                        const OutputFile& input_dep,
+                        const std::vector<OutputFile>& order_only_deps,
+                        std::vector<OutputFile>* object_files,
+                        std::vector<OutputFile>* other_files);
+
+  // Writes a .pch compile build line for a language type.
+  void WritePCHCommand(SubstitutionType flag_type,
+                       const char* tool_name,
+                       CTool::PrecompiledHeaderType header_type,
+                       const OutputFile& input_dep,
+                       const std::vector<OutputFile>& order_only_deps,
+                       std::vector<OutputFile>* object_files,
+                       std::vector<OutputFile>* other_files);
+
+  void WriteGCCPCHCommand(SubstitutionType flag_type,
+                          const char* tool_name,
+                          const OutputFile& input_dep,
+                          const std::vector<OutputFile>& order_only_deps,
+                          std::vector<OutputFile>* gch_files);
+
+  void WriteWindowsPCHCommand(SubstitutionType flag_type,
+                              const char* tool_name,
+                              const OutputFile& input_dep,
+                              const std::vector<OutputFile>& order_only_deps,
+                              std::vector<OutputFile>* object_files);
+
+  // pch_deps are additional dependencies to run before the rule. They are
+  // expected to abide by the naming conventions specified by GetPCHOutputFiles.
+  //
+  // order_only_dep are the dependencies that must be run before doing any
+  // compiles.
+  //
+  // The files produced by the compiler will be added to two output vectors.
+  void WriteSources(const std::vector<OutputFile>& pch_deps,
+                    const OutputFile& input_dep,
+                    const std::vector<OutputFile>& order_only_deps,
+                    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);
+  void WriteLinkerFlags(const SourceFile* optional_def_file);
+  void WriteLibs();
+  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.
+  //
+  // The order-only dependencies are the non-linkable deps passed in as an
+  // argument, plus the data file depdencies in the target.
+  void WriteOrderOnlyDependencies(
+      const UniqueVector<const Target*>& non_linkable_deps);
+
+  // Checks for duplicates in the given list of output files. If any duplicates
+  // are found, throws an error and return false.
+  bool CheckForDuplicateObjectFiles(const std::vector<OutputFile>& files) const;
+
+  const CTool* tool_;
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaCBinaryTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_C_BINARY_TARGET_WRITER_H_
diff --git a/tools/gn/ninja_c_binary_target_writer_unittest.cc b/tools/gn/ninja_c_binary_target_writer_unittest.cc
new file mode 100644
index 0000000..829114a
--- /dev/null
+++ b/tools/gn/ninja_c_binary_target_writer_unittest.cc
@@ -0,0 +1,1159 @@
+// 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_c_binary_target_writer.h"
+
+#include <memory>
+#include <sstream>
+#include <utility>
+
+#include "tools/gn/config.h"
+#include "tools/gn/ninja_target_command_util.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 NinjaCBinaryTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSet) {
+  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.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  // Also test object files, which should be just passed through to the
+  // dependents to link.
+  target.sources().push_back(SourceFile("//foo/input3.o"));
+  target.sources().push_back(SourceFile("//foo/input4.obj"));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Source set itself.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+
+  // A shared library that depends on the source set.
+  Target shlib_target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  shlib_target.set_output_type(Target::SHARED_LIBRARY);
+  shlib_target.public_deps().push_back(LabelTargetPair(&target));
+  shlib_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(shlib_target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&shlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libshlib\n"
+        "\n"
+        "\n"
+        // Ordering of the obj files here should come out in the order
+        // specified, with the target's first, followed by the source set's, in
+        // order.
+        "build ./libshlib.so: solink obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+        "|| obj/foo/bar.stamp\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  output_extension = .so\n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+
+  // A static library that depends on the source set (should not link it).
+  Target stlib_target(setup.settings(), Label(SourceDir("//foo/"), "stlib"));
+  stlib_target.set_output_type(Target::STATIC_LIBRARY);
+  stlib_target.public_deps().push_back(LabelTargetPair(&target));
+  stlib_target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(stlib_target.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&stlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libstlib\n"
+        "\n"
+        "\n"
+        // There are no sources so there are no params to alink. (In practice
+        // this will probably fail in the archive tool.)
+        "build obj/foo/libstlib.a: alink || obj/foo/bar.stamp\n"
+        "  arflags =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+
+  // Make the static library 'complete', which means it should be linked.
+  stlib_target.set_complete_static_lib(true);
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&stlib_target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libstlib\n"
+        "\n"
+        "\n"
+        // Ordering of the obj files here should come out in the order
+        // specified, with the target's first, followed by the source set's, in
+        // order.
+        "build obj/foo/libstlib.a: alink obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o ../../foo/input3.o ../../foo/input4.obj "
+        "|| obj/foo/bar.stamp\n"
+        "  arflags =\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EscapeDefines) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.config_values().defines().push_back("BOOL_DEF");
+  target.config_values().defines().push_back("INT_DEF=123");
+  target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expectedSubstr[] =
+#if defined(OS_WIN)
+      "defines = -DBOOL_DEF -DINT_DEF=123 \"-DSTR_DEF=\\\"ABCD-1\\\"\"";
+#else
+      "defines = -DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\"ABCD-1\\\"";
+#endif
+  std::string out_str = out.str();
+  EXPECT_TRUE(out_str.find(out_str) != std::string::npos);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, StaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.config_values().arflags().push_back("--asdf");
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+      "\n"
+      "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o\n"
+      "  arflags = --asdf\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, CompleteStaticLibrary) {
+  TestWithScope setup;
+  Err err;
+
+  TestTarget target(setup, "//foo:bar", Target::STATIC_LIBRARY);
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.config_values().arflags().push_back("--asdf");
+  target.set_complete_static_lib(true);
+
+  TestTarget baz(setup, "//foo:baz", Target::STATIC_LIBRARY);
+  baz.sources().push_back(SourceFile("//foo/input2.cc"));
+
+  target.public_deps().push_back(LabelTargetPair(&baz));
+
+  ASSERT_TRUE(target.OnResolved(&err));
+  ASSERT_TRUE(baz.OnResolved(&err));
+
+  // A complete static library that depends on an incomplete static library
+  // should link in the dependent object files as if the dependent target
+  // were a source set.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+        "\n"
+        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+        "obj/foo/libbaz.input2.o || obj/foo/libbaz.a\n"
+        "  arflags = --asdf\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+
+  // Make the dependent static library complete.
+  baz.set_complete_static_lib(true);
+
+  // Dependent complete static libraries should not be linked directly.
+  {
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "build obj/foo/libbar.input1.o: cxx ../../foo/input1.cc\n"
+        "\n"
+        "build obj/foo/libbar.a: alink obj/foo/libbar.input1.o "
+        "|| obj/foo/libbaz.a\n"
+        "  arflags = --asdf\n"
+        "  output_extension = \n"
+        "  output_dir = \n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str);
+  }
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, OutputExtensionAndInputDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // An action for our library to depend on.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "action"));
+  action.set_output_type(Target::ACTION_FOREACH);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // A shared library w/ the output_extension set to a custom value.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.set_output_extension(std::string("so.6"));
+  target.set_output_dir(SourceDir("//out/Debug/foo/"));
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+  target.public_deps().push_back(LabelTargetPair(&action));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "build obj/foo/libshlib.input1.o: cxx ../../foo/input1.cc"
+      " || obj/foo/action.stamp\n"
+      "build obj/foo/libshlib.input2.o: cxx ../../foo/input2.cc"
+      " || obj/foo/action.stamp\n"
+      "\n"
+      "build ./libshlib.so.6: solink obj/foo/libshlib.input1.o "
+      // The order-only dependency here is stricly unnecessary since the
+      // sources list this as an order-only dep. See discussion in the code
+      // that writes this.
+      "obj/foo/libshlib.input2.o || obj/foo/action.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = .so.6\n"
+      "  output_dir = foo\n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
+  Err err;
+  TestWithScope setup;
+
+  SourceFile generated_file("//out/Debug/generated.cc");
+
+  // An action does code generation.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
+  action.set_output_type(Target::ACTION);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  action.set_output_dir(SourceDir("//out/Debug/foo/"));
+  action.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/generated.cc");
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // A source set compiling geneated code, this target does not publicize any
+  // headers.
+  Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
+  gen_obj.set_output_type(Target::SOURCE_SET);
+  gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_obj.sources().push_back(generated_file);
+  gen_obj.visibility().SetPublic();
+  gen_obj.private_deps().push_back(LabelTargetPair(&action));
+  gen_obj.set_all_headers_public(false);
+  gen_obj.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_obj.OnResolved(&err));
+
+  std::ostringstream obj_out;
+  NinjaCBinaryTargetWriter obj_writer(&gen_obj, obj_out);
+  obj_writer.Run();
+
+  const char obj_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = gen_obj\n"
+      "\n"
+      "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
+      " || obj/foo/generate.stamp\n"
+      "\n"
+      "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since the
+      // sources list this as an order-only dep.
+      " || obj/foo/generate.stamp\n";
+
+  std::string obj_str = obj_out.str();
+  EXPECT_EQ(obj_expected, obj_str);
+
+  // A shared library depends on gen_obj, having corresponding header for
+  // generated obj.
+  Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
+  gen_lib.set_output_type(Target::SHARED_LIBRARY);
+  gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
+  gen_lib.visibility().SetPublic();
+  gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
+  gen_lib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_lib.OnResolved(&err));
+
+  std::ostringstream lib_out;
+  NinjaCBinaryTargetWriter lib_writer(&gen_lib, lib_out);
+  lib_writer.Run();
+
+  const char lib_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libgen_lib\n"
+      "\n"
+      "\n"
+      "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since
+      // obj/out/Debug/gen_obj.generated.o has dependency to
+      // obj/foo/gen_obj.stamp
+      " || obj/foo/gen_obj.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = .so\n"
+      "  output_dir = foo\n";
+
+  std::string lib_str = lib_out.str();
+  EXPECT_EQ(lib_expected, lib_str);
+
+  // An executable depends on gen_lib.
+  Target executable(setup.settings(),
+                    Label(SourceDir("//foo/"), "final_target"));
+  executable.set_output_type(Target::EXECUTABLE);
+  executable.set_output_dir(SourceDir("//out/Debug/foo/"));
+  executable.sources().push_back(SourceFile("//foo/main.cc"));
+  executable.private_deps().push_back(LabelTargetPair(&gen_lib));
+  executable.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&executable, final_out);
+  final_writer.Run();
+
+  // There is no order only dependency to action target.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = final_target\n"
+      "\n"
+      "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
+      "\n"
+      "build ./final_target: link obj/foo/final_target.main.o"
+      " ./libgen_lib.so\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = \n"
+      "  output_dir = foo\n";
+
+  std::string final_str = final_out.str();
+  EXPECT_EQ(final_expected, final_str);
+}
+
+// Tests libs are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, LibsAndLibDirs) {
+  Err err;
+  TestWithScope setup;
+
+  // A shared library w/ libs and lib_dirs.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.config_values().libs().push_back(LibFile(SourceFile("//foo/lib1.a")));
+  target.config_values().libs().push_back(LibFile("foo"));
+  target.config_values().lib_dirs().push_back(SourceDir("//foo/bar/"));
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libshlib\n"
+      "\n"
+      "\n"
+      "build ./libshlib.so: solink | ../../foo/lib1.a\n"
+      "  ldflags = -L../../foo/bar\n"
+      "  libs = ../../foo/lib1.a -lfoo\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, EmptyOutputExtension) {
+  Err err;
+  TestWithScope setup;
+
+  // This test is the same as OutputExtensionAndInputDeps, except that we call
+  // set_output_extension("") and ensure that we get an empty one and override
+  // the output prefix so that the name matches the target exactly.
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "shlib"));
+  target.set_output_type(Target::SHARED_LIBRARY);
+  target.set_output_prefix_override(true);
+  target.set_output_extension(std::string());
+  target.sources().push_back(SourceFile("//foo/input1.cc"));
+  target.sources().push_back(SourceFile("//foo/input2.cc"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = shlib\n"
+      "\n"
+      "build obj/foo/shlib.input1.o: cxx ../../foo/input1.cc\n"
+      "build obj/foo/shlib.input2.o: cxx ../../foo/input2.cc\n"
+      "\n"
+      "build ./shlib: solink obj/foo/shlib.input1.o "
+      "obj/foo/shlib.input2.o\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SourceSetDataDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // This target is a data (runtime) dependency of the intermediate target.
+  Target data(setup.settings(), Label(SourceDir("//foo/"), "data_target"));
+  data.set_output_type(Target::EXECUTABLE);
+  data.visibility().SetPublic();
+  data.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(data.OnResolved(&err));
+
+  // Intermediate source set target.
+  Target inter(setup.settings(), Label(SourceDir("//foo/"), "inter"));
+  inter.set_output_type(Target::SOURCE_SET);
+  inter.visibility().SetPublic();
+  inter.data_deps().push_back(LabelTargetPair(&data));
+  inter.SetToolchain(setup.toolchain());
+  inter.sources().push_back(SourceFile("//foo/inter.cc"));
+  ASSERT_TRUE(inter.OnResolved(&err)) << err.message();
+
+  // Write out the intermediate target.
+  std::ostringstream inter_out;
+  NinjaCBinaryTargetWriter inter_writer(&inter, inter_out);
+  inter_writer.Run();
+
+  // The intermediate source set will be a stamp file that depends on the
+  // object files, and will have an order-only dependency on its data dep and
+  // data file.
+  const char inter_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = inter\n"
+      "\n"
+      "build obj/foo/inter.inter.o: cxx ../../foo/inter.cc\n"
+      "\n"
+      "build obj/foo/inter.stamp: stamp obj/foo/inter.inter.o || "
+      "./data_target\n";
+  EXPECT_EQ(inter_expected, inter_out.str());
+
+  // Final target.
+  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+  exe.set_output_type(Target::EXECUTABLE);
+  exe.public_deps().push_back(LabelTargetPair(&inter));
+  exe.SetToolchain(setup.toolchain());
+  exe.sources().push_back(SourceFile("//foo/final.cc"));
+  ASSERT_TRUE(exe.OnResolved(&err));
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+  final_writer.Run();
+
+  // The final output depends on both object files (one from the final target,
+  // one from the source set) and has an order-only dependency on the source
+  // set's stamp file and the final target's data file. The source set stamp
+  // dependency will create an implicit order-only dependency on the data
+  // target.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = exe\n"
+      "\n"
+      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+      "\n"
+      "build ./exe: link obj/foo/exe.final.o obj/foo/inter.inter.o || "
+      "obj/foo/inter.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, SharedLibraryModuleDefinitionFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target shared_lib(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  shared_lib.set_output_type(Target::SHARED_LIBRARY);
+  shared_lib.SetToolchain(setup.toolchain());
+  shared_lib.sources().push_back(SourceFile("//foo/sources.cc"));
+  shared_lib.sources().push_back(SourceFile("//foo/bar.def"));
+  ASSERT_TRUE(shared_lib.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&shared_lib, out);
+  writer.Run();
+
+  const char expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+      "\n"
+      "build ./libbar.so: solink obj/foo/libbar.sources.o | ../../foo/bar.def\n"
+      "  ldflags = /DEF:../../foo/bar.def\n"
+      "  libs =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+  EXPECT_EQ(expected, out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, LoadableModule) {
+  Err err;
+  TestWithScope setup;
+
+  Target loadable_module(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  loadable_module.set_output_type(Target::LOADABLE_MODULE);
+  loadable_module.visibility().SetPublic();
+  loadable_module.SetToolchain(setup.toolchain());
+  loadable_module.sources().push_back(SourceFile("//foo/sources.cc"));
+  ASSERT_TRUE(loadable_module.OnResolved(&err)) << err.message();
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&loadable_module, out);
+  writer.Run();
+
+  const char loadable_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libbar\n"
+      "\n"
+      "build obj/foo/libbar.sources.o: cxx ../../foo/sources.cc\n"
+      "\n"
+      "build ./libbar.so: solink_module obj/foo/libbar.sources.o\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = .so\n"
+      "  output_dir = \n";
+  EXPECT_EQ(loadable_expected, out.str());
+
+  // Final target.
+  Target exe(setup.settings(), Label(SourceDir("//foo/"), "exe"));
+  exe.set_output_type(Target::EXECUTABLE);
+  exe.public_deps().push_back(LabelTargetPair(&loadable_module));
+  exe.SetToolchain(setup.toolchain());
+  exe.sources().push_back(SourceFile("//foo/final.cc"));
+  ASSERT_TRUE(exe.OnResolved(&err)) << err.message();
+
+  std::ostringstream final_out;
+  NinjaCBinaryTargetWriter final_writer(&exe, final_out);
+  final_writer.Run();
+
+  // The final output depends on the loadable module so should have an
+  // order-only dependency on the loadable modules's output file.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = exe\n"
+      "\n"
+      "build obj/foo/exe.final.o: cxx ../../foo/final.cc\n"
+      "\n"
+      "build ./exe: link obj/foo/exe.final.o || ./libbar.so\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = \n"
+      "  output_dir = \n";
+  EXPECT_EQ(final_expected, final_out.str());
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, WinPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  TestWithScope setup;
+
+  // A precompiled header toolchain.
+  Settings pch_settings(setup.build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cxx));
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_MSVC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+    writer.Run();
+
+    const char no_pch_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99\n"
+        "cflags_cc =\n"
+        "target_output_name = no_pch_target\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc\n"
+        "build withpch/obj/foo/no_pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+        "withpch/obj/foo/no_pch_target.input2.o\n";
+    EXPECT_EQ(no_pch_expected, out.str());
+  }
+
+  // This target specifies PCH.
+  {
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_header("build/precompile.h");
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.cc"));
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&pch_target, out);
+    writer.Run();
+
+    const char pch_win_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        // It should output language-specific pch files.
+        "cflags_c = /Fpwithpch/obj/foo/pch_target_c.pch "
+        "/Yubuild/precompile.h\n"
+        "cflags_cc = /Fpwithpch/obj/foo/pch_target_cc.pch "
+        "/Yubuild/precompile.h\n"
+        "target_output_name = pch_target\n"
+        "\n"
+        // Compile the precompiled source files with /Yc.
+        "build withpch/obj/build/pch_target.precompile.c.o: "
+        "withpch_cc ../../build/precompile.cc\n"
+        "  cflags_c = ${cflags_c} /Ycbuild/precompile.h\n"
+        "\n"
+        "build withpch/obj/build/pch_target.precompile.cc.o: "
+        "withpch_cxx ../../build/precompile.cc\n"
+        "  cflags_cc = ${cflags_cc} /Ycbuild/precompile.h\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.cc.o\n"
+        "build withpch/obj/foo/pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.c.o\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.stamp: withpch_stamp "
+        "withpch/obj/foo/pch_target.input1.o "
+        "withpch/obj/foo/pch_target.input2.o "
+        // The precompiled object files were added to the outputs.
+        "withpch/obj/build/pch_target.precompile.c.o "
+        "withpch/obj/build/pch_target.precompile.cc.o\n";
+    EXPECT_EQ(pch_win_expected, out.str());
+  }
+}
+
+TEST_F(NinjaCBinaryTargetWriterTest, GCCPrecompiledHeaders) {
+  Err err;
+
+  // This setup's toolchain does not have precompiled headers defined.
+  TestWithScope setup;
+
+  // A precompiled header toolchain.
+  Settings pch_settings(setup.build_settings(), "withpch/");
+  Toolchain pch_toolchain(&pch_settings,
+                          Label(SourceDir("//toolchain/"), "withpch"));
+  pch_settings.set_toolchain_label(pch_toolchain.label());
+  pch_settings.set_default_toolchain_label(setup.toolchain()->label());
+
+  // Declare a C++ compiler that supports PCH.
+  std::unique_ptr<Tool> cxx = std::make_unique<CTool>(CTool::kCToolCxx);
+  CTool* cxx_tool = cxx->AsC();
+  TestWithScope::SetCommandForTool(
+      "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cxx_tool);
+  cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cxx_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cxx));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // Add a C compiler as well.
+  std::unique_ptr<Tool> cc = std::make_unique<CTool>(CTool::kCToolCc);
+  CTool* cc_tool = cc->AsC();
+  TestWithScope::SetCommandForTool(
+      "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+      "-o {{output}}",
+      cc_tool);
+  cc_tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+  cc_tool->set_precompiled_header_type(CTool::PCH_GCC);
+  pch_toolchain.SetTool(std::move(cc));
+  pch_toolchain.ToolchainSetupComplete();
+
+  // This target doesn't specify precompiled headers.
+  {
+    Target no_pch_target(&pch_settings,
+                         Label(SourceDir("//foo/"), "no_pch_target"));
+    no_pch_target.set_output_type(Target::SOURCE_SET);
+    no_pch_target.visibility().SetPublic();
+    no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    no_pch_target.config_values().cflags_c().push_back("-std=c99");
+    no_pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(no_pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&no_pch_target, out);
+    writer.Run();
+
+    const char no_pch_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99\n"
+        "cflags_cc =\n"
+        "target_output_name = no_pch_target\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc\n"
+        "build withpch/obj/foo/no_pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c\n"
+        "\n"
+        "build withpch/obj/foo/no_pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/no_pch_target.input1.o "
+        "withpch/obj/foo/no_pch_target.input2.o\n";
+    EXPECT_EQ(no_pch_expected, out.str());
+  }
+
+  // This target specifies PCH.
+  {
+    Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+    pch_target.config_values().set_precompiled_source(
+        SourceFile("//build/precompile.h"));
+    pch_target.config_values().cflags_c().push_back("-std=c99");
+    pch_target.set_output_type(Target::SOURCE_SET);
+    pch_target.visibility().SetPublic();
+    pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+    pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+    pch_target.SetToolchain(&pch_toolchain);
+    ASSERT_TRUE(pch_target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&pch_target, out);
+    writer.Run();
+
+    const char pch_gcc_expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_c = -std=c99 "
+        "-include withpch/obj/build/pch_target.precompile.h-c\n"
+        "cflags_cc = -include withpch/obj/build/pch_target.precompile.h-cc\n"
+        "target_output_name = pch_target\n"
+        "\n"
+        // Compile the precompiled sources with -x <lang>.
+        "build withpch/obj/build/pch_target.precompile.h-c.gch: "
+        "withpch_cc ../../build/precompile.h\n"
+        "  cflags_c = -std=c99 -x c-header\n"
+        "\n"
+        "build withpch/obj/build/pch_target.precompile.h-cc.gch: "
+        "withpch_cxx ../../build/precompile.h\n"
+        "  cflags_cc = -x c++-header\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.input1.o: "
+        "withpch_cxx ../../foo/input1.cc | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.h-cc.gch\n"
+        "build withpch/obj/foo/pch_target.input2.o: "
+        "withpch_cc ../../foo/input2.c | "
+        // Explicit dependency on the PCH build step.
+        "withpch/obj/build/pch_target.precompile.h-c.gch\n"
+        "\n"
+        "build withpch/obj/foo/pch_target.stamp: "
+        "withpch_stamp withpch/obj/foo/pch_target.input1.o "
+        "withpch/obj/foo/pch_target.input2.o\n";
+    EXPECT_EQ(pch_gcc_expected, out.str());
+  }
+}
+
+// Should throw an error with the scheduler if a duplicate object file exists.
+// This is dependent on the toolchain's object file mapping.
+TEST_F(NinjaCBinaryTargetWriterTest, DupeObjFileError) {
+  TestWithScope setup;
+  TestTarget target(setup, "//foo:bar", Target::EXECUTABLE);
+  target.sources().push_back(SourceFile("//a.cc"));
+  target.sources().push_back(SourceFile("//a.cc"));
+
+  EXPECT_FALSE(scheduler().is_failed());
+
+  scheduler().SuppressOutputForTesting(true);
+
+  std::ostringstream out;
+  NinjaCBinaryTargetWriter writer(&target, out);
+  writer.Run();
+
+  scheduler().SuppressOutputForTesting(false);
+
+  // Should have issued an error.
+  EXPECT_TRUE(scheduler().is_failed());
+}
+
+// This tests that output extension and output dir overrides apply, and input
+// dependencies are applied.
+TEST_F(NinjaCBinaryTargetWriterTest, InputFiles) {
+  Err err;
+  TestWithScope setup;
+
+  // This target has one input.
+  {
+    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.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | ../../foo/input.data\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | ../../foo/input.data\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has one input but no source files.
+  {
+    Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+    target.set_output_type(Target::SHARED_LIBRARY);
+    target.visibility().SetPublic();
+    target.config_values().inputs().push_back(SourceFile("//foo/input.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = libbar\n"
+        "\n"
+        "\n"
+        "build ./libbar.so: solink | ../../foo/input.data\n"
+        "  ldflags =\n"
+        "  libs =\n"
+        "  output_extension = .so\n"
+        "  output_dir = \n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has multiple inputs.
+  {
+    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.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input2.data"));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.inputs.stamp: stamp"
+        " ../../foo/input1.data ../../foo/input2.data\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+
+  // This target has one input itself, one from an immediate config, and one
+  // from a config tacked on to said config.
+  {
+    Config far_config(setup.settings(), Label(SourceDir("//foo/"), "qux"));
+    far_config.own_values().inputs().push_back(SourceFile("//foo/input3.data"));
+    ASSERT_TRUE(far_config.OnResolved(&err));
+
+    Config config(setup.settings(), Label(SourceDir("//foo/"), "baz"));
+    config.own_values().inputs().push_back(SourceFile("//foo/input2.data"));
+    config.configs().push_back(LabelConfigPair(&far_config));
+    ASSERT_TRUE(config.OnResolved(&err));
+
+    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.cc"));
+    target.sources().push_back(SourceFile("//foo/input2.cc"));
+    target.config_values().inputs().push_back(SourceFile("//foo/input1.data"));
+    target.configs().push_back(LabelConfigPair(&config));
+    target.SetToolchain(setup.toolchain());
+    ASSERT_TRUE(target.OnResolved(&err));
+
+    std::ostringstream out;
+    NinjaCBinaryTargetWriter writer(&target, out);
+    writer.Run();
+
+    const char expected[] =
+        "defines =\n"
+        "include_dirs =\n"
+        "cflags =\n"
+        "cflags_cc =\n"
+        "root_out_dir = .\n"
+        "target_out_dir = obj/foo\n"
+        "target_output_name = bar\n"
+        "\n"
+        "build obj/foo/bar.inputs.stamp: stamp"
+        " ../../foo/input1.data ../../foo/input2.data ../../foo/input3.data\n"
+        "build obj/foo/bar.input1.o: cxx ../../foo/input1.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "build obj/foo/bar.input2.o: cxx ../../foo/input2.cc"
+        " | obj/foo/bar.inputs.stamp\n"
+        "\n"
+        "build obj/foo/bar.stamp: stamp obj/foo/bar.input1.o "
+        "obj/foo/bar.input2.o\n";
+
+    EXPECT_EQ(expected, out.str());
+  }
+}