| // 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_binary_target_writer.h" | |
| #include <algorithm> | |
| #include <sstream> | |
| #include <unordered_set> | |
| #include "base/strings/string_util.h" | |
| #include "gn/builtin_tool.h" | |
| #include "gn/config_values_extractors.h" | |
| #include "gn/deps_iterator.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/general_tool.h" | |
| #include "gn/ninja_c_binary_target_writer.h" | |
| #include "gn/ninja_rust_binary_target_writer.h" | |
| #include "gn/ninja_target_command_util.h" | |
| #include "gn/ninja_utils.h" | |
| #include "gn/pool.h" | |
| #include "gn/settings.h" | |
| #include "gn/string_output_buffer.h" | |
| #include "gn/string_utils.h" | |
| #include "gn/substitution_writer.h" | |
| #include "gn/target.h" | |
| #include "gn/variables.h" | |
| namespace { | |
| // Returns the proper escape options for writing compiler and linker flags. | |
| EscapeOptions GetFlagOptions() { | |
| EscapeOptions opts; | |
| opts.mode = ESCAPE_NINJA_COMMAND; | |
| return opts; | |
| } | |
| std::vector<const Target*> ExpandModules(const LabelTargetVector& targets) { | |
| std::vector<const LabelTargetVector*> stack = {&targets}; | |
| std::unordered_set<const Target*> visited; | |
| std::vector<const Target*> modules; | |
| while (!stack.empty()) { | |
| const LabelTargetVector* current = stack.back(); | |
| stack.pop_back(); | |
| for (const auto& pair : *current) { | |
| const Target* target = pair.ptr; | |
| if (visited.insert(target).second) { | |
| if (target->module_type().none()) { | |
| stack.push_back(&target->public_deps()); | |
| // If you declare `public_deps = ...` on a group, it shows up as a | |
| // private dep. Probably because groups don't distinguish between | |
| // public and private deps. | |
| if (target->output_type() == Target::GROUP) { | |
| stack.push_back(&target->private_deps()); | |
| } | |
| } else { | |
| modules.push_back(target); | |
| } | |
| } | |
| } | |
| } | |
| return modules; | |
| } | |
| void WriteModuleMapHeaders(std::ostream& out, | |
| const SourceDir& out_dir, | |
| const Target::FileList& headers, | |
| const Settings* settings) { | |
| for (const auto& header : headers) { | |
| if (header.GetType() == SourceFile::SOURCE_H) { | |
| out << " textual header \""; | |
| out << RebasePath(header.value(), out_dir, | |
| settings->build_settings()->root_path_utf8()); | |
| out << "\"\n"; | |
| } | |
| } | |
| } | |
| void WriteModuleDeps(std::ostream& out, | |
| const std::vector<const Target*>& deps, | |
| const SourceDir& base) { | |
| for (const auto& dep : deps) { | |
| auto module_name = dep->module_name(); | |
| auto modulemap = RebasePath(dep->modulemap_file()->value(), base); | |
| out << " extern module \"" << module_name << "\" \"" << modulemap | |
| << "\"\n"; | |
| out << " use \"" << module_name << "\"\n"; | |
| } | |
| } | |
| } // namespace | |
| NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target, | |
| std::ostream& out) | |
| : NinjaTargetWriter(target, out), | |
| rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) {} | |
| NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default; | |
| void NinjaBinaryTargetWriter::Run() { | |
| if (target_->source_types_used().RustSourceUsed()) { | |
| NinjaRustBinaryTargetWriter writer(target_, out_); | |
| writer.SetResolvedTargetData(GetResolvedTargetData()); | |
| writer.SetNinjaOutputs(ninja_outputs_); | |
| writer.Run(); | |
| return; | |
| } | |
| NinjaCBinaryTargetWriter writer(target_, out_); | |
| writer.SetResolvedTargetData(GetResolvedTargetData()); | |
| writer.SetNinjaOutputs(ninja_outputs_); | |
| writer.Run(); | |
| } | |
| void NinjaBinaryTargetWriter::WritePublicModuleMap(std::ostream& out, | |
| const SourceDir& out_dir) { | |
| out << "module \"" << target_->module_name() << "\" {\n"; | |
| if (target_->all_headers_public()) { | |
| WriteModuleMapHeaders(out, out_dir, target_->sources(), settings_); | |
| } else { | |
| WriteModuleMapHeaders(out, out_dir, target_->public_headers(), settings_); | |
| } | |
| auto base = target_->modulemap_file()->GetDir(); | |
| auto deps = ExpandModules(target_->public_deps()); | |
| std::ranges::sort(deps, [](const Target* lhs, const Target* rhs) { | |
| return lhs->module_name() < rhs->module_name(); | |
| }); | |
| WriteModuleDeps(out, deps, base); | |
| out << " export *\n}\n"; | |
| } | |
| void NinjaBinaryTargetWriter::WritePrivateModuleMap(std::ostream& out, | |
| const SourceDir& out_dir) { | |
| auto base = target_->modulemap_file()->GetDir(); | |
| auto module_name = target_->module_name(); | |
| // Though it's not documented, clang special-cases modules suffixed with | |
| // _Private. Private and public in the context of clang means basically the | |
| // same thing as in the context of GN. | |
| out << "module \"" << module_name << "_Private\" {\n"; | |
| if (!target_->all_headers_public()) { | |
| WriteModuleMapHeaders(out, out_dir, target_->sources(), settings_); | |
| } | |
| out << " extern module \"" << module_name << "\" \"" | |
| << target_->modulemap_file()->GetName() << "\"\n"; | |
| out << " use \"" << module_name << "\"\n"; | |
| auto deps = ExpandModules(target_->private_deps()); | |
| auto pub_deps = ExpandModules(target_->public_deps()); | |
| deps.insert(deps.end(), pub_deps.begin(), pub_deps.end()); | |
| std::ranges::sort(deps, [](const Target* lhs, const Target* rhs) { | |
| return lhs->module_name() < rhs->module_name(); | |
| }); | |
| auto dirty = std::ranges::unique(deps); | |
| deps.erase(dirty.begin(), dirty.end()); | |
| WriteModuleDeps(out, deps, base); | |
| out << "}\n"; | |
| } | |
| std::vector<OutputFile> | |
| NinjaBinaryTargetWriter::WriteInputsStampOrPhonyAndGetDep( | |
| size_t num_output_uses) const { | |
| CHECK(target_->toolchain()) << "Toolchain not set on target " | |
| << target_->label().GetUserVisibleName(true); | |
| UniqueVector<const SourceFile*> inputs; | |
| for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) { | |
| for (const auto& input : iter.cur().inputs()) { | |
| inputs.push_back(&input); | |
| } | |
| } | |
| if (inputs.size() == 0) | |
| return std::vector<OutputFile>(); // No inputs | |
| // If we only have one input, return it directly instead of writing a phony | |
| // target for it. | |
| if (inputs.size() == 1) { | |
| return std::vector<OutputFile>{ | |
| OutputFile(settings_->build_settings(), *inputs[0])}; | |
| } | |
| std::vector<OutputFile> outs; | |
| for (const SourceFile* source : inputs) | |
| outs.push_back(OutputFile(settings_->build_settings(), *source)); | |
| // 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; | |
| OutputFile 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 those would have been peeled off already. | |
| CHECK(!inputs.empty()); | |
| stamp_or_phony = GetOutputFile(*target_, BuildDirType::PHONY, | |
| target_->label().name(), ".inputs"); | |
| tool = BuiltinTool::kBuiltinToolPhony; | |
| } else { | |
| // Make a stamp target. | |
| stamp_or_phony = GetOutputFile(*target_, BuildDirType::OBJ, | |
| target_->label().name(), ".inputs.stamp"); | |
| tool = GetNinjaRulePrefixForToolchain(settings_) + | |
| GeneralTool::kGeneralToolStamp; | |
| } | |
| out_ << "build "; | |
| WriteOutput(stamp_or_phony); | |
| out_ << ": " << tool; | |
| // File inputs. | |
| for (const auto* input : inputs) { | |
| out_ << " "; | |
| path_output_.WriteFile(out_, *input); | |
| } | |
| out_ << std::endl; | |
| return {stamp_or_phony}; | |
| } | |
| NinjaBinaryTargetWriter::ClassifiedDeps | |
| NinjaBinaryTargetWriter::GetClassifiedDeps() const { | |
| ClassifiedDeps classified_deps; | |
| const auto& target_deps = resolved().GetTargetDeps(target_); | |
| // Normal public/private deps. | |
| for (const Target* dep : target_deps.linked_deps()) { | |
| ClassifyDependency(dep, &classified_deps); | |
| } | |
| // Inherited libraries. | |
| for (const auto& inherited : resolved().GetInheritedLibraries(target_)) { | |
| ClassifyDependency(inherited.target(), &classified_deps); | |
| } | |
| // Data deps. | |
| for (const Target* data_dep : target_deps.data_deps()) | |
| classified_deps.non_linkable_deps.push_back(data_dep); | |
| return classified_deps; | |
| } | |
| void NinjaBinaryTargetWriter::ClassifyDependency( | |
| const Target* dep, | |
| ClassifiedDeps* classified_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 (can_link_libs && dep->builds_swift_module()) | |
| classified_deps->swiftmodule_deps.push_back(dep); | |
| if (target_->source_types_used().RustSourceUsed() && | |
| (target_->output_type() == Target::RUST_LIBRARY || | |
| target_->output_type() == Target::STATIC_LIBRARY) && | |
| dep->IsLinkable()) { | |
| // Rust libraries and static libraries aren't final, but need to have the | |
| // link lines of all transitive deps specified. | |
| classified_deps->linkable_deps.push_back(dep); | |
| } else if (dep->output_type() == Target::SOURCE_SET || | |
| // If a complete static library depends on an incomplete static | |
| // library, manually link in the object files of the dependent | |
| // library as if it were a source set. This avoids problems with | |
| // braindead tools such as ar which don't properly link dependent | |
| // static libraries. | |
| (target_->complete_static_lib() && | |
| (dep->output_type() == Target::STATIC_LIBRARY && | |
| !dep->complete_static_lib()))) { | |
| // Source sets have their object files linked into final targets | |
| // (shared libraries, executables, loadable modules, and complete static | |
| // libraries). Intermediate static libraries and other source sets | |
| // just forward the dependency, otherwise the files in the source | |
| // set can easily get linked more than once which will cause | |
| // multiple definition errors. | |
| if (can_link_libs) | |
| AddSourceSetFiles(dep, &classified_deps->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 phony target | |
| // 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. | |
| classified_deps->non_linkable_deps.push_back(dep); | |
| } else if (target_->complete_static_lib() && dep->IsFinal()) { | |
| classified_deps->non_linkable_deps.push_back(dep); | |
| } else if (can_link_libs && dep->IsLinkable()) { | |
| classified_deps->linkable_deps.push_back(dep); | |
| } else if (dep->output_type() == Target::CREATE_BUNDLE && | |
| dep->bundle_data().is_framework()) { | |
| classified_deps->framework_deps.push_back(dep); | |
| } else { | |
| classified_deps->non_linkable_deps.push_back(dep); | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::AddSourceSetFiles( | |
| const Target* source_set, | |
| UniqueVector<OutputFile>* obj_files) const { | |
| std::vector<OutputFile> tool_outputs; // Prevent allocation in loop. | |
| // Compute object files for all sources. Only link the first output from | |
| // the tool if there are more than one. | |
| for (const auto& source : source_set->sources()) { | |
| const char* tool_name = Tool::kToolNone; | |
| // Do not add .pcm files as they are not object files linked to final | |
| // binaries. | |
| if (source.GetType() != SourceFile::SOURCE_MODULEMAP && | |
| source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) | |
| obj_files->push_back(tool_outputs[0]); | |
| } | |
| // Swift files may generate one object file per module or one per source file | |
| // depending on how the compiler is invoked (whole module optimization). | |
| if (source_set->source_types_used().SwiftSourceUsed()) { | |
| std::vector<OutputFile> outputs; | |
| source_set->swift_values().GetOutputs(source_set, &outputs); | |
| for (const OutputFile& output : outputs) { | |
| SourceFile output_as_source = | |
| output.AsSourceFile(source_set->settings()->build_settings()); | |
| if (output_as_source.IsObjectType()) { | |
| obj_files->push_back(output); | |
| } | |
| } | |
| } | |
| // Add MSVC precompiled header object files. GCC .gch files are not object | |
| // files so they are omitted. | |
| if (source_set->config_values().has_precompiled_headers()) { | |
| if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) { | |
| const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc); | |
| if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) { | |
| GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs); | |
| obj_files->Append(tool_outputs.begin(), tool_outputs.end()); | |
| } | |
| } | |
| if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) { | |
| const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx); | |
| if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) { | |
| GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs); | |
| obj_files->Append(tool_outputs.begin(), tool_outputs.end()); | |
| } | |
| } | |
| if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) { | |
| const CTool* tool = | |
| source_set->toolchain()->GetToolAsC(CTool::kCToolObjC); | |
| if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) { | |
| GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs); | |
| obj_files->Append(tool_outputs.begin(), tool_outputs.end()); | |
| } | |
| } | |
| if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) { | |
| const CTool* tool = | |
| source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx); | |
| if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) { | |
| GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs); | |
| obj_files->Append(tool_outputs.begin(), tool_outputs.end()); | |
| } | |
| } | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteCompilerBuildLine( | |
| const std::vector<SourceFile>& sources, | |
| const std::vector<OutputFile>& extra_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| const Tool* tool, | |
| const std::vector<OutputFile>& outputs, | |
| bool can_write_source_info, | |
| bool restat_output_allowed) { | |
| out_ << "build"; | |
| WriteOutputs(outputs); | |
| out_ << ": " << rule_prefix_ << tool->name(); | |
| path_output_.WriteFiles(out_, sources); | |
| if (!extra_deps.empty() || !tool->inputs().empty()) { | |
| out_ << " |"; | |
| path_output_.WriteFiles(out_, extra_deps); | |
| if (auto phony = tool->inputs_phony_or_file(rule_prefix_, | |
| *settings_->build_settings())) { | |
| out_ << " "; | |
| path_output_.WriteFile(out_, *phony); | |
| } | |
| } | |
| if (!order_only_deps.empty()) { | |
| out_ << " ||"; | |
| path_output_.WriteFiles(out_, order_only_deps); | |
| } | |
| WriteValidations(); | |
| out_ << std::endl; | |
| if (!sources.empty() && can_write_source_info) { | |
| out_ << " " << "source_file_part = " << sources[0].GetName(); | |
| out_ << std::endl; | |
| out_ << " " << "source_name_part = " | |
| << FindFilenameNoExtension(&sources[0].value()); | |
| out_ << std::endl; | |
| } | |
| if (restat_output_allowed) { | |
| out_ << " restat = 1" << std::endl; | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteCustomLinkerFlags(std::ostream& out, | |
| const Tool* tool) { | |
| if (tool->AsC() || (tool->AsRust() && tool->AsRust()->MayLink())) { | |
| // First the ldflags from the target and its config. | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::ldflags, | |
| GetFlagOptions(), out); | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteLibrarySearchPath(std::ostream& out, | |
| const Tool* tool) { | |
| // Write library search paths that have been recursively pushed | |
| // through the dependency tree. | |
| const auto& all_lib_dirs = resolved().GetLinkedLibraryDirs(target_); | |
| 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); | |
| } | |
| } | |
| const auto& all_framework_dirs = resolved().GetLinkedFrameworkDirs(target_); | |
| if (!all_framework_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 framework_path_output( | |
| path_output_.current_dir(), | |
| settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); | |
| for (size_t i = 0; i < all_framework_dirs.size(); i++) { | |
| out << " " << tool->framework_dir_switch(); | |
| framework_path_output.WriteDir(out, all_framework_dirs[i], | |
| PathOutput::DIR_NO_LAST_SLASH); | |
| } | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteLinkerFlags( | |
| std::ostream& out, | |
| const Tool* tool, | |
| const SourceFile* optional_def_file) { | |
| // First any ldflags | |
| WriteCustomLinkerFlags(out, tool); | |
| // Then the library search path | |
| WriteLibrarySearchPath(out, tool); | |
| if (optional_def_file) { | |
| out_ << " /DEF:"; | |
| path_output_.WriteFile(out, *optional_def_file); | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteLibs(std::ostream& out, const Tool* tool) { | |
| // Libraries that have been recursively pushed through the dependency tree. | |
| // 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); | |
| EscapeOptions lib_escape_opts; | |
| lib_escape_opts.mode = ESCAPE_NINJA_COMMAND; | |
| const auto& all_libs = resolved().GetLinkedLibraries(target_); | |
| 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 << " " << tool->linker_arg(); | |
| lib_path_output.WriteFile(out, lib_file.source_file()); | |
| } else { | |
| out << " " << tool->lib_switch(); | |
| EscapeStringToStream(out, lib_value, lib_escape_opts); | |
| } | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteFrameworks(std::ostream& out, | |
| const Tool* tool) { | |
| // Frameworks that have been recursively pushed through the dependency tree. | |
| FrameworksWriter writer(tool->framework_switch()); | |
| const auto& all_frameworks = resolved().GetLinkedFrameworks(target_); | |
| for (size_t i = 0; i < all_frameworks.size(); i++) { | |
| writer(all_frameworks[i], out); | |
| } | |
| FrameworksWriter weak_writer(tool->weak_framework_switch()); | |
| const auto& all_weak_frameworks = resolved().GetLinkedWeakFrameworks(target_); | |
| for (size_t i = 0; i < all_weak_frameworks.size(); i++) { | |
| weak_writer(all_weak_frameworks[i], out); | |
| } | |
| if (!tool->weak_library_switch().empty()) { | |
| WeakLibrariesWriter weak_library_writer(tool->weak_library_switch()); | |
| const auto& all_weak_libraries = resolved().GetLinkedWeakLibraries(target_); | |
| for (const auto& weak_library : all_weak_libraries) { | |
| weak_library_writer(weak_library, out); | |
| } | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WriteSwiftModules( | |
| std::ostream& out, | |
| const Tool* tool, | |
| const std::vector<OutputFile>& swiftmodules) { | |
| // Since we're passing these on the command line to the linker and not | |
| // to Ninja, we need to do shell escaping. | |
| PathOutput swiftmodule_path_output( | |
| path_output_.current_dir(), settings_->build_settings()->root_path_utf8(), | |
| ESCAPE_NINJA_COMMAND); | |
| for (const OutputFile& swiftmodule : swiftmodules) { | |
| out << " " << tool->swiftmodule_switch(); | |
| swiftmodule_path_output.WriteFile(out, swiftmodule); | |
| } | |
| } | |
| void NinjaBinaryTargetWriter::WritePool(std::ostream& out) { | |
| if (target_->pool().ptr) { | |
| out << " pool = "; | |
| out << target_->pool().ptr->GetNinjaName( | |
| settings_->default_toolchain_label()); | |
| out << std::endl; | |
| } | |
| } | |
| // Returns true if the build statement for |target|'s dependency output is | |
| // written by NinjaCBinaryTargetWriter and lists the order-only deps of all | |
| // non-linkable deps of |target|, which include (almost, see below) all of | |
| // |target|'s inherited libraries: Source sets list them on their .linkdeps | |
| // phony target, static libraries on their link step. | |
| static bool ListsOrderOnlyDepsOfInheritedLibs(const Target* target) { | |
| return (target->output_type() == Target::SOURCE_SET || | |
| target->output_type() == Target::STATIC_LIBRARY) && | |
| !target->IsFinal() && !target->source_types_used().RustSourceUsed() && | |
| target->has_dependency_output(); | |
| } | |
| std::vector<OutputFile> | |
| NinjaBinaryTargetWriter::GetOrderOnlyDepsFromNonLinkableDeps( | |
| const UniqueVector<const Target*>& non_linkable_deps) const { | |
| // |non_linkable_deps| contains the transitive closure of all inherited | |
| // libraries. Listing all of them is redundant: If this target depends on a | |
| // source set or static library D, then every library inherited through D is | |
| // a non-linkable dep of D, and D's build statement (which this target | |
| // depends on) already has an order-only dep on it. So only list: | |
| // 1. direct deps, looking through groups | |
| // 2. libraries inherited through direct deps whose build statement doesn't | |
| // list them (see ListsOrderOnlyDepsOfInheritedLibs()) | |
| // 3. frameworks, since ClassifyDependency() doesn't treat them as | |
| // non-linkable deps for non-final targets. | |
| TargetSet direct_deps; | |
| TargetSet inherited_through_other_deps; | |
| bool can_omit_deps = false; | |
| // The Rust writer lists some deps differently; keep it simple there. | |
| if (!target_->source_types_used().RustSourceUsed()) { | |
| const auto& target_deps = resolved().GetTargetDeps(target_); | |
| std::vector<const Target*> worklist(target_deps.linked_deps().begin(), | |
| target_deps.linked_deps().end()); | |
| while (!worklist.empty()) { | |
| const Target* dep = worklist.back(); | |
| worklist.pop_back(); | |
| if (!direct_deps.add(dep)) | |
| continue; | |
| if (dep->output_type() == Target::GROUP) { | |
| const auto& group_deps = resolved().GetTargetDeps(dep); | |
| worklist.insert(worklist.end(), group_deps.linked_deps().begin(), | |
| group_deps.linked_deps().end()); | |
| continue; | |
| } | |
| if (ListsOrderOnlyDepsOfInheritedLibs(dep)) { | |
| can_omit_deps = true; | |
| continue; | |
| } | |
| // Final targets only forward (some) final targets, see | |
| // ResolvedTargetData::ComputeInheritedLibsFor(). | |
| for (const auto& pair : resolved().GetInheritedLibraries(dep)) { | |
| if (!dep->IsFinal() || pair.target()->IsFinal()) | |
| inherited_through_other_deps.add(pair.target()); | |
| } | |
| } | |
| for (const Target* data_dep : target_deps.data_deps()) | |
| direct_deps.add(data_dep); | |
| } | |
| UniqueVector<OutputFile> outputs_to_write; | |
| for (const Target* dep : non_linkable_deps) { | |
| if (can_omit_deps && !direct_deps.contains(dep) && | |
| !inherited_through_other_deps.contains(dep) && | |
| dep->output_type() != Target::CREATE_BUNDLE) { | |
| continue; | |
| } | |
| outputs_to_write.Append(resolved().GetOrderOnlyDeps(dep)); | |
| } | |
| return outputs_to_write.release(); | |
| } |