// 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 "tools/gn/ninja_binary_target_writer.h" #include #include #include "base/strings/string_util.h" #include "tools/gn/config_values_extractors.h" #include "tools/gn/deps_iterator.h" #include "tools/gn/err.h" #include "tools/gn/escape.h" #include "tools/gn/ninja_utils.h" #include "tools/gn/settings.h" #include "tools/gn/string_utils.h" #include "tools/gn/substitution_writer.h" #include "tools/gn/target.h" namespace { // Returns the proper escape options for writing compiler and linker flags. EscapeOptions GetFlagOptions() { EscapeOptions opts; opts.mode = ESCAPE_NINJA_COMMAND; // Some flag strings are actually multiple flags that expect to be just // added to the command line. We assume that quoting is done by the // buildfiles if it wants such things quoted. opts.inhibit_quoting = true; return opts; } struct DefineWriter { DefineWriter() { options.mode = ESCAPE_NINJA_COMMAND; } void operator()(const std::string& s, std::ostream& out) const { out << " -D"; EscapeStringToStream(out, s, options); } EscapeOptions options; }; struct IncludeWriter { explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) { } ~IncludeWriter() { } void operator()(const SourceDir& d, std::ostream& out) const { std::ostringstream path_out; path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH); const std::string& path = path_out.str(); if (path[0] == '"') out << " \"-I" << path.substr(1); else out << " -I" << path; } PathOutput& path_output_; }; } // namespace NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target, std::ostream& out) : NinjaTargetWriter(target, out), tool_(target->toolchain()->GetToolForTargetFinalOutput(target)) { } NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() { } void NinjaBinaryTargetWriter::Run() { WriteCompilerVars(); std::vector obj_files; WriteSources(&obj_files); if (target_->output_type() == Target::SOURCE_SET) WriteSourceSetStamp(obj_files); else WriteLinkerStuff(obj_files); } void NinjaBinaryTargetWriter::WriteCompilerVars() { const SubstitutionBits& subst = target_->toolchain()->substitution_bits(); // Defines. if (subst.used[SUBSTITUTION_DEFINES]) { out_ << kSubstitutionNinjaNames[SUBSTITUTION_DEFINES] << " ="; RecursiveTargetConfigToStream( target_, &ConfigValues::defines, DefineWriter(), out_); out_ << std::endl; } // Include directories. if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) { out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " ="; PathOutput include_path_output( path_output_.current_dir(), settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND); RecursiveTargetConfigToStream( target_, &ConfigValues::include_dirs, IncludeWriter(include_path_output), out_); out_ << std::endl; } // C flags and friends. EscapeOptions flag_escape_options = GetFlagOptions(); #define WRITE_FLAGS(name, subst_enum) \ if (subst.used[subst_enum]) { \ out_ << kSubstitutionNinjaNames[subst_enum] << " ="; \ RecursiveTargetConfigStringsToStream(target_, &ConfigValues::name, \ flag_escape_options, out_); \ out_ << std::endl; \ } WRITE_FLAGS(cflags, SUBSTITUTION_CFLAGS) WRITE_FLAGS(cflags_c, SUBSTITUTION_CFLAGS_C) WRITE_FLAGS(cflags_cc, SUBSTITUTION_CFLAGS_CC) WRITE_FLAGS(cflags_objc, SUBSTITUTION_CFLAGS_OBJC) WRITE_FLAGS(cflags_objcc, SUBSTITUTION_CFLAGS_OBJCC) #undef WRITE_FLAGS WriteSharedVars(subst); } void NinjaBinaryTargetWriter::WriteSources( std::vector* object_files) { object_files->reserve(target_->sources().size()); OutputFile input_dep = WriteInputDepsStampAndGetDep(std::vector()); std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_); std::vector tool_outputs; // Prevent reallocation in loop. for (const auto& source : target_->sources()) { Toolchain::ToolType tool_type = Toolchain::TYPE_NONE; if (!GetOutputFilesForSource(target_, source, &tool_type, &tool_outputs)) continue; // No output for this source. if (tool_type != Toolchain::TYPE_NONE) { out_ << "build"; path_output_.WriteFiles(out_, tool_outputs); out_ << ": " << rule_prefix << Toolchain::ToolTypeToName(tool_type); out_ << " "; path_output_.WriteFile(out_, source); if (!input_dep.value().empty()) { // Write out the input dependencies as 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. out_ << " || "; path_output_.WriteFile(out_, input_dep); } out_ << std::endl; } // It's theoretically possible for a compiler to produce more than one // output, but we'll only link to the first output. object_files->push_back(tool_outputs[0]); } out_ << std::endl; } void NinjaBinaryTargetWriter::WriteLinkerStuff( const std::vector& object_files) { std::vector output_files; SubstitutionWriter::ApplyListToLinkerAsOutputFile( target_, tool_, tool_->outputs(), &output_files); out_ << "build"; path_output_.WriteFiles(out_, output_files); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << Toolchain::ToolTypeToName( target_->toolchain()->GetToolTypeForTargetFinalOutput(target_)); UniqueVector extra_object_files; UniqueVector linkable_deps; UniqueVector non_linkable_deps; GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps); // Object files. for (const auto& obj : object_files) { out_ << " "; path_output_.WriteFile(out_, obj); } for (const auto& obj : extra_object_files) { out_ << " "; path_output_.WriteFile(out_, obj); } std::vector implicit_deps; std::vector solibs; for (const Target* cur : 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->dependency_output_file().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_file()); solibs.push_back(cur->link_output_file()); } else { // Normal case, just link to this target. out_ << " "; path_output_.WriteFile(out_, cur->link_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 stamp 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(non_linkable_deps); // End of the link "build" line. out_ << std::endl; // These go in the inner scope of the link line. WriteLinkerFlags(); WriteLibs(); WriteOutputExtension(); WriteSolibs(solibs); } void NinjaBinaryTargetWriter::WriteLinkerFlags() { out_ << " ldflags ="; // First the ldflags from the target and its config. EscapeOptions flag_options = GetFlagOptions(); RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags, flag_options, out_); // Followed by library search paths that have been recursively pushed // through the dependency tree. const OrderedSet all_lib_dirs = target_->all_lib_dirs(); 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); } } out_ << std::endl; } void NinjaBinaryTargetWriter::WriteLibs() { out_ << " libs ="; // Libraries that have been recursively pushed through the dependency tree. EscapeOptions lib_escape_opts; lib_escape_opts.mode = ESCAPE_NINJA_COMMAND; const OrderedSet all_libs = target_->all_libs(); const std::string framework_ending(".framework"); for (size_t i = 0; i < all_libs.size(); i++) { if (settings_->IsMac() && EndsWith(all_libs[i], framework_ending, false)) { // Special-case libraries ending in ".framework" on Mac. Add the // -framework switch and don't add the extension to the output. out_ << " -framework "; EscapeStringToStream(out_, all_libs[i].substr(0, all_libs[i].size() - framework_ending.size()), lib_escape_opts); } else { out_ << " " << tool_->lib_switch(); EscapeStringToStream(out_, all_libs[i], lib_escape_opts); } } out_ << std::endl; } void NinjaBinaryTargetWriter::WriteOutputExtension() { out_ << " output_extension = "; if (target_->output_extension().empty()) { // Use the default from the tool. out_ << tool_->default_output_extension(); } else { // Use the one specified in the target. Note that the one in the target // does not include the leading dot, so add that. out_ << "." << target_->output_extension(); } out_ << std::endl; } void NinjaBinaryTargetWriter::WriteSolibs( const std::vector& solibs) { if (solibs.empty()) return; out_ << " solibs ="; path_output_.WriteFiles(out_, solibs); out_ << std::endl; } void NinjaBinaryTargetWriter::WriteSourceSetStamp( const std::vector& object_files) { // 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. UniqueVector extra_object_files; UniqueVector linkable_deps; UniqueVector non_linkable_deps; GetDeps(&extra_object_files, &linkable_deps, &non_linkable_deps); // The classifier should never put extra object files in a source set: // any source sets that we depend on should appear in our non-linkable // deps instead. DCHECK(extra_object_files.empty()); std::vector order_only_deps; for (const auto& dep : non_linkable_deps) order_only_deps.push_back(dep->dependency_output_file()); WriteStampForTarget(object_files, order_only_deps); } void NinjaBinaryTargetWriter::GetDeps( UniqueVector* extra_object_files, UniqueVector* linkable_deps, UniqueVector* non_linkable_deps) const { // Normal public/private deps. for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) { ClassifyDependency(pair.ptr, extra_object_files, linkable_deps, non_linkable_deps); } // Inherited libraries. for (const auto& inherited_target : target_->inherited_libraries().GetOrdered()) { ClassifyDependency(inherited_target, extra_object_files, linkable_deps, non_linkable_deps); } // Data deps. for (const auto& data_dep_pair : target_->data_deps()) non_linkable_deps->push_back(data_dep_pair.ptr); } void NinjaBinaryTargetWriter::ClassifyDependency( const Target* dep, UniqueVector* extra_object_files, UniqueVector* linkable_deps, UniqueVector* non_linkable_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 (dep->output_type() == Target::SOURCE_SET) { // Source sets have their object files linked into final targets // (shared libraries, executables, 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) { // Linking in a source set to an executable, shared library, or // complete static library, so copy its object files. std::vector tool_outputs; // Prevent allocation in loop. for (const auto& source : dep->sources()) { Toolchain::ToolType tool_type = Toolchain::TYPE_NONE; if (GetOutputFilesForSource(dep, source, &tool_type, &tool_outputs)) { // Only link the first output if there are more than one. extra_object_files->push_back(tool_outputs[0]); } } } // Add the source set itself as a non-linkable dependency on the current // target. This will make sure that anything the source set's stamp file // 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. non_linkable_deps->push_back(dep); } else if (can_link_libs && dep->IsLinkable()) { linkable_deps->push_back(dep); } else { non_linkable_deps->push_back(dep); } } void NinjaBinaryTargetWriter::WriteOrderOnlyDependencies( const UniqueVector& non_linkable_deps) { if (!non_linkable_deps.empty()) { out_ << " ||"; // Non-linkable targets. for (const auto& non_linkable_dep : non_linkable_deps) { out_ << " "; path_output_.WriteFile(out_, non_linkable_dep->dependency_output_file()); } } } bool NinjaBinaryTargetWriter::GetOutputFilesForSource( const Target* target, const SourceFile& source, Toolchain::ToolType* computed_tool_type, std::vector* outputs) const { outputs->clear(); *computed_tool_type = Toolchain::TYPE_NONE; SourceFileType file_type = GetSourceFileType(source); if (file_type == SOURCE_UNKNOWN) return false; if (file_type == SOURCE_O) { // Object files just get passed to the output and not compiled. outputs->push_back(OutputFile(settings_->build_settings(), source)); return true; } *computed_tool_type = target->toolchain()->GetToolTypeForSourceType(file_type); if (*computed_tool_type == Toolchain::TYPE_NONE) return false; // No tool for this file (it's a header file or something). const Tool* tool = target->toolchain()->GetTool(*computed_tool_type); if (!tool) return false; // Tool does not apply for this toolchain.file. // Figure out what output(s) this compiler produces. SubstitutionWriter::ApplyListToCompilerAsOutputFile( target, source, tool->outputs(), outputs); return !outputs->empty(); }