// Copyright (c) 2013 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 "gn/ninja_target_writer.h"

#include <sstream>

#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "gn/builtin_tool.h"
#include "gn/c_substitution_type.h"
#include "gn/config_values_extractors.h"
#include "gn/err.h"
#include "gn/escape.h"
#include "gn/filesystem_utils.h"
#include "gn/general_tool.h"
#include "gn/ninja_action_target_writer.h"
#include "gn/ninja_binary_target_writer.h"
#include "gn/ninja_bundle_data_target_writer.h"
#include "gn/ninja_copy_target_writer.h"
#include "gn/ninja_create_bundle_target_writer.h"
#include "gn/ninja_generated_file_target_writer.h"
#include "gn/ninja_group_target_writer.h"
#include "gn/ninja_target_command_util.h"
#include "gn/ninja_utils.h"
#include "gn/output_file.h"
#include "gn/rust_substitution_type.h"
#include "gn/scheduler.h"
#include "gn/string_output_buffer.h"
#include "gn/string_utils.h"
#include "gn/substitution_writer.h"
#include "gn/target.h"
#include "gn/trace.h"

NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
    : settings_(target->settings()),
      target_(target),
      out_(out),
      path_output_(settings_->build_settings()->build_dir(),
                   settings_->build_settings()->root_path_utf8(),
                   ESCAPE_NINJA) {}

void NinjaTargetWriter::SetResolvedTargetData(ResolvedTargetData* resolved) {
  if (resolved) {
    resolved_owned_.reset();
    resolved_ptr_ = resolved;
  }
}

void NinjaTargetWriter::SetNinjaOutputs(
    std::vector<OutputFile>* ninja_outputs) {
  ninja_outputs_ = ninja_outputs;
}

ResolvedTargetData* NinjaTargetWriter::GetResolvedTargetData() {
  return const_cast<ResolvedTargetData*>(&resolved());
}

const ResolvedTargetData& NinjaTargetWriter::resolved() const {
  if (!resolved_ptr_) {
    resolved_owned_ = std::make_unique<ResolvedTargetData>();
    resolved_ptr_ = resolved_owned_.get();
  }
  return *resolved_ptr_;
}

NinjaTargetWriter::~NinjaTargetWriter() = default;

void NinjaTargetWriter::WriteOutput(const OutputFile& output) const {
  path_output_.WriteFile(out_, output);
  if (ninja_outputs_)
    ninja_outputs_->push_back(output);
}

void NinjaTargetWriter::WriteOutput(OutputFile&& output) const {
  path_output_.WriteFile(out_, output);
  if (ninja_outputs_)
    ninja_outputs_->push_back(std::move(output));
}

void NinjaTargetWriter::WriteOutputs(
    const std::vector<OutputFile>& outputs) const {
  path_output_.WriteFiles(out_, outputs);
  if (ninja_outputs_)
    ninja_outputs_->insert(ninja_outputs_->end(), outputs.begin(),
                           outputs.end());
}

void NinjaTargetWriter::WriteOutputs(std::vector<OutputFile>&& outputs) const {
  path_output_.WriteFiles(out_, outputs);
  if (ninja_outputs_) {
    for (auto& output : outputs)
      ninja_outputs_->push_back(std::move(output));
  }
}

// static
std::string NinjaTargetWriter::RunAndWriteFile(
    const Target* target,
    ResolvedTargetData* resolved,
    std::vector<OutputFile>* ninja_outputs) {
  const Settings* settings = target->settings();

  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE_NINJA,
                    target->label().GetUserVisibleName(false));
  trace.SetToolchain(settings->toolchain_label());

  if (g_scheduler->verbose_logging())
    g_scheduler->Log("Computing", target->label().GetUserVisibleName(true));

  // It's ridiculously faster to write to a string and then write that to
  // disk in one operation than to use an fstream here.
  StringOutputBuffer storage;
  std::ostream rules(&storage);

  // Call out to the correct sub-type of writer. Binary targets need to be
  // written to separate files for compiler flag scoping, but other target
  // types can have their rules coalesced.
  //
  // In ninja, if a rule uses a variable (like $include_dirs) it will use
  // the value set by indenting it under the build line or it takes the value
  // from the end of the invoking scope (otherwise the current file). It does
  // not copy the value from what it was when the build line was encountered.
  // To avoid writing lots of duplicate rules for defines and cflags, etc. on
  // each source file build line, we use separate .ninja files with the shared
  // variables set at the top.
  //
  // Groups and actions don't use this type of flag, they make unique rules
  // or write variables scoped under each build line. As a result, they don't
  // need the separate files.
  bool needs_file_write = false;
  if (target->output_type() == Target::BUNDLE_DATA) {
    NinjaBundleDataTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->output_type() == Target::CREATE_BUNDLE) {
    NinjaCreateBundleTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->output_type() == Target::COPY_FILES) {
    NinjaCopyTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->output_type() == Target::ACTION ||
             target->output_type() == Target::ACTION_FOREACH) {
    NinjaActionTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->output_type() == Target::GROUP) {
    NinjaGroupTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->output_type() == Target::GENERATED_FILE) {
    NinjaGeneratedFileTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else if (target->IsBinary()) {
    needs_file_write = true;
    NinjaBinaryTargetWriter writer(target, rules);
    writer.SetResolvedTargetData(resolved);
    writer.SetNinjaOutputs(ninja_outputs);
    writer.Run();
  } else {
    CHECK(0) << "Output type of target not handled.";
  }

  if (needs_file_write) {
    // Write the ninja file.
    SourceFile ninja_file = GetNinjaFileForTarget(target);
    base::FilePath full_ninja_file =
        settings->build_settings()->GetFullPath(ninja_file);
    storage.WriteToFileIfChanged(full_ninja_file, nullptr);

    EscapeOptions options;
    options.mode = ESCAPE_NINJA;

    // Return the subninja command to load the rules file.
    std::string result = "subninja ";
    result.append(EscapeString(
        OutputFile(target->settings()->build_settings(), ninja_file).value(),
        options, nullptr));
    result.push_back('\n');
    return result;
  }

  // No separate file required, just return the rules.
  return storage.str();
}

void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) {
  EscapeOptions opts;
  opts.mode = ESCAPE_NINJA;

  out_ << type->ninja_name << " = ";
  EscapeStringToStream(
      out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts);
  out_ << std::endl;
}

void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) {
  bool written_anything = false;

  // Target label.
  if (bits.used.count(&SubstitutionLabel)) {
    WriteEscapedSubstitution(&SubstitutionLabel);
    written_anything = true;
  }

  // Target label name.
  if (bits.used.count(&SubstitutionLabelName)) {
    WriteEscapedSubstitution(&SubstitutionLabelName);
    written_anything = true;
  }

  // Target label name without toolchain.
  if (bits.used.count(&SubstitutionLabelNoToolchain)) {
    WriteEscapedSubstitution(&SubstitutionLabelNoToolchain);
    written_anything = true;
  }

  // Root gen dir.
  if (bits.used.count(&SubstitutionRootGenDir)) {
    WriteEscapedSubstitution(&SubstitutionRootGenDir);
    written_anything = true;
  }

  // Root out dir.
  if (bits.used.count(&SubstitutionRootOutDir)) {
    WriteEscapedSubstitution(&SubstitutionRootOutDir);
    written_anything = true;
  }

  // Target gen dir.
  if (bits.used.count(&SubstitutionTargetGenDir)) {
    WriteEscapedSubstitution(&SubstitutionTargetGenDir);
    written_anything = true;
  }

  // Target out dir.
  if (bits.used.count(&SubstitutionTargetOutDir)) {
    WriteEscapedSubstitution(&SubstitutionTargetOutDir);
    written_anything = true;
  }

  // Target output name.
  if (bits.used.count(&SubstitutionTargetOutputName)) {
    WriteEscapedSubstitution(&SubstitutionTargetOutputName);
    written_anything = true;
  }

  // If we wrote any vars, separate them from the rest of the file that follows
  // with a blank line.
  if (written_anything)
    out_ << std::endl;
}

void NinjaTargetWriter::WriteCCompilerVars(const SubstitutionBits& bits,
                                           bool indent,
                                           bool respect_source_used) {
  // Defines.
  if (bits.used.count(&CSubstitutionDefines)) {
    if (indent)
      out_ << "  ";
    out_ << CSubstitutionDefines.ninja_name << " =";
    RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates,
                                               target_, &ConfigValues::defines,
                                               DefineWriter(), out_);
    out_ << std::endl;
  }

  // Framework search path.
  if (bits.used.count(&CSubstitutionFrameworkDirs)) {
    const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);

    if (indent)
      out_ << "  ";
    out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
    PathOutput framework_dirs_output(
        path_output_.current_dir(),
        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
    RecursiveTargetConfigToStream<SourceDir>(
        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs,
        FrameworkDirsWriter(framework_dirs_output,
                            tool->framework_dir_switch()),
        out_);
    out_ << std::endl;
  }

  // Include directories.
  if (bits.used.count(&CSubstitutionIncludeDirs)) {
    if (indent)
      out_ << "  ";
    out_ << CSubstitutionIncludeDirs.ninja_name << " =";
    PathOutput include_path_output(
        path_output_.current_dir(),
        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
    RecursiveTargetConfigToStream<SourceDir>(
        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs,
        IncludeWriter(include_path_output), out_);
    out_ << std::endl;
  }

  bool has_precompiled_headers =
      target_->config_values().has_precompiled_headers();

  EscapeOptions opts;
  opts.mode = ESCAPE_NINJA_COMMAND;
  if (respect_source_used
          ? target_->source_types_used().Get(SourceFile::SOURCE_S)
          : bits.used.count(&CSubstitutionAsmFlags)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &CSubstitutionAsmFlags, false, Tool::kToolNone,
                 &ConfigValues::asmflags, opts, path_output_, out_, true,
                 indent);
  }
  if (respect_source_used
          ? (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
             target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
             target_->source_types_used().Get(SourceFile::SOURCE_M) ||
             target_->source_types_used().Get(SourceFile::SOURCE_MM) ||
             target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
          : bits.used.count(&CSubstitutionCFlags)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags,
                 false, Tool::kToolNone, &ConfigValues::cflags, opts,
                 path_output_, out_, true, indent);
  }
  if (respect_source_used
          ? target_->source_types_used().Get(SourceFile::SOURCE_C)
          : bits.used.count(&CSubstitutionCFlagsC)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlagsC,
                 has_precompiled_headers, CTool::kCToolCc,
                 &ConfigValues::cflags_c, opts, path_output_, out_, true,
                 indent);
  }
  if (respect_source_used
          ? (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
             target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
          : bits.used.count(&CSubstitutionCFlagsCc)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &CSubstitutionCFlagsCc, has_precompiled_headers,
                 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
                 out_, true, indent);
  }
  if (respect_source_used
          ? target_->source_types_used().Get(SourceFile::SOURCE_M)
          : bits.used.count(&CSubstitutionCFlagsObjC)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &CSubstitutionCFlagsObjC, has_precompiled_headers,
                 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
                 path_output_, out_, true, indent);
  }
  if (respect_source_used
          ? target_->source_types_used().Get(SourceFile::SOURCE_MM)
          : bits.used.count(&CSubstitutionCFlagsObjCc)) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &CSubstitutionCFlagsObjCc, has_precompiled_headers,
                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
                 path_output_, out_, true, indent);
  }
  if (target_->source_types_used().SwiftSourceUsed() || !respect_source_used) {
    if (bits.used.count(&CSubstitutionSwiftModuleName)) {
      if (indent)
        out_ << "  ";
      out_ << CSubstitutionSwiftModuleName.ninja_name << " = ";
      EscapeStringToStream(out_, target_->swift_values().module_name(), opts);
      out_ << std::endl;
    }

    if (bits.used.count(&CSubstitutionSwiftBridgeHeader)) {
      if (indent)
        out_ << "  ";
      out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = ";
      if (!target_->swift_values().bridge_header().is_null()) {
        path_output_.WriteFile(out_, target_->swift_values().bridge_header());
      } else {
        out_ << R"("")";
      }
      out_ << std::endl;
    }

    if (bits.used.count(&CSubstitutionSwiftModuleDirs)) {
      // Uniquify the list of swiftmodule dirs (in case multiple swiftmodules
      // are generated in the same directory).
      UniqueVector<SourceDir> swiftmodule_dirs;
      for (const Target* dep : resolved().GetSwiftModuleDependencies(target_))
        swiftmodule_dirs.push_back(dep->swift_values().module_output_dir());

      if (indent)
        out_ << "  ";
      out_ << CSubstitutionSwiftModuleDirs.ninja_name << " =";
      PathOutput swiftmodule_path_output(
          path_output_.current_dir(),
          settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
      IncludeWriter swiftmodule_path_writer(swiftmodule_path_output);
      for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) {
        swiftmodule_path_writer(swiftmodule_dir, out_);
      }
      out_ << std::endl;
    }

    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
                 &ConfigValues::swiftflags, opts, path_output_, out_, true,
                 indent);
  }
}

void NinjaTargetWriter::WriteRustCompilerVars(const SubstitutionBits& bits,
                                              bool indent,
                                              bool always_write) {
  EscapeOptions opts;
  opts.mode = ESCAPE_NINJA_COMMAND;

  if (bits.used.count(&kRustSubstitutionRustFlags) || always_write) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &kRustSubstitutionRustFlags, false, Tool::kToolNone,
                 &ConfigValues::rustflags, opts, path_output_, out_, true,
                 indent);
  }

  if (bits.used.count(&kRustSubstitutionRustEnv) || always_write) {
    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
                 &kRustSubstitutionRustEnv, false, Tool::kToolNone,
                 &ConfigValues::rustenv, opts, path_output_, out_, true,
                 indent);
  }
}

std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampOrPhonyAndGetDep(
    const std::vector<const Target*>& additional_hard_deps,
    size_t num_output_uses) const {
  CHECK(target_->toolchain()) << "Toolchain not set on target "
                              << target_->label().GetUserVisibleName(true);

  // ----------
  // Collect all input files that are input deps of this target. Knowing the
  // number before writing allows us to either skip writing the input deps
  // phony or optimize it. Use pointers to avoid copies here.
  std::vector<const SourceFile*> input_deps_sources;
  input_deps_sources.reserve(32);

  // Actions get implicit dependencies on the script itself.
  if (target_->output_type() == Target::ACTION ||
      target_->output_type() == Target::ACTION_FOREACH)
    input_deps_sources.push_back(&target_->action_values().script());

  // Input files are only considered for non-binary targets which use an
  // implicit dependency instead. The implicit dependency in this case is
  // handled separately by the binary target writer.
  if (!target_->IsBinary()) {
    for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
      for (const auto& input : iter.cur().inputs())
        input_deps_sources.push_back(&input);
    }
  }

  // For an action (where we run a script only once) the sources are the same
  // as the inputs. For action_foreach, the sources will be operated on
  // separately so don't handle them here.
  if (target_->output_type() == Target::ACTION) {
    for (const auto& source : target_->sources())
      input_deps_sources.push_back(&source);
  }

  // ----------
  // Collect all target input dependencies of this target as was done for the
  // files above.
  std::vector<const Target*> input_deps_targets;
  input_deps_targets.reserve(32);

  // Hard dependencies that are direct or indirect dependencies.
  // These are large (up to 100s), hence why we check other
  const TargetSet& hard_deps = resolved().GetHardDeps(target_);
  for (const Target* target : hard_deps) {
    // BUNDLE_DATA should normally be treated as a data-only dependency
    // (see Target::IsDataOnly()). Only the CREATE_BUNDLE target, that actually
    // consumes this data, needs to have the BUNDLE_DATA as an input dependency.
    if (target->output_type() != Target::BUNDLE_DATA ||
        target_->output_type() == Target::CREATE_BUNDLE)
      input_deps_targets.push_back(target);
  }

  // Additional hard dependencies passed in. These are usually empty or small,
  // and we don't want to duplicate the explicit hard deps of the target.
  for (const Target* target : additional_hard_deps) {
    if (!hard_deps.contains(target))
      input_deps_targets.push_back(target);
  }

  // Toolchain dependencies. These must be resolved before doing anything.
  // This just writes all toolchain deps for simplicity. If we find that
  // toolchains often have more than one dependency, we could consider writing
  // a toolchain-specific phony target and only include the phony here.
  // Note that these are usually empty/small.
  const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
  for (const auto& toolchain_dep : toolchain_deps) {
    // This could theoretically duplicate dependencies already in the list,
    // but it shouldn't happen in practice, is inconvenient to check for,
    // and only results in harmless redundant dependencies listed.
    input_deps_targets.push_back(toolchain_dep.ptr);
  }

  // ---------
  // Write the outputs.

  if (input_deps_sources.size() + input_deps_targets.size() == 0)
    return std::vector<OutputFile>();  // No input dependencies.

  // If we're only generating one input dependency, return it directly instead
  // of writing a phony target for it.
  if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
    return std::vector<OutputFile>{
        OutputFile(settings_->build_settings(), *input_deps_sources[0])};
  if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) {
    const auto& dep = *input_deps_targets[0];
    if (!dep.has_dependency_output())
      return std::vector<OutputFile>();
    return std::vector<OutputFile>{dep.dependency_output()};
  }

  std::vector<OutputFile> outs;
  // File input deps.
  for (const SourceFile* source : input_deps_sources)
    outs.push_back(OutputFile(settings_->build_settings(), *source));
  // Target input deps. Sort by label so the output is deterministic (otherwise
  // some of the targets will have gone through std::sets which will have
  // sorted them by pointer).
  std::sort(
      input_deps_targets.begin(), input_deps_targets.end(),
      [](const Target* a, const Target* b) { return a->label() < b->label(); });
  for (auto* dep : input_deps_targets) {
    if (dep->has_dependency_output())
      outs.push_back(dep->dependency_output());
  }

  // If there are multiple inputs, but the phony target would be referenced only
  // once, don't write it but depend on the inputs directly.
  if (num_output_uses == 1u)
    return outs;

  // If outs is empty, we don't need stamp or phony target for this.
  if (outs.empty()) {
    return outs;
  }

  OutputFile input_stamp_or_phony;
  std::string tool;
  if (settings_->build_settings()->no_stamp_files()) {
    // Make a phony target. We don't need to worry about an empty phony target,
    // as we would return early if there were no inputs.
    input_stamp_or_phony =
        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
    input_stamp_or_phony.value().append(target_->label().name());
    input_stamp_or_phony.value().append(".inputdeps");
    tool = BuiltinTool::kBuiltinToolPhony;
  } else {
    // Make a stamp file.
    input_stamp_or_phony =
        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
    input_stamp_or_phony.value().append(target_->label().name());
    input_stamp_or_phony.value().append(".inputdeps.stamp");

    tool = GetNinjaRulePrefixForToolchain(settings_) +
           GeneralTool::kGeneralToolStamp;
  }

  // These are not real outputs, so do not use WriteOutput() here.
  // See https://gn.issues.chromium.org/448860851.
  out_ << "build ";
  path_output_.WriteFile(out_, input_stamp_or_phony);
  out_ << ": " << tool;
  path_output_.WriteFiles(out_, outs);
  out_ << "\n";
  return std::vector<OutputFile>{input_stamp_or_phony};
}

void NinjaTargetWriter::WriteStampOrPhonyForTarget(
    const std::vector<OutputFile>& files,
    const std::vector<OutputFile>& order_only_deps) {
  // We should have already discerned whether this target is a stamp or a phony.
  // If there's a dependency_output_file, it should be a stamp. Else is a phony
  // or omitted phony (in which case, we don't write it).
  if (target_->has_dependency_output_file()) {
    // Make a stamp target.
    const OutputFile& stamp_file = target_->dependency_output_file();

    // First validate that the target's dependency is a stamp file. Otherwise,
    // we shouldn't have gotten here!
    CHECK(base::EndsWithCaseInsensitiveASCII(stamp_file.value(), ".stamp"))
        << "Output should end in \".stamp\" for stamp file output. Instead "
           "got: "
        << "\"" << stamp_file.value() << "\"";

    out_ << "build ";
    WriteOutput(stamp_file);

    out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
         << GeneralTool::kGeneralToolStamp;
  } else if (target_->has_dependency_output_alias()) {
    // Make a phony target.
    const OutputFile& phony_target = target_->dependency_output_alias();
    CHECK(!phony_target.value().empty());

    out_ << "build ";
    WriteOutput(phony_target);

    out_ << ": " << BuiltinTool::kBuiltinToolPhony;

  } else {
    // This is the omitted phony case. We should not get here if there were any
    // dependencies, so ensure that none got added.
    CHECK(files.empty());
    CHECK(order_only_deps.empty());
    return;
  }

  path_output_.WriteFiles(out_, files);

  if (!order_only_deps.empty()) {
    out_ << " ||";
    path_output_.WriteFiles(out_, order_only_deps);
  }
  WriteValidations();
  out_ << std::endl;
}

void NinjaTargetWriter::WriteValidations() {
  const LabelTargetVector& validations = target_->validations();
  if (validations.empty())
    return;

  bool first = true;
  for (const auto& pair : validations) {
    // This check is needed because empty groups have no output.
    if (!pair.ptr->has_dependency_output()) {
      continue;
    }
    if (first) {
      out_ << " |@";
      first = false;
    }
    out_ << " ";
    WriteOutput(pair.ptr->dependency_output());
  }
}