| // 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/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) {} | 
 |  | 
 | NinjaTargetWriter::~NinjaTargetWriter() = default; | 
 |  | 
 | // static | 
 | std::string NinjaTargetWriter::RunAndWriteFile(const Target* target) { | 
 |   const Settings* settings = target->settings(); | 
 |  | 
 |   ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, | 
 |                     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.Run(); | 
 |   } else if (target->output_type() == Target::CREATE_BUNDLE) { | 
 |     NinjaCreateBundleTargetWriter writer(target, rules); | 
 |     writer.Run(); | 
 |   } else if (target->output_type() == Target::COPY_FILES) { | 
 |     NinjaCopyTargetWriter writer(target, rules); | 
 |     writer.Run(); | 
 |   } else if (target->output_type() == Target::ACTION || | 
 |              target->output_type() == Target::ACTION_FOREACH) { | 
 |     NinjaActionTargetWriter writer(target, rules); | 
 |     writer.Run(); | 
 |   } else if (target->output_type() == Target::GROUP) { | 
 |     NinjaGroupTargetWriter writer(target, rules); | 
 |     writer.Run(); | 
 |   } else if (target->output_type() == Target::GENERATED_FILE) { | 
 |     NinjaGeneratedFileTargetWriter writer(target, rules); | 
 |     writer.Run(); | 
 |   } else if (target->IsBinary()) { | 
 |     needs_file_write = true; | 
 |     NinjaBinaryTargetWriter writer(target, rules); | 
 |     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 : target_->swift_values().modules()) | 
 |         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::WriteInputDepsStampAndGetDep( | 
 |     const std::vector<const Target*>& additional_hard_deps, | 
 |     size_t num_stamp_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 | 
 |   // stamp 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(target_->recursive_hard_deps()); | 
 |   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 stamp file and only include the stamp 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 stamp file 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 OutputFile& dep = input_deps_targets[0]->dependency_output_file(); | 
 |     DCHECK(!dep.value().empty()); | 
 |     return std::vector<OutputFile>{dep}; | 
 |   } | 
 |  | 
 |   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) { | 
 |     DCHECK(!dep->dependency_output_file().value().empty()); | 
 |     outs.push_back(dep->dependency_output_file()); | 
 |   } | 
 |  | 
 |   // If there are multiple inputs, but the stamp file would be referenced only | 
 |   // once, don't write it but depend on the inputs directly. | 
 |   if (num_stamp_uses == 1u) | 
 |     return outs; | 
 |  | 
 |   // 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(".inputdeps.stamp"); | 
 |  | 
 |   out_ << "build "; | 
 |   path_output_.WriteFile(out_, input_stamp_file); | 
 |   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) | 
 |        << GeneralTool::kGeneralToolStamp; | 
 |   path_output_.WriteFiles(out_, outs); | 
 |  | 
 |   out_ << "\n"; | 
 |   return std::vector<OutputFile>{input_stamp_file}; | 
 | } | 
 |  | 
 | void NinjaTargetWriter::WriteStampForTarget( | 
 |     const std::vector<OutputFile>& files, | 
 |     const std::vector<OutputFile>& order_only_deps) { | 
 |   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::EndsWith(stamp_file.value(), ".stamp", | 
 |                        base::CompareCase::INSENSITIVE_ASCII)) | 
 |       << "Output should end in \".stamp\" for stamp file output. Instead got: " | 
 |       << "\"" << stamp_file.value() << "\""; | 
 |  | 
 |   out_ << "build "; | 
 |   path_output_.WriteFile(out_, stamp_file); | 
 |  | 
 |   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) | 
 |        << GeneralTool::kGeneralToolStamp; | 
 |   path_output_.WriteFiles(out_, files); | 
 |  | 
 |   if (!order_only_deps.empty()) { | 
 |     out_ << " ||"; | 
 |     path_output_.WriteFiles(out_, order_only_deps); | 
 |   } | 
 |   out_ << std::endl; | 
 | } |