blob: 594b368a5522911faca499e00da771da6c8413eb [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/target.h"
#include <stddef.h>
#include <algorithm>
#include "base/bind.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "tools/gn/config_values_extractors.h"
#include "tools/gn/deps_iterator.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/scheduler.h"
#include "tools/gn/source_file_type.h"
#include "tools/gn/substitution_writer.h"
#include "tools/gn/tool.h"
#include "tools/gn/toolchain.h"
#include "tools/gn/trace.h"
namespace {
typedef std::set<const Config*> ConfigSet;
// Merges the public configs from the given target to the given config list.
void MergePublicConfigsFrom(const Target* from_target,
UniqueVector<LabelConfigPair>* dest) {
const UniqueVector<LabelConfigPair>& pub = from_target->public_configs();
dest->Append(pub.begin(), pub.end());
}
// Like MergePublicConfigsFrom above except does the "all dependent" ones. This
// additionally adds all configs to the all_dependent_configs_ of the dest
// target given in *all_dest.
void MergeAllDependentConfigsFrom(const Target* from_target,
UniqueVector<LabelConfigPair>* dest,
UniqueVector<LabelConfigPair>* all_dest) {
for (const auto& pair : from_target->all_dependent_configs()) {
all_dest->push_back(pair);
dest->push_back(pair);
}
}
Err MakeTestOnlyError(const Target* from, const Target* to) {
return Err(from->defined_from(), "Test-only dependency not allowed.",
from->label().GetUserVisibleName(false) + "\n"
"which is NOT marked testonly can't depend on\n" +
to->label().GetUserVisibleName(false) + "\n"
"which is marked testonly. Only targets with \"testonly = true\"\n"
"can depend on other test-only targets.\n"
"\n"
"Either mark it test-only or don't do this dependency.");
}
Err MakeStaticLibDepsError(const Target* from, const Target* to) {
return Err(from->defined_from(),
"Complete static libraries can't depend on static libraries.",
from->label().GetUserVisibleName(false) +
"\n"
"which is a complete static library can't depend on\n" +
to->label().GetUserVisibleName(false) +
"\n"
"which is a static library.\n"
"\n"
"Use source sets for intermediate targets instead.");
}
// Set check_private_deps to true for the first invocation since a target
// can see all of its dependencies. For recursive invocations this will be set
// to false to follow only public dependency paths.
//
// Pass a pointer to an empty set for the first invocation. This will be used
// to avoid duplicate checking.
//
// Checking of object files is optional because it is much slower. This allows
// us to check targets for normal outputs, and then as a second pass check
// object files (since we know it will be an error otherwise). This allows
// us to avoid computing all object file names in the common case.
bool EnsureFileIsGeneratedByDependency(const Target* target,
const OutputFile& file,
bool check_private_deps,
bool consider_object_files,
std::set<const Target*>* seen_targets) {
if (seen_targets->find(target) != seen_targets->end())
return false; // Already checked this one and it's not found.
seen_targets->insert(target);
// Assume that we have relatively few generated inputs so brute-force
// searching here is OK. If this becomes a bottleneck, consider storing
// computed_outputs as a hash set.
for (const OutputFile& cur : target->computed_outputs()) {
if (file == cur)
return true;
}
// Check binary target intermediate files if requested.
if (consider_object_files && target->IsBinary()) {
std::vector<OutputFile> source_outputs;
for (const SourceFile& source : target->sources()) {
Toolchain::ToolType tool_type;
if (!target->GetOutputFilesForSource(source, &tool_type, &source_outputs))
continue;
if (std::find(source_outputs.begin(), source_outputs.end(), file) !=
source_outputs.end())
return true;
}
}
// Check all public dependencies (don't do data ones since those are
// runtime-only).
for (const auto& pair : target->public_deps()) {
if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
consider_object_files, seen_targets))
return true; // Found a path.
}
// Only check private deps if requested.
if (check_private_deps) {
for (const auto& pair : target->private_deps()) {
if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
consider_object_files,
seen_targets))
return true; // Found a path.
}
}
return false;
}
// check_this indicates if the given target should be matched against the
// patterns. It should be set to false for the first call since assert_no_deps
// shouldn't match the target itself.
//
// visited should point to an empty set, this will be used to prevent
// multiple visits.
//
// *failure_path_str will be filled with a string describing the path of the
// dependency failure, and failure_pattern will indicate the pattern in
// assert_no that matched the target.
//
// Returns true if everything is OK. failure_path_str and failure_pattern_index
// will be unchanged in this case.
bool RecursiveCheckAssertNoDeps(const Target* target,
bool check_this,
const std::vector<LabelPattern>& assert_no,
std::set<const Target*>* visited,
std::string* failure_path_str,
const LabelPattern** failure_pattern) {
static const char kIndentPath[] = " ";
if (visited->find(target) != visited->end())
return true; // Already checked this target.
visited->insert(target);
if (check_this) {
// Check this target against the given list of patterns.
for (const LabelPattern& pattern : assert_no) {
if (pattern.Matches(target->label())) {
// Found a match.
*failure_pattern = &pattern;
*failure_path_str =
kIndentPath + target->label().GetUserVisibleName(false);
return false;
}
}
}
// Recursively check dependencies.
for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
if (pair.ptr->output_type() == Target::EXECUTABLE)
continue;
if (!RecursiveCheckAssertNoDeps(pair.ptr, true, assert_no, visited,
failure_path_str, failure_pattern)) {
// To reconstruct the path, prepend the current target to the error.
std::string prepend_path =
kIndentPath + target->label().GetUserVisibleName(false) + " ->\n";
failure_path_str->insert(0, prepend_path);
return false;
}
}
return true;
}
} // namespace
Target::Target(const Settings* settings, const Label& label)
: Item(settings, label),
output_type_(UNKNOWN),
all_headers_public_(true),
check_includes_(true),
complete_static_lib_(false),
testonly_(false),
toolchain_(nullptr) {
}
Target::~Target() {
}
// static
const char* Target::GetStringForOutputType(OutputType type) {
switch (type) {
case UNKNOWN:
return "Unknown";
case GROUP:
return "Group";
case EXECUTABLE:
return "Executable";
case LOADABLE_MODULE:
return "Loadable module";
case SHARED_LIBRARY:
return "Shared library";
case STATIC_LIBRARY:
return "Static library";
case SOURCE_SET:
return "Source set";
case COPY_FILES:
return "Copy";
case ACTION:
return "Action";
case ACTION_FOREACH:
return "ActionForEach";
default:
return "";
}
}
Target* Target::AsTarget() {
return this;
}
const Target* Target::AsTarget() const {
return this;
}
bool Target::OnResolved(Err* err) {
DCHECK(output_type_ != UNKNOWN);
DCHECK(toolchain_) << "Toolchain should have been set before resolving.";
ScopedTrace trace(TraceItem::TRACE_ON_RESOLVED, label());
trace.SetToolchain(settings()->toolchain_label());
// Copy this target's own dependent and public configs to the list of configs
// applying to it.
configs_.Append(all_dependent_configs_.begin(), all_dependent_configs_.end());
MergePublicConfigsFrom(this, &configs_);
// Copy public configs from all dependencies into the list of configs
// applying to this target (configs_).
PullDependentTargetConfigs();
// Copies public dependencies' public configs to this target's public
// configs. These configs have already been applied to this target by
// PullDependentTargetConfigs above, along with the public configs from
// private deps. This step re-exports them as public configs for targets that
// depend on this one.
for (const auto& dep : public_deps_) {
public_configs_.Append(dep.ptr->public_configs().begin(),
dep.ptr->public_configs().end());
}
// Copy our own libs and lib_dirs to the final set. This will be from our
// target and all of our configs. We do this specially since these must be
// inherited through the dependency tree (other flags don't work this way).
//
// This needs to happen after we pull dependent target configs for the
// public config's libs to be included here. And it needs to happen
// before pulling the dependent target libs so the libs are in the correct
// order (local ones first, then the dependency's).
for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
const ConfigValues& cur = iter.cur();
all_lib_dirs_.append(cur.lib_dirs().begin(), cur.lib_dirs().end());
all_libs_.append(cur.libs().begin(), cur.libs().end());
}
PullDependentTargetLibs();
PullRecursiveHardDeps();
if (!ResolvePrecompiledHeaders(err))
return false;
FillOutputFiles();
if (settings()->build_settings()->check_for_bad_items()) {
if (!CheckVisibility(err))
return false;
if (!CheckTestonly(err))
return false;
if (!CheckNoNestedStaticLibs(err))
return false;
if (!CheckAssertNoDeps(err))
return false;
CheckSourcesGenerated();
}
return true;
}
bool Target::IsBinary() const {
return output_type_ == EXECUTABLE ||
output_type_ == SHARED_LIBRARY ||
output_type_ == LOADABLE_MODULE ||
output_type_ == STATIC_LIBRARY ||
output_type_ == SOURCE_SET;
}
bool Target::IsLinkable() const {
return output_type_ == STATIC_LIBRARY || output_type_ == SHARED_LIBRARY;
}
bool Target::IsFinal() const {
return output_type_ == EXECUTABLE ||
output_type_ == SHARED_LIBRARY ||
output_type_ == LOADABLE_MODULE ||
output_type_ == ACTION ||
output_type_ == ACTION_FOREACH ||
output_type_ == COPY_FILES ||
(output_type_ == STATIC_LIBRARY && complete_static_lib_);
}
DepsIteratorRange Target::GetDeps(DepsIterationType type) const {
if (type == DEPS_LINKED) {
return DepsIteratorRange(DepsIterator(
&public_deps_, &private_deps_, nullptr));
}
// All deps.
return DepsIteratorRange(DepsIterator(
&public_deps_, &private_deps_, &data_deps_));
}
std::string Target::GetComputedOutputName(bool include_prefix) const {
DCHECK(toolchain_)
<< "Toolchain must be specified before getting the computed output name.";
const std::string& name = output_name_.empty() ? label().name()
: output_name_;
std::string result;
if (include_prefix) {
const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
if (tool) {
// Only add the prefix if the name doesn't already have it.
if (!base::StartsWith(name, tool->output_prefix(),
base::CompareCase::SENSITIVE))
result = tool->output_prefix();
}
}
result.append(name);
return result;
}
bool Target::SetToolchain(const Toolchain* toolchain, Err* err) {
DCHECK(!toolchain_);
DCHECK_NE(UNKNOWN, output_type_);
toolchain_ = toolchain;
const Tool* tool = toolchain->GetToolForTargetFinalOutput(this);
if (tool)
return true;
// Tool not specified for this target type.
if (err) {
*err = Err(defined_from(), "This target uses an undefined tool.",
base::StringPrintf(
"The target %s\n"
"of type \"%s\"\n"
"uses toolchain %s\n"
"which doesn't have the tool \"%s\" defined.\n\n"
"Alas, I can not continue.",
label().GetUserVisibleName(false).c_str(),
GetStringForOutputType(output_type_),
label().GetToolchainLabel().GetUserVisibleName(false).c_str(),
Toolchain::ToolTypeToName(
toolchain->GetToolTypeForTargetFinalOutput(this)).c_str()));
}
return false;
}
bool Target::GetOutputFilesForSource(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 = 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 = 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(
this, source, tool->outputs(), outputs);
return !outputs->empty();
}
void Target::PullDependentTargetConfigsFrom(const Target* dep) {
MergeAllDependentConfigsFrom(dep, &configs_, &all_dependent_configs_);
MergePublicConfigsFrom(dep, &configs_);
}
void Target::PullDependentTargetConfigs() {
for (const auto& pair : GetDeps(DEPS_LINKED))
PullDependentTargetConfigsFrom(pair.ptr);
}
void Target::PullDependentTargetLibsFrom(const Target* dep, bool is_public) {
// Direct dependent libraries.
if (dep->output_type() == STATIC_LIBRARY ||
dep->output_type() == SHARED_LIBRARY ||
dep->output_type() == SOURCE_SET)
inherited_libraries_.Append(dep, is_public);
if (dep->output_type() == SHARED_LIBRARY) {
// Shared library dependendencies are inherited across public shared
// library boundaries.
//
// In this case:
// EXE -> INTERMEDIATE_SHLIB --[public]--> FINAL_SHLIB
// The EXE will also link to to FINAL_SHLIB. The public dependeny means
// that the EXE can use the headers in FINAL_SHLIB so the FINAL_SHLIB
// will need to appear on EXE's link line.
//
// However, if the dependency is private:
// EXE -> INTERMEDIATE_SHLIB --[private]--> FINAL_SHLIB
// the dependency will not be propagated because INTERMEDIATE_SHLIB is
// not granting permission to call functiosn from FINAL_SHLIB. If EXE
// wants to use functions (and link to) FINAL_SHLIB, it will need to do
// so explicitly.
//
// Static libraries and source sets aren't inherited across shared
// library boundaries because they will be linked into the shared
// library.
inherited_libraries_.AppendPublicSharedLibraries(
dep->inherited_libraries(), is_public);
} else if (!dep->IsFinal()) {
// The current target isn't linked, so propogate linked deps and
// libraries up the dependency tree.
inherited_libraries_.AppendInherited(dep->inherited_libraries(), is_public);
// Inherited library settings.
all_lib_dirs_.append(dep->all_lib_dirs());
all_libs_.append(dep->all_libs());
}
}
void Target::PullDependentTargetLibs() {
for (const auto& dep : public_deps_)
PullDependentTargetLibsFrom(dep.ptr, true);
for (const auto& dep : private_deps_)
PullDependentTargetLibsFrom(dep.ptr, false);
}
void Target::PullRecursiveHardDeps() {
for (const auto& pair : GetDeps(DEPS_LINKED)) {
// Direct hard dependencies.
if (pair.ptr->hard_dep())
recursive_hard_deps_.insert(pair.ptr);
// Recursive hard dependencies of all dependencies.
recursive_hard_deps_.insert(pair.ptr->recursive_hard_deps().begin(),
pair.ptr->recursive_hard_deps().end());
}
}
void Target::FillOutputFiles() {
const Tool* tool = toolchain_->GetToolForTargetFinalOutput(this);
bool check_tool_outputs = false;
switch (output_type_) {
case GROUP:
case SOURCE_SET:
case COPY_FILES:
case ACTION:
case ACTION_FOREACH: {
// These don't get linked to and use stamps which should be the first
// entry in the outputs. These stamps are named
// "<target_out_dir>/<targetname>.stamp".
dependency_output_file_ = GetTargetOutputDirAsOutputFile(this);
dependency_output_file_.value().append(GetComputedOutputName(true));
dependency_output_file_.value().append(".stamp");
break;
}
case EXECUTABLE:
case LOADABLE_MODULE:
// Executables and loadable modules don't get linked to, but the first
// output is used for dependency management.
CHECK_GE(tool->outputs().list().size(), 1u);
check_tool_outputs = true;
dependency_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->outputs().list()[0]);
break;
case STATIC_LIBRARY:
// Static libraries both have dependencies and linking going off of the
// first output.
CHECK(tool->outputs().list().size() >= 1);
check_tool_outputs = true;
link_output_file_ = dependency_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->outputs().list()[0]);
break;
case SHARED_LIBRARY:
CHECK(tool->outputs().list().size() >= 1);
check_tool_outputs = true;
if (tool->link_output().empty() && tool->depend_output().empty()) {
// Default behavior, use the first output file for both.
link_output_file_ = dependency_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->outputs().list()[0]);
} else {
// Use the tool-specified ones.
if (!tool->link_output().empty()) {
link_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->link_output());
}
if (!tool->depend_output().empty()) {
dependency_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->depend_output());
}
}
if (tool->runtime_link_output().empty()) {
runtime_link_output_file_ = link_output_file_;
} else {
runtime_link_output_file_ =
SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
this, tool, tool->runtime_link_output());
}
break;
case UNKNOWN:
default:
NOTREACHED();
}
// Count all outputs from this tool as something generated by this target.
if (check_tool_outputs) {
SubstitutionWriter::ApplyListToLinkerAsOutputFile(
this, tool, tool->outputs(), &computed_outputs_);
// Output names aren't canonicalized in the same way that source files
// are. For example, the tool outputs often use
// {{some_var}}/{{output_name}} which expands to "./foo", but this won't
// match "foo" which is what we'll compute when converting a SourceFile to
// an OutputFile.
for (auto& out : computed_outputs_)
NormalizePath(&out.value());
}
// Also count anything the target has declared to be an output.
std::vector<SourceFile> outputs_as_sources;
action_values_.GetOutputsAsSourceFiles(this, &outputs_as_sources);
for (const SourceFile& out : outputs_as_sources)
computed_outputs_.push_back(OutputFile(settings()->build_settings(), out));
}
bool Target::ResolvePrecompiledHeaders(Err* err) {
// Precompiled headers are stored on a ConfigValues struct. This way, the
// build can set all the precompiled header settings in a config and apply
// it to many targets. Likewise, the precompiled header values may be
// specified directly on a target.
//
// Unlike other values on configs which are lists that just get concatenated,
// the precompiled header settings are unique values. We allow them to be
// specified anywhere, but if they are specified in more than one place all
// places must match.
// Track where the current settings came from for issuing errors.
const Label* pch_header_settings_from = NULL;
if (config_values_.has_precompiled_headers())
pch_header_settings_from = &label();
for (ConfigValuesIterator iter(this); !iter.done(); iter.Next()) {
if (!iter.GetCurrentConfig())
continue; // Skip the one on the target itself.
const Config* config = iter.GetCurrentConfig();
const ConfigValues& cur = config->resolved_values();
if (!cur.has_precompiled_headers())
continue; // This one has no precompiled header info, skip.
if (config_values_.has_precompiled_headers()) {
// Already have a precompiled header values, the settings must match.
if (config_values_.precompiled_header() != cur.precompiled_header() ||
config_values_.precompiled_source() != cur.precompiled_source()) {
*err = Err(defined_from(),
"Precompiled header setting conflict.",
"The target " + label().GetUserVisibleName(false) + "\n"
"has conflicting precompiled header settings.\n"
"\n"
"From " + pch_header_settings_from->GetUserVisibleName(false) +
"\n header: " + config_values_.precompiled_header() +
"\n source: " + config_values_.precompiled_source().value() +
"\n\n"
"From " + config->label().GetUserVisibleName(false) +
"\n header: " + cur.precompiled_header() +
"\n source: " + cur.precompiled_source().value());
return false;
}
} else {
// Have settings from a config, apply them to ourselves.
pch_header_settings_from = &config->label();
config_values_.set_precompiled_header(cur.precompiled_header());
config_values_.set_precompiled_source(cur.precompiled_source());
}
}
return true;
}
bool Target::CheckVisibility(Err* err) const {
for (const auto& pair : GetDeps(DEPS_ALL)) {
if (!Visibility::CheckItemVisibility(this, pair.ptr, err))
return false;
}
return true;
}
bool Target::CheckTestonly(Err* err) const {
// If the current target is marked testonly, it can include both testonly
// and non-testonly targets, so there's nothing to check.
if (testonly())
return true;
// Verify no deps have "testonly" set.
for (const auto& pair : GetDeps(DEPS_ALL)) {
if (pair.ptr->testonly()) {
*err = MakeTestOnlyError(this, pair.ptr);
return false;
}
}
return true;
}
bool Target::CheckNoNestedStaticLibs(Err* err) const {
// If the current target is not a complete static library, it can depend on
// static library targets with no problem.
if (!(output_type() == Target::STATIC_LIBRARY && complete_static_lib()))
return true;
// Verify no deps are static libraries.
for (const auto& pair : GetDeps(DEPS_ALL)) {
if (pair.ptr->output_type() == Target::STATIC_LIBRARY) {
*err = MakeStaticLibDepsError(this, pair.ptr);
return false;
}
}
// Verify no inherited libraries are static libraries.
for (const auto& lib : inherited_libraries().GetOrdered()) {
if (lib->output_type() == Target::STATIC_LIBRARY) {
*err = MakeStaticLibDepsError(this, lib);
return false;
}
}
return true;
}
bool Target::CheckAssertNoDeps(Err* err) const {
if (assert_no_deps_.empty())
return true;
std::set<const Target*> visited;
std::string failure_path_str;
const LabelPattern* failure_pattern = nullptr;
if (!RecursiveCheckAssertNoDeps(this, false, assert_no_deps_, &visited,
&failure_path_str, &failure_pattern)) {
*err = Err(defined_from(), "assert_no_deps failed.",
label().GetUserVisibleName(false) +
" has an assert_no_deps entry:\n " +
failure_pattern->Describe() +
"\nwhich fails for the dependency path:\n" +
failure_path_str);
return false;
}
return true;
}
void Target::CheckSourcesGenerated() const {
// Checks that any inputs or sources to this target that are in the build
// directory are generated by a target that this one transitively depends on
// in some way. We already guarantee that all generated files are written
// to the build dir.
//
// See Scheduler::AddUnknownGeneratedInput's declaration for more.
for (const SourceFile& file : sources_)
CheckSourceGenerated(file);
for (const SourceFile& file : inputs_)
CheckSourceGenerated(file);
// TODO(agrieve): Check all_libs_ here as well (those that are source files).
// http://crbug.com/571731
}
void Target::CheckSourceGenerated(const SourceFile& source) const {
if (!IsStringInOutputDir(settings()->build_settings()->build_dir(),
source.value()))
return; // Not in output dir, this is OK.
// Tell the scheduler about unknown files. This will be noted for later so
// the list of files written by the GN build itself (often response files)
// can be filtered out of this list.
OutputFile out_file(settings()->build_settings(), source);
std::set<const Target*> seen_targets;
if (!EnsureFileIsGeneratedByDependency(this, out_file, true, false,
&seen_targets)) {
// Check object files (much slower and very rare) only if the "normal"
// output check failed.
seen_targets.clear();
if (!EnsureFileIsGeneratedByDependency(this, out_file, true, true,
&seen_targets))
g_scheduler->AddUnknownGeneratedInput(this, source);
}
}