| // 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 <algorithm> | |
| #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); | |
| if (target->module_type().test(Target::MODULEMAP_IS_GENERATED)) { | |
| const SourceFile* modulemap = target->modulemap_file(); | |
| CHECK(modulemap); | |
| // Write public module map | |
| StringOutputBuffer public_storage; | |
| std::ostream public_os(&public_storage); | |
| writer.WritePublicModuleMap(public_os, modulemap->GetDir()); | |
| base::FilePath public_path = | |
| settings->build_settings()->GetFullPath(*modulemap); | |
| public_storage.WriteToFileIfChanged(public_path, nullptr); | |
| // Write private module map adjacent to the public one | |
| StringOutputBuffer private_storage; | |
| std::ostream private_os(&private_storage); | |
| writer.WritePrivateModuleMap(private_os, modulemap->GetDir()); | |
| base::FilePath private_path = settings->build_settings()->GetFullPath( | |
| *target->private_modulemap_file()); | |
| private_storage.WriteToFileIfChanged(private_path, nullptr); | |
| } | |
| writer.Run(); | |
| } else { | |
| CHECK(0) << "Output type of target not handled."; | |
| } | |
| WritePublicInputsStampOrPhony(target, resolved, rules); | |
| 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(); | |
| } | |
| // static | |
| void NinjaTargetWriter::WritePublicInputsStampOrPhony( | |
| const Target* target, | |
| ResolvedTargetData* resolved, | |
| std::ostream& out) { | |
| DCHECK(resolved); | |
| if (!resolved->ExportsPublicInputs(target)) | |
| return; | |
| const BuildSettings* build_settings = target->settings()->build_settings(); | |
| OutputFile output = GetPublicInputsOutputFile(target, build_settings); | |
| std::vector<OutputFile> deps; | |
| for (const auto& file : target->public_inputs()) { | |
| deps.push_back(OutputFile(build_settings, file)); | |
| } | |
| for (const auto& dep : target->public_deps()) { | |
| if (resolved->ExportsPublicInputs(dep.ptr)) { | |
| deps.push_back(GetPublicInputsOutputFile(dep.ptr, build_settings)); | |
| } | |
| } | |
| PathOutput path_output(build_settings->build_dir(), | |
| build_settings->root_path_utf8(), ESCAPE_NINJA); | |
| out << "build "; | |
| path_output.WriteFile(out, output); | |
| if (build_settings->no_stamp_files()) { | |
| out << ": " << BuiltinTool::kBuiltinToolPhony; | |
| } else { | |
| out << ": " << GetNinjaRulePrefixForToolchain(target->settings()) | |
| << GeneralTool::kGeneralToolStamp; | |
| } | |
| path_output.WriteFiles(out, deps); | |
| out << std::endl << std::endl; | |
| } | |
| 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); | |
| } | |
| } | |
| NinjaTargetWriter::InputDeps | |
| 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. | |
| std::vector<const Target*> toolchain_deps_targets; | |
| 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. | |
| toolchain_deps_targets.push_back(toolchain_dep.ptr); | |
| } | |
| // --------- | |
| // Write the outputs. | |
| if (input_deps_sources.empty() && input_deps_targets.empty() && | |
| toolchain_deps_targets.empty()) | |
| return InputDeps{}; // No input dependencies. | |
| InputDeps deps; | |
| // Inherited public_inputs target dependencies. | |
| std::vector<OutputFile> public_inputs_deps; | |
| if (!target_->public_inputs().empty()) { | |
| public_inputs_deps.push_back( | |
| GetPublicInputsOutputFile(target_, settings_->build_settings())); | |
| } | |
| for (const auto& pair : target_->public_deps()) { | |
| if (resolved().ExportsPublicInputs(pair.ptr)) { | |
| public_inputs_deps.push_back( | |
| GetPublicInputsOutputFile(pair.ptr, settings_->build_settings())); | |
| } | |
| } | |
| for (const auto& pair : target_->private_deps()) { | |
| if (resolved().ExportsPublicInputs(pair.ptr)) { | |
| public_inputs_deps.push_back( | |
| GetPublicInputsOutputFile(pair.ptr, settings_->build_settings())); | |
| } | |
| } | |
| std::sort(public_inputs_deps.begin(), public_inputs_deps.end()); | |
| public_inputs_deps.erase( | |
| std::unique(public_inputs_deps.begin(), public_inputs_deps.end()), | |
| public_inputs_deps.end()); | |
| deps.implicit.insert(deps.implicit.end(), public_inputs_deps.begin(), | |
| public_inputs_deps.end()); | |
| // File input deps. | |
| for (const SourceFile* source : input_deps_sources) | |
| deps.order_only.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). | |
| auto add_target_deps = [](std::vector<OutputFile>& deps, | |
| std::vector<const Target*>& targets) { | |
| std::sort(targets.begin(), targets.end(), | |
| [](const Target* a, const Target* b) { | |
| return a->label() < b->label(); | |
| }); | |
| for (auto* dep : targets) { | |
| if (dep->has_dependency_output()) | |
| deps.push_back(dep->dependency_output()); | |
| } | |
| }; | |
| add_target_deps(deps.order_only, input_deps_targets); | |
| add_target_deps(deps.implicit, toolchain_deps_targets); | |
| // If we're only generating one input dependency, or if there are no | |
| // dependencies, return it directly instead of writing a phony target for it. | |
| // Also, 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 (deps.implicit.size() + deps.order_only.size() <= 1 || | |
| num_output_uses == 1u) | |
| return deps; | |
| // Action targets are special because all of their dependencies are implicit | |
| // dependencies. This is because prior to | |
| // https://gn-review.googlesource.com/c/gn/+/22000 action targets had an | |
| // implicit dependency on inputdeps whereas other target types had an | |
| // order-only dependency on inputdeps (which at the time only had implicit | |
| // dependencies), and scripts may be depending on that. | |
| if (target_->output_type() == Target::ACTION || | |
| target_->output_type() == Target::ACTION_FOREACH) { | |
| deps.implicit.insert(deps.implicit.end(), deps.order_only.begin(), | |
| deps.order_only.end()); | |
| deps.order_only.clear(); | |
| } | |
| 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 = GetOutputFile(*target_, BuildDirType::PHONY, | |
| target_->label().name(), ".inputdeps"); | |
| tool = BuiltinTool::kBuiltinToolPhony; | |
| } else { | |
| // Make a stamp file. | |
| input_stamp_or_phony = | |
| GetOutputFile(*target_, BuildDirType::OBJ, target_->label().name(), | |
| ".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_, deps.implicit); | |
| if (!deps.order_only.empty()) { | |
| out_ << " ||"; | |
| path_output_.WriteFiles(out_, deps.order_only); | |
| } | |
| out_ << "\n"; | |
| InputDeps result; | |
| result.implicit.push_back(input_stamp_or_phony); | |
| return result; | |
| } | |
| void NinjaTargetWriter::WriteStampOrPhonyForTarget( | |
| const std::vector<OutputFile>& files, | |
| const std::vector<OutputFile>& order_only_deps) { | |
| // Add dependency outputs of public_deps to this target's stamp or phony | |
| // target to propagate public dependencies transitively. This allows | |
| // dependents of this target to implicitly depend on the public_deps without | |
| // listing them all directly on their build lines, preventing inflation of | |
| // implicit inputs. | |
| std::vector<OutputFile> all_files = files; | |
| for (const auto& pair : target_->public_deps()) { | |
| if (pair.ptr->has_dependency_output() && !pair.ptr->IsDataOnly()) { | |
| OutputFile dep_out = pair.ptr->dependency_output(); | |
| if (std::find(all_files.begin(), all_files.end(), dep_out) == | |
| all_files.end()) { | |
| all_files.push_back(dep_out); | |
| } | |
| } | |
| } | |
| // 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(all_files.empty()); | |
| CHECK(order_only_deps.empty()); | |
| return; | |
| } | |
| path_output_.WriteFiles(out_, all_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()); | |
| } | |
| } |