blob: b341639b5d433964712093f72b0a519f0c14e290 [file] [log] [blame]
// 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 <set>
#include <sstream>
#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 {
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<OutputFile> 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<std::string>(
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(),
ESCAPE_NINJA_COMMAND);
RecursiveTargetConfigToStream<SourceDir>(
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<OutputFile>* object_files) {
object_files->reserve(target_->sources().size());
OutputFile input_dep =
WriteInputDepsStampAndGetDep(std::vector<const Target*>());
std::string rule_prefix = GetNinjaRulePrefixForToolchain(settings_);
std::vector<OutputFile> 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<OutputFile>& object_files) {
std::vector<OutputFile> 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<OutputFile> extra_object_files;
UniqueVector<const Target*> linkable_deps;
UniqueVector<const Target*> 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<OutputFile> implicit_deps;
std::vector<OutputFile> 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<SourceDir> 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(),
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<std::string> 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<OutputFile>& solibs) {
if (solibs.empty())
return;
out_ << " solibs =";
path_output_.WriteFiles(out_, solibs);
out_ << std::endl;
}
void NinjaBinaryTargetWriter::WriteSourceSetStamp(
const std::vector<OutputFile>& 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<OutputFile> extra_object_files;
UniqueVector<const Target*> linkable_deps;
UniqueVector<const Target*> 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<OutputFile> 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<OutputFile>* extra_object_files,
UniqueVector<const Target*>* linkable_deps,
UniqueVector<const Target*>* 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()) {
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<OutputFile>* extra_object_files,
UniqueVector<const Target*>* linkable_deps,
UniqueVector<const Target*>* 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<OutputFile> 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]);
}
}
}
} else if (can_link_libs && dep->IsLinkable()) {
linkable_deps->push_back(dep);
} else {
non_linkable_deps->push_back(dep);
}
}
void NinjaBinaryTargetWriter::WriteOrderOnlyDependencies(
const UniqueVector<const Target*>& non_linkable_deps) {
const std::vector<SourceFile>& data = target_->data();
if (!non_linkable_deps.empty() || !data.empty()) {
out_ << " ||";
// Non-linkable targets.
for (size_t i = 0; i < non_linkable_deps.size(); i++) {
out_ << " ";
path_output_.WriteFile(
out_, non_linkable_deps[i]->dependency_output_file());
}
}
}
bool NinjaBinaryTargetWriter::GetOutputFilesForSource(
const Target* target,
const SourceFile& source,
Toolchain::ToolType* computed_tool_type,
std::vector<OutputFile>* 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();
}