| // Copyright 2019 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gn/ninja_c_binary_target_writer.h" | |
| #include <stddef.h> | |
| #include <string.h> | |
| #include <cstring> | |
| #include <set> | |
| #include <sstream> | |
| #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/deps_iterator.h" | |
| #include "gn/err.h" | |
| #include "gn/escape.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/general_tool.h" | |
| #include "gn/ninja_module_writer_util.h" | |
| #include "gn/ninja_target_command_util.h" | |
| #include "gn/ninja_utils.h" | |
| #include "gn/pool.h" | |
| #include "gn/scheduler.h" | |
| #include "gn/settings.h" | |
| #include "gn/string_utils.h" | |
| #include "gn/substitution_writer.h" | |
| #include "gn/target.h" | |
| namespace { | |
| // Returns the proper escape options for writing compiler and linker flags. | |
| EscapeOptions GetFlagOptions() { | |
| EscapeOptions opts; | |
| opts.mode = ESCAPE_NINJA_COMMAND; | |
| return opts; | |
| } | |
| // Returns the language-specific lang recognized by gcc’s -x flag for | |
| // precompiled header files. | |
| const char* GetPCHLangForToolType(const char* name) { | |
| if (name == CTool::kCToolCc) | |
| return "c-header"; | |
| if (name == CTool::kCToolCxx) | |
| return "c++-header"; | |
| if (name == CTool::kCToolObjC) | |
| return "objective-c-header"; | |
| if (name == CTool::kCToolObjCxx) | |
| return "objective-c++-header"; | |
| NOTREACHED() << "Not a valid PCH tool type: " << name; | |
| return ""; | |
| } | |
| } // namespace | |
| NinjaCBinaryTargetWriter::NinjaCBinaryTargetWriter(const Target* target, | |
| std::ostream& out) | |
| : NinjaBinaryTargetWriter(target, out), | |
| tool_(target->toolchain()->GetToolForTargetFinalOutputAsC(target)) {} | |
| NinjaCBinaryTargetWriter::~NinjaCBinaryTargetWriter() = default; | |
| void NinjaCBinaryTargetWriter::Run() { | |
| std::set<ClangModuleDep> module_dep_info = | |
| GetModuleDepsInformation(target_, resolved()); | |
| WriteCompilerVars(module_dep_info); | |
| size_t num_output_uses = target_->sources().size(); | |
| std::vector<OutputFile> input_deps = | |
| WriteInputsStampOrPhonyAndGetDep(num_output_uses); | |
| // The input dependencies will be an order-only dependency. This will cause | |
| // Ninja to make sure the inputs are up to date before compiling this source, | |
| // but changes in the inputs deps won't cause the file to be recompiled. | |
| // | |
| // This is important to prevent changes in unrelated actions that are | |
| // upstream of this target from causing everything to be recompiled. | |
| // | |
| // Why can we get away with this rather than using implicit deps ("|", which | |
| // will force rebuilds when the inputs change)? For source code, the | |
| // computed dependencies of all headers will be computed by the compiler, | |
| // which will cause source rebuilds if any "real" upstream dependencies | |
| // change. | |
| // | |
| // If a .cc file is generated by an input dependency, Ninja will see the | |
| // input to the build rule doesn't exist, and that it is an output from a | |
| // previous step, and build the previous step first. This is a "real" | |
| // dependency and doesn't need | or || to express. | |
| // | |
| // The only case where this rule matters is for the first build where no .d | |
| // files exist, and Ninja doesn't know what that source file depends on. In | |
| // this case it's sufficient to ensure that the upstream dependencies are | |
| // built first. This is exactly what Ninja's order-only dependencies | |
| // expresses. | |
| // | |
| // The order only deps are referenced by each source file compile, | |
| // but also by PCH compiles. The latter are annoying to count, so omit | |
| // them here. This means that binary targets with a single source file | |
| // that also use PCH files won't have a phony target even though having | |
| // one would make output ninja file size a bit lower. That's ok, binary | |
| // targets with a single source are rare. | |
| NinjaTargetWriter::InputDeps stamp_deps = WriteInputDepsStampOrPhonyAndGetDep( | |
| std::vector<const Target*>(), num_output_uses); | |
| input_deps.insert(input_deps.end(), stamp_deps.implicit.begin(), | |
| stamp_deps.implicit.end()); | |
| std::vector<OutputFile> order_only_deps = stamp_deps.order_only; | |
| // For GCC builds, the .gch files are not object files, but still need to be | |
| // added as explicit dependencies below. The .gch output files are placed in | |
| // |pch_other_files|. This is to prevent linking against them. | |
| std::vector<OutputFile> pch_obj_files; | |
| std::vector<OutputFile> pch_other_files; | |
| WritePCHCommands(input_deps, order_only_deps, &pch_obj_files, | |
| &pch_other_files); | |
| std::vector<OutputFile>* pch_files = | |
| !pch_obj_files.empty() ? &pch_obj_files : &pch_other_files; | |
| // Treat all pch output files as explicit dependencies of all | |
| // compiles that support them. Some notes: | |
| // | |
| // - On Windows, the .pch file is the input to the compile, not the | |
| // precompiled header's corresponding object file that we're using here. | |
| // But Ninja's depslog doesn't support multiple outputs from the | |
| // precompiled header compile step (it outputs both the .pch file and a | |
| // corresponding .obj file). So we consistently list the .obj file and the | |
| // .pch file we really need comes along with it. | |
| // | |
| // - GCC .gch files are not object files, therefore they are not added to the | |
| // object file list. | |
| std::vector<OutputFile> obj_files; | |
| std::vector<OutputFile> extra_files; | |
| std::vector<SourceFile> other_files; | |
| std::vector<OutputFile>* stamp_files = &obj_files; // default | |
| if (!target_->source_types_used().SwiftSourceUsed()) { | |
| WriteSources(*pch_files, input_deps, order_only_deps, module_dep_info, | |
| &obj_files, &extra_files, &other_files); | |
| } else { | |
| stamp_files = &extra_files; // Swift generates more than object files | |
| WriteSwiftSources(input_deps, order_only_deps, &obj_files, &extra_files); | |
| } | |
| // Link all MSVC pch object files. The vector will be empty on GCC toolchains. | |
| obj_files.insert(obj_files.end(), pch_obj_files.begin(), pch_obj_files.end()); | |
| if (!CheckForDuplicateObjectFiles(obj_files)) | |
| return; | |
| if (target_->output_type() == Target::SOURCE_SET) { | |
| #ifndef NDEBUG | |
| // Verify that the function that separately computes a source set's object | |
| // files match the object files just computed. | |
| UniqueVector<OutputFile> computed_obj; | |
| AddSourceSetFiles(target_, &computed_obj); | |
| DCHECK_EQ(obj_files.size(), computed_obj.size()); | |
| for (const auto& obj : obj_files) | |
| DCHECK(computed_obj.Contains(obj)); | |
| #endif | |
| if (!target_->source_types_used().SwiftSourceUsed()) { | |
| // Add extra files like pre compiled module to stamp files for phony | |
| // targets. | |
| stamp_files->insert(stamp_files->end(), extra_files.begin(), | |
| extra_files.end()); | |
| } | |
| WriteSourceSetStamp(*stamp_files); | |
| } else { | |
| WriteLinkerStuff(obj_files, other_files, input_deps); | |
| } | |
| } | |
| void NinjaCBinaryTargetWriter::WriteCompilerVars( | |
| const std::set<ClangModuleDep>& module_dep_info) { | |
| const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); | |
| WriteCCompilerVars(subst, /*indent=*/false, | |
| /*respect_source_types_used=*/true); | |
| WriteModuleNameSubstitution(); | |
| if (!module_dep_info.empty()) { | |
| // TODO(scottmg): Currently clang modules only working for C++. | |
| if (target_->source_types_used().Get(SourceFile::SOURCE_CPP) || | |
| target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) { | |
| WriteModuleDepsSubstitution(&CSubstitutionModuleDeps, module_dep_info, | |
| true); | |
| WriteModuleDepsSubstitution(&CSubstitutionModuleDepsNoSelf, | |
| module_dep_info, false); | |
| } | |
| } | |
| WriteSharedVars(subst); | |
| } | |
| void NinjaCBinaryTargetWriter::WriteModuleNameSubstitution() { | |
| if (target_->toolchain()->substitution_bits().used.count( | |
| &CSubstitutionModuleName)) { | |
| out_ << CSubstitutionModuleName.ninja_name << " = "; | |
| EscapeOptions options; | |
| options.mode = ESCAPE_NINJA; | |
| EscapeStringToStream(out_, target_->module_name(), options); | |
| out_ << std::endl; | |
| } | |
| } | |
| void NinjaCBinaryTargetWriter::WriteModuleDepsSubstitution( | |
| const Substitution* substitution, | |
| const std::set<ClangModuleDep>& module_dep_info, | |
| bool include_self) { | |
| if (target_->toolchain()->substitution_bits().used.count(substitution)) { | |
| EscapeOptions options; | |
| options.mode = ESCAPE_NINJA_COMMAND; | |
| out_ << substitution->ninja_name << " ="; | |
| for (const auto& module_dep : module_dep_info) { | |
| module_dep.Write(out_, path_output_, include_self); | |
| } | |
| out_ << std::endl; | |
| } | |
| } | |
| void NinjaCBinaryTargetWriter::WritePCHCommands( | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| std::vector<OutputFile>* object_files, | |
| std::vector<OutputFile>* other_files) { | |
| if (!target_->config_values().has_precompiled_headers()) | |
| return; | |
| const CTool* tool_c = target_->toolchain()->GetToolAsC(CTool::kCToolCc); | |
| if (tool_c && tool_c->precompiled_header_type() != CTool::PCH_NONE && | |
| target_->source_types_used().Get(SourceFile::SOURCE_C)) { | |
| WritePCHCommand(&CSubstitutionCFlagsC, tool_c, | |
| tool_c->precompiled_header_type(), input_deps, | |
| order_only_deps, object_files, other_files); | |
| } | |
| const CTool* tool_cxx = target_->toolchain()->GetToolAsC(CTool::kCToolCxx); | |
| if (tool_cxx && tool_cxx->precompiled_header_type() != CTool::PCH_NONE && | |
| target_->source_types_used().Get(SourceFile::SOURCE_CPP)) { | |
| WritePCHCommand(&CSubstitutionCFlagsCc, tool_cxx, | |
| tool_cxx->precompiled_header_type(), input_deps, | |
| order_only_deps, object_files, other_files); | |
| } | |
| const CTool* tool_objc = target_->toolchain()->GetToolAsC(CTool::kCToolObjC); | |
| if (tool_objc && tool_objc->precompiled_header_type() == CTool::PCH_GCC && | |
| target_->source_types_used().Get(SourceFile::SOURCE_M)) { | |
| WritePCHCommand(&CSubstitutionCFlagsObjC, tool_objc, | |
| tool_objc->precompiled_header_type(), input_deps, | |
| order_only_deps, object_files, other_files); | |
| } | |
| const CTool* tool_objcxx = | |
| target_->toolchain()->GetToolAsC(CTool::kCToolObjCxx); | |
| if (tool_objcxx && tool_objcxx->precompiled_header_type() == CTool::PCH_GCC && | |
| target_->source_types_used().Get(SourceFile::SOURCE_MM)) { | |
| WritePCHCommand(&CSubstitutionCFlagsObjCc, tool_objcxx, | |
| tool_objcxx->precompiled_header_type(), input_deps, | |
| order_only_deps, object_files, other_files); | |
| } | |
| } | |
| void NinjaCBinaryTargetWriter::WritePCHCommand( | |
| const Substitution* flag_type, | |
| const Tool* tool, | |
| CTool::PrecompiledHeaderType header_type, | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| std::vector<OutputFile>* object_files, | |
| std::vector<OutputFile>* other_files) { | |
| switch (header_type) { | |
| case CTool::PCH_MSVC: | |
| WriteWindowsPCHCommand(flag_type, tool, input_deps, order_only_deps, | |
| object_files); | |
| break; | |
| case CTool::PCH_GCC: | |
| WriteGCCPCHCommand(flag_type, tool, input_deps, order_only_deps, | |
| other_files); | |
| break; | |
| case CTool::PCH_NONE: | |
| NOTREACHED() << "Cannot write a PCH command with no PCH header type"; | |
| break; | |
| } | |
| } | |
| void NinjaCBinaryTargetWriter::WriteGCCPCHCommand( | |
| const Substitution* flag_type, | |
| const Tool* tool, | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| std::vector<OutputFile>* gch_files) { | |
| // Compute the pch output file (it will be language-specific). | |
| std::vector<OutputFile> outputs; | |
| auto tool_name = tool->name(); | |
| GetPCHOutputFiles(target_, tool_name, &outputs); | |
| if (outputs.empty()) | |
| return; | |
| gch_files->insert(gch_files->end(), outputs.begin(), outputs.end()); | |
| std::vector<OutputFile> extra_deps; | |
| std::copy(input_deps.begin(), input_deps.end(), | |
| std::back_inserter(extra_deps)); | |
| // Build line to compile the file. | |
| WriteCompilerBuildLine({target_->config_values().precompiled_source()}, | |
| extra_deps, order_only_deps, tool, outputs); | |
| // This build line needs a custom language-specific flags value. Rule-specific | |
| // variables are just indented underneath the rule line. | |
| out_ << " " << flag_type->ninja_name << " ="; | |
| // Each substitution flag is overwritten in the target rule to replace the | |
| // implicitly generated -include flag with the -x <header lang> flag required | |
| // for .gch targets. | |
| EscapeOptions opts = GetFlagOptions(); | |
| if (tool_name == CTool::kCToolCc) { | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::cflags_c, opts, | |
| out_); | |
| } else if (tool_name == CTool::kCToolCxx) { | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::cflags_cc, | |
| opts, out_); | |
| } else if (tool_name == CTool::kCToolObjC) { | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::cflags_objc, | |
| opts, out_); | |
| } else if (tool_name == CTool::kCToolObjCxx) { | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::cflags_objcc, | |
| opts, out_); | |
| } | |
| // Append the command to specify the language of the .gch file. | |
| out_ << " -x " << GetPCHLangForToolType(tool_name); | |
| // Write two blank lines to help separate the PCH build lines from the | |
| // regular source build lines. | |
| out_ << std::endl << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteWindowsPCHCommand( | |
| const Substitution* flag_type, | |
| const Tool* tool, | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| std::vector<OutputFile>* object_files) { | |
| // Compute the pch output file (it will be language-specific). | |
| std::vector<OutputFile> outputs; | |
| GetPCHOutputFiles(target_, tool->name(), &outputs); | |
| if (outputs.empty()) | |
| return; | |
| object_files->insert(object_files->end(), outputs.begin(), outputs.end()); | |
| std::vector<OutputFile> extra_deps; | |
| std::copy(input_deps.begin(), input_deps.end(), | |
| std::back_inserter(extra_deps)); | |
| // Build line to compile the file. | |
| WriteCompilerBuildLine({target_->config_values().precompiled_source()}, | |
| extra_deps, order_only_deps, tool, outputs); | |
| // This build line needs a custom language-specific flags value. Rule-specific | |
| // variables are just indented underneath the rule line. | |
| out_ << " " << flag_type->ninja_name << " ="; | |
| // Append the command to generate the .pch file. | |
| // This adds the value to the existing flag instead of overwriting it. | |
| out_ << " ${" << flag_type->ninja_name << "}"; | |
| out_ << " /Yc" << target_->config_values().precompiled_header(); | |
| // Write two blank lines to help separate the PCH build lines from the | |
| // regular source build lines. | |
| out_ << std::endl << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteSources( | |
| const std::vector<OutputFile>& pch_deps, | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| const std::set<ClangModuleDep>& module_dep_info, | |
| std::vector<OutputFile>* object_files, | |
| std::vector<OutputFile>* extra_files, | |
| std::vector<SourceFile>* other_files) { | |
| DCHECK(!target_->source_types_used().SwiftSourceUsed()); | |
| object_files->reserve(object_files->size() + target_->sources().size()); | |
| std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop. | |
| std::vector<OutputFile> deps; | |
| // Collect unique C-family additional outputs from all recursive configs. | |
| std::vector<SubstitutionPattern> c_additional_outputs; | |
| std::set<std::string> seen_patterns; | |
| for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) { | |
| for (const auto& pattern : iter.cur().c_additional_outputs()) { | |
| std::string pattern_str = pattern.AsString(); | |
| if (seen_patterns.insert(std::move(pattern_str)).second) { | |
| c_additional_outputs.push_back(pattern); | |
| } | |
| } | |
| } | |
| for (const auto& source : target_->sources()) { | |
| DCHECK_NE(source.GetType(), SourceFile::SOURCE_SWIFT); | |
| // Clear the vector but maintain the max capacity to prevent reallocations. | |
| deps.resize(0); | |
| const char* tool_name = Tool::kToolNone; | |
| if (!target_->GetOutputFilesForSource(source, &tool_name, &tool_outputs)) { | |
| if (source.IsDefType()) | |
| other_files->push_back(source); | |
| continue; // No output for this source. | |
| } | |
| std::copy(input_deps.begin(), input_deps.end(), std::back_inserter(deps)); | |
| if (tool_name != Tool::kToolNone) { | |
| // Only include PCH deps that correspond to the tool type, for instance, | |
| // do not specify target_name.precompile.cc.obj (a CXX PCH file) as a dep | |
| // for the output of a C tool type. | |
| // | |
| // This makes the assumption that pch_deps only contains pch output files | |
| // with the naming scheme specified in GetWindowsPCHObjectExtension or | |
| // GetGCCPCHOutputExtension. | |
| const CTool* tool = target_->toolchain()->GetToolAsC(tool_name); | |
| if (tool->precompiled_header_type() != CTool::PCH_NONE) { | |
| for (const auto& dep : pch_deps) { | |
| std::string_view output_value = dep.value(); | |
| size_t extension_offset = FindExtensionOffset(output_value); | |
| if (extension_offset == std::string::npos) | |
| continue; | |
| std::string output_extension; | |
| if (tool->precompiled_header_type() == CTool::PCH_MSVC) { | |
| output_extension = GetWindowsPCHObjectExtension( | |
| tool_name, output_value.substr(extension_offset - 1)); | |
| } else if (tool->precompiled_header_type() == CTool::PCH_GCC) { | |
| output_extension = GetGCCPCHOutputExtension(tool_name); | |
| } | |
| if (output_value.compare( | |
| output_value.size() - output_extension.size(), | |
| output_extension.size(), output_extension) == 0) { | |
| deps.push_back(dep); | |
| } | |
| } | |
| } | |
| for (const auto& module_dep : module_dep_info) { | |
| if (module_dep.pcm && tool_outputs[0] != *module_dep.pcm) | |
| deps.push_back(*module_dep.pcm); | |
| } | |
| if (tool_name == CTool::kCToolCc || tool_name == CTool::kCToolCxx || | |
| tool_name == CTool::kCToolObjC || tool_name == CTool::kCToolObjCxx) { | |
| for (const auto& pattern : c_additional_outputs) { | |
| // Use ApplyPatternToCompilerAsOutputFile instead of | |
| // ApplyPatternToSourceAsOutputFile to support target-level | |
| // substitutions (e.g. {{target_out_dir}}) in addition to source-level | |
| // substitutions. | |
| OutputFile extra_output = | |
| SubstitutionWriter::ApplyPatternToCompilerAsOutputFile( | |
| target_, source, pattern); | |
| if (!extra_output.value().empty() && | |
| std::find(tool_outputs.begin(), tool_outputs.end(), | |
| extra_output) == tool_outputs.end()) { | |
| tool_outputs.push_back(std::move(extra_output)); | |
| } | |
| } | |
| } | |
| WriteCompilerBuildLine({source}, deps, order_only_deps, tool, | |
| tool_outputs); | |
| WritePool(out_); | |
| } | |
| // It's theoretically possible for a compiler to produce more than one | |
| // output, but we'll only link to the first output. | |
| if (!source.IsModuleMapType()) { | |
| object_files->push_back(tool_outputs[0]); | |
| } else { | |
| extra_files->push_back(tool_outputs[0]); | |
| } | |
| // Add additional outputs to extra_files so they are included in the | |
| // target's stamp file. | |
| for (size_t i = 1; i < tool_outputs.size(); ++i) { | |
| extra_files->push_back(tool_outputs[i]); | |
| } | |
| } | |
| out_ << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteSwiftSources( | |
| const std::vector<OutputFile>& input_deps, | |
| const std::vector<OutputFile>& order_only_deps, | |
| std::vector<OutputFile>* object_files, | |
| std::vector<OutputFile>* output_files) { | |
| DCHECK(target_->builds_swift_module()); | |
| target_->swift_values().GetOutputs(target_, output_files); | |
| const BuildSettings* build_settings = settings_->build_settings(); | |
| for (const OutputFile& output : *output_files) { | |
| const SourceFile output_as_source = output.AsSourceFile(build_settings); | |
| if (output_as_source.IsObjectType()) { | |
| object_files->push_back(output); | |
| } | |
| } | |
| UniqueVector<OutputFile> swift_order_only_deps; | |
| swift_order_only_deps.reserve(order_only_deps.size()); | |
| swift_order_only_deps.Append(order_only_deps.begin(), order_only_deps.end()); | |
| for (const Target* swiftmodule : | |
| resolved().GetSwiftModuleDependencies(target_)) { | |
| CHECK(swiftmodule->has_dependency_output()); | |
| { | |
| swift_order_only_deps.push_back(swiftmodule->dependency_output()); | |
| } | |
| } | |
| const Tool* tool = target_->swift_values().GetTool(target_); | |
| WriteCompilerBuildLine(target_->sources(), input_deps, | |
| swift_order_only_deps.vector(), tool, *output_files, | |
| /*can_write_source_info=*/false, | |
| /*restat_output_allowed=*/true); | |
| out_ << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteSourceSetStamp( | |
| const std::vector<OutputFile>& object_files) { | |
| // If the target doesn't have a dependency output, we shouldn't write | |
| // anything. | |
| if (!target_->has_dependency_output()) { | |
| return; | |
| } | |
| // The stamp rule for source sets is generally not used, since targets that | |
| // depend on this will reference the object files directly. However, writing | |
| // this rule allows the user to type the name of the target and get a build | |
| // which can be convenient for development. | |
| ClassifiedDeps classified_deps = GetClassifiedDeps(); | |
| // The classifier should never put extra object files in a source sets: any | |
| // source sets that we depend on should appear in our non-linkable deps | |
| // instead. | |
| DCHECK(classified_deps.extra_object_files.empty()); | |
| std::vector<OutputFile> order_only_deps = | |
| GetOrderOnlyDepsFromNonLinkableDeps(classified_deps.non_linkable_deps); | |
| // 1. Link-only phony target (.linkdeps) containing only object files. | |
| std::vector<OutputFile> link_files; | |
| const BuildSettings* build_settings = settings_->build_settings(); | |
| for (const auto& file : object_files) { | |
| if (file.AsSourceFile(build_settings).IsObjectType()) { | |
| link_files.push_back(file); | |
| } | |
| } | |
| OutputFile link_phony = target_->dependency_output(); | |
| std::string path(link_phony.value()); | |
| path.append(".linkdeps"); | |
| link_phony = OutputFile(std::move(path)); | |
| out_ << "build "; | |
| path_output_.WriteFile(out_, link_phony); | |
| out_ << ": " << BuiltinTool::kBuiltinToolPhony; | |
| path_output_.WriteFiles(out_, link_files); | |
| if (!order_only_deps.empty()) { | |
| out_ << " ||"; | |
| path_output_.WriteFiles(out_, order_only_deps); | |
| } | |
| out_ << std::endl; | |
| // 2. Default phony target containing all files (including additional | |
| // outputs). Depend on the .link target to avoid duplicating object files. | |
| out_ << "build "; | |
| path_output_.WriteFile(out_, target_->dependency_output()); | |
| out_ << ": " << BuiltinTool::kBuiltinToolPhony; | |
| out_ << " "; | |
| path_output_.WriteFile(out_, link_phony); | |
| // Collect non-object files (additional outputs) to add here. | |
| std::vector<OutputFile> non_object_files; | |
| for (const auto& file : object_files) { | |
| if (!file.AsSourceFile(build_settings).IsObjectType()) { | |
| non_object_files.push_back(file); | |
| } | |
| } | |
| path_output_.WriteFiles(out_, non_object_files); | |
| out_ << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteLinkerStuff( | |
| const std::vector<OutputFile>& object_files, | |
| const std::vector<SourceFile>& other_files, | |
| const std::vector<OutputFile>& input_deps) { | |
| std::vector<OutputFile> output_files; | |
| SubstitutionWriter::ApplyListToLinkerAsOutputFile( | |
| target_, tool_, tool_->outputs(), &output_files); | |
| out_ << "build"; | |
| WriteOutputs(output_files); | |
| out_ << ": " << rule_prefix_ << tool_->name(); | |
| ClassifiedDeps classified_deps = GetClassifiedDeps(); | |
| // Object files. | |
| path_output_.WriteFiles(out_, object_files); | |
| path_output_.WriteFiles(out_, classified_deps.extra_object_files); | |
| // Dependencies. | |
| std::vector<OutputFile> implicit_deps; | |
| std::vector<OutputFile> solibs; | |
| for (const Target* cur : classified_deps.linkable_deps) { | |
| // All linkable deps should have a link output file. | |
| DCHECK(!cur->link_output_file().value().empty()) | |
| << "No link output file for " | |
| << target_->label().GetUserVisibleName(false); | |
| if (cur->output_type() == Target::RUST_LIBRARY || | |
| cur->output_type() == Target::RUST_PROC_MACRO) | |
| continue; | |
| if (cur->has_dependency_output() && | |
| cur->dependency_output().value() != cur->link_output_file().value()) { | |
| // This is a shared library with separate link and deps files. Save for | |
| // later. | |
| implicit_deps.push_back(cur->dependency_output()); | |
| solibs.push_back(cur->link_output_file()); | |
| } else { | |
| // Normal case, just link to this target. | |
| out_ << " "; | |
| path_output_.WriteFile(out_, cur->link_output_file()); | |
| } | |
| } | |
| const SourceFile* optional_def_file = nullptr; | |
| if (!other_files.empty()) { | |
| for (const SourceFile& src_file : other_files) { | |
| if (src_file.IsDefType()) { | |
| optional_def_file = &src_file; | |
| implicit_deps.push_back( | |
| OutputFile(settings_->build_settings(), src_file)); | |
| break; // Only one def file is allowed. | |
| } | |
| } | |
| } | |
| // Libraries specified by paths. | |
| for (const auto& lib : resolved().GetLinkedLibraries(target_)) { | |
| if (lib.is_source_file()) { | |
| implicit_deps.push_back( | |
| OutputFile(settings_->build_settings(), lib.source_file())); | |
| } | |
| } | |
| // If any target creates a framework bundle, then treat it as an implicit | |
| // dependency via the phony target. This is a pessimisation as it is not | |
| // always necessary to relink the current target if one of the framework | |
| // is regenerated, but it ensure that if one of the framework API changes, | |
| // any dependent target will relink it (see crbug.com/1037607). | |
| for (const Target* dep : classified_deps.framework_deps) { | |
| if (dep->has_dependency_output()) | |
| implicit_deps.push_back(dep->dependency_output()); | |
| } | |
| // The input dependency is only needed if there are no object files, as the | |
| // dependency is normally provided transitively by the source files. | |
| std::copy(input_deps.begin(), input_deps.end(), | |
| std::back_inserter(implicit_deps)); | |
| if (auto phony = tool_->inputs_phony_or_file(rule_prefix_, | |
| *settings_->build_settings()); | |
| phony) { | |
| implicit_deps.emplace_back(std::move(*phony)); | |
| } | |
| // Any C++ target which depends on a Rust .rlib has to depend on its entire | |
| // tree of transitive rlibs found inside the linking target (which excludes | |
| // rlibs only depended on inside a shared library dependency). | |
| std::vector<OutputFile> transitive_rustlibs; | |
| if (target_->IsFinal()) { | |
| for (const auto& inherited : resolved().GetInheritedLibraries(target_)) { | |
| const Target* dep = inherited.target(); | |
| if (dep->output_type() == Target::RUST_LIBRARY) { | |
| CHECK(dep->has_dependency_output_file()); | |
| transitive_rustlibs.push_back(dep->dependency_output_file()); | |
| implicit_deps.push_back(dep->dependency_output_file()); | |
| } | |
| } | |
| } | |
| // Swift modules from dependencies (and possibly self). | |
| std::vector<OutputFile> swiftmodules; | |
| if (target_->IsFinal()) { | |
| for (const Target* dep : classified_deps.swiftmodule_deps) { | |
| swiftmodules.push_back(dep->swift_values().module_output_file()); | |
| implicit_deps.push_back(dep->swift_values().module_output_file()); | |
| } | |
| if (target_->builds_swift_module()) { | |
| swiftmodules.push_back(target_->swift_values().module_output_file()); | |
| implicit_deps.push_back(target_->swift_values().module_output_file()); | |
| } | |
| } | |
| // Append implicit dependencies collected above. | |
| if (!implicit_deps.empty()) { | |
| out_ << " |"; | |
| path_output_.WriteFiles(out_, implicit_deps); | |
| } | |
| // Append data dependencies as order-only dependencies. | |
| // | |
| // This will include data dependencies and input dependencies (like when | |
| // this target depends on an action). Having the data dependencies in this | |
| // list ensures that the data is available at runtime when the user builds | |
| // this target. | |
| // | |
| // The action dependencies are not strictly necessary in this case. They | |
| // should also have been collected via the input deps phony alias that each | |
| // source file has for an order-only dependency, and since this target depends | |
| // on the sources, there is already an implicit order-only dependency. | |
| // However, it's extra work to separate these out and there's no disadvantage | |
| // to listing them again. | |
| WriteOrderOnlyDependencies(classified_deps.non_linkable_deps); | |
| WriteValidations(); | |
| // End of the link "build" line. | |
| out_ << std::endl; | |
| // The remaining things go in the inner scope of the link line. | |
| if (target_->output_type() == Target::EXECUTABLE || | |
| target_->output_type() == Target::SHARED_LIBRARY || | |
| target_->output_type() == Target::LOADABLE_MODULE) { | |
| out_ << " ldflags ="; | |
| WriteLinkerFlags(out_, tool_, optional_def_file); | |
| out_ << std::endl; | |
| out_ << " libs ="; | |
| WriteLibs(out_, tool_); | |
| out_ << std::endl; | |
| out_ << " frameworks ="; | |
| WriteFrameworks(out_, tool_); | |
| out_ << std::endl; | |
| out_ << " swiftmodules ="; | |
| WriteSwiftModules(out_, tool_, swiftmodules); | |
| out_ << std::endl; | |
| } else if (target_->output_type() == Target::STATIC_LIBRARY) { | |
| out_ << " arflags ="; | |
| RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates, | |
| target_, &ConfigValues::arflags, | |
| GetFlagOptions(), out_); | |
| out_ << std::endl; | |
| } | |
| WriteOutputSubstitutions(); | |
| WriteLibsList("solibs", solibs); | |
| WriteLibsList("rlibs", transitive_rustlibs); | |
| WritePool(out_); | |
| } | |
| void NinjaCBinaryTargetWriter::WriteOutputSubstitutions() { | |
| const std::string output_extension = | |
| SubstitutionWriter::GetLinkerSubstitution(target_, tool_, | |
| &SubstitutionOutputExtension); | |
| out_ << " output_extension ="; | |
| if (!output_extension.empty()) { | |
| out_ << " " << output_extension; | |
| } | |
| out_ << std::endl; | |
| const std::string output_dir = SubstitutionWriter::GetLinkerSubstitution( | |
| target_, tool_, &SubstitutionOutputDir); | |
| out_ << " output_dir ="; | |
| if (!output_dir.empty()) { | |
| out_ << " " << output_dir; | |
| } | |
| out_ << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteLibsList( | |
| const std::string& label, | |
| const std::vector<OutputFile>& libs) { | |
| if (libs.empty()) | |
| return; | |
| out_ << " " << label << " ="; | |
| PathOutput output(path_output_.current_dir(), | |
| settings_->build_settings()->root_path_utf8(), | |
| ESCAPE_NINJA_COMMAND); | |
| output.WriteFiles(out_, libs); | |
| out_ << std::endl; | |
| } | |
| void NinjaCBinaryTargetWriter::WriteOrderOnlyDependencies( | |
| const UniqueVector<const Target*>& non_linkable_deps) { | |
| std::vector<OutputFile> outputs_to_write = | |
| GetOrderOnlyDepsFromNonLinkableDeps(non_linkable_deps); | |
| if (!outputs_to_write.empty()) { | |
| out_ << " ||"; | |
| for (const auto& output : outputs_to_write) { | |
| out_ << " "; | |
| path_output_.WriteFile(out_, output); | |
| } | |
| } | |
| } | |
| bool NinjaCBinaryTargetWriter::CheckForDuplicateObjectFiles( | |
| const std::vector<OutputFile>& files) const { | |
| std::set<std::string_view> set; | |
| for (const auto& file : files) { | |
| if (!set.insert(file.value()).second) { | |
| Err err( | |
| target_->defined_from(), "Duplicate object file", | |
| "The target " + target_->label().GetUserVisibleName(false) + | |
| "\ngenerates two object files with the same name:\n " + | |
| std::string(file.value()) + | |
| "\n" | |
| "\n" | |
| "It could be you accidentally have a file listed twice in the\n" | |
| "sources. Or, depending on how your toolchain maps sources to\n" | |
| "object files, two source files with the same name in different\n" | |
| "directories could map to the same object file.\n" | |
| "\n" | |
| "In the latter case, either rename one of the files or move one " | |
| "of\n" | |
| "the sources to a separate source_set to avoid them both being " | |
| "in\n" | |
| "the same target."); | |
| g_scheduler->FailWithError(err); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } |