blob: 2af2e186e4be40504698f744a0b252ac35111f26 [file] [log] [blame]
// Copyright 2014 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/substitution_writer.h"
#include "tools/gn/build_settings.h"
#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/output_file.h"
#include "tools/gn/settings.h"
#include "tools/gn/source_file.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/substitution_list.h"
#include "tools/gn/substitution_pattern.h"
#include "tools/gn/target.h"
namespace {
// Sets the given directory string to the destination, trimming any trailing
// slash from the directory (SourceDirs and OutputFiles representing
// directories will end in a trailing slash). If the directory is empty,
// it will be replaced with a ".".
void SetDirOrDotWithNoSlash(const std::string& dir, std::string* dest) {
if (!dir.empty() && dir[dir.size() - 1] == '/')
dest->assign(dir.data(), dir.size() - 1);
else
dest->assign(dir);
if (dest->empty())
dest->push_back('.');
}
} // namespace
const char kSourceExpansion_Help[] =
"How Source Expansion Works\n"
"\n"
" Source expansion is used for the action_foreach and copy target types\n"
" to map source file names to output file names or arguments.\n"
"\n"
" To perform source expansion in the outputs, GN maps every entry in the\n"
" sources to every entry in the outputs list, producing the cross\n"
" product of all combinations, expanding placeholders (see below).\n"
"\n"
" Source expansion in the args works similarly, but performing the\n"
" placeholder substitution produces a different set of arguments for\n"
" each invocation of the script.\n"
"\n"
" If no placeholders are found, the outputs or args list will be treated\n"
" as a static list of literal file names that do not depend on the\n"
" sources.\n"
"\n"
" See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
" this is applied.\n"
"\n"
"Placeholders\n"
"\n"
" {{source}}\n"
" The name of the source file including directory (*). This will\n"
" generally be used for specifying inputs to a script in the\n"
" \"args\" variable.\n"
" \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
"\n"
" {{source_file_part}}\n"
" The file part of the source including the extension.\n"
" \"//foo/bar/baz.txt\" => \"baz.txt\"\n"
"\n"
" {{source_name_part}}\n"
" The filename part of the source file with no directory or\n"
" extension. This will generally be used for specifying a\n"
" transformation from a soruce file to a destination file with the\n"
" same name but different extension.\n"
" \"//foo/bar/baz.txt\" => \"baz\"\n"
"\n"
" {{source_dir}}\n"
" The directory (*) containing the source file with no\n"
" trailing slash.\n"
" \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
"\n"
" {{source_root_relative_dir}}\n"
" The path to the source file's directory relative to the source\n"
" root, with no leading \"//\" or trailing slashes. If the path is\n"
" system-absolute, (beginning in a single slash) this will just\n"
" return the path with no trailing slash. This value will always\n"
" be the same, regardless of whether it appears in the \"outputs\"\n"
" or \"args\" section.\n"
" \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
"\n"
" {{source_gen_dir}}\n"
" The generated file directory (*) corresponding to the source\n"
" file's path. This will be different than the target's generated\n"
" file directory if the source file is in a different directory\n"
" than the BUILD.gn file.\n"
" \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
"\n"
" {{source_out_dir}}\n"
" The object file directory (*) corresponding to the source file's\n"
" path, relative to the build directory. this us be different than\n"
" the target's out directory if the source file is in a different\n"
" directory than the build.gn file.\n"
" \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
"\n"
"(*) Note on directories\n"
"\n"
" Paths containing directories (except the source_root_relative_dir)\n"
" will be different depending on what context the expansion is evaluated\n"
" in. Generally it should \"just work\" but it means you can't\n"
" concatenate strings containing these values with reasonable results.\n"
"\n"
" Details: source expansions can be used in the \"outputs\" variable,\n"
" the \"args\" variable, and in calls to \"process_file_template\". The\n"
" \"args\" are passed to a script which is run from the build directory,\n"
" so these directories will relative to the build directory for the\n"
" script to find. In the other cases, the directories will be source-\n"
" absolute (begin with a \"//\") because the results of those expansions\n"
" will be handled by GN internally.\n"
"\n"
"Examples\n"
"\n"
" Non-varying outputs:\n"
" action(\"hardcoded_outputs\") {\n"
" sources = [ \"input1.idl\", \"input2.idl\" ]\n"
" outputs = [ \"$target_out_dir/output1.dat\",\n"
" \"$target_out_dir/output2.dat\" ]\n"
" }\n"
" The outputs in this case will be the two literal files given.\n"
"\n"
" Varying outputs:\n"
" action_foreach(\"varying_outputs\") {\n"
" sources = [ \"input1.idl\", \"input2.idl\" ]\n"
" outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n"
" \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n"
" }\n"
" Performing source expansion will result in the following output names:\n"
" //out/Debug/obj/mydirectory/input1.h\n"
" //out/Debug/obj/mydirectory/input1.cc\n"
" //out/Debug/obj/mydirectory/input2.h\n"
" //out/Debug/obj/mydirectory/input2.cc\n";
// static
void SubstitutionWriter::WriteWithNinjaVariables(
const SubstitutionPattern& pattern,
const EscapeOptions& escape_options,
std::ostream& out) {
// The result needs to be quoted as if it was one string, but the $ for
// the inserted Ninja variables can't be escaped. So write to a buffer with
// no quoting, and then quote the whole thing if necessary.
EscapeOptions no_quoting(escape_options);
no_quoting.inhibit_quoting = true;
bool needs_quotes = false;
std::string result;
for (const auto& range : pattern.ranges()) {
if (range.type == SUBSTITUTION_LITERAL) {
result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
} else {
result.append("${");
result.append(kSubstitutionNinjaNames[range.type]);
result.append("}");
}
}
if (needs_quotes && !escape_options.inhibit_quoting)
out << "\"" << result << "\"";
else
out << result;
}
// static
void SubstitutionWriter::GetListAsSourceFiles(
const SubstitutionList& list,
std::vector<SourceFile>* output) {
for (const auto& pattern : list.list()) {
CHECK(pattern.ranges().size() == 1 &&
pattern.ranges()[0].type == SUBSTITUTION_LITERAL)
<< "The substitution patterm \""
<< pattern.AsString()
<< "\" was expected to be a literal with no {{substitutions}}.";
const std::string& literal = pattern.ranges()[0].literal;
CHECK(literal.size() >= 1 && literal[0] == '/')
<< "The result of the pattern \""
<< pattern.AsString()
<< "\" was not an absolute path.";
output->push_back(SourceFile(literal));
}
}
// static
void SubstitutionWriter::GetListAsOutputFiles(
const Settings* settings,
const SubstitutionList& list,
std::vector<OutputFile>* output) {
std::vector<SourceFile> output_as_sources;
GetListAsSourceFiles(list, &output_as_sources);
for (const auto& file : output_as_sources)
output->push_back(OutputFile(settings->build_settings(), file));
}
// static
SourceFile SubstitutionWriter::ApplyPatternToSource(
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
std::string result_value = ApplyPatternToSourceAsString(
settings, pattern, source);
CHECK(!result_value.empty() && result_value[0] == '/')
<< "The result of the pattern \""
<< pattern.AsString()
<< "\" was not a path beginning in \"/\" or \"//\".";
return SourceFile(SourceFile::SWAP_IN, &result_value);
}
// static
std::string SubstitutionWriter::ApplyPatternToSourceAsString(
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
std::string result_value;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == SUBSTITUTION_LITERAL) {
result_value.append(subrange.literal);
} else {
result_value.append(
GetSourceSubstitution(settings, source, subrange.type,
OUTPUT_ABSOLUTE, SourceDir()));
}
}
return result_value;
}
// static
OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source);
return OutputFile(settings->build_settings(), result_as_source);
}
// static
void SubstitutionWriter::ApplyListToSource(
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<SourceFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToSource(settings, item, source));
}
// static
void SubstitutionWriter::ApplyListToSourceAsString(
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<std::string>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToSourceAsString(settings, item, source));
}
// static
void SubstitutionWriter::ApplyListToSourceAsOutputFile(
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<OutputFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToSourceAsOutputFile(settings, item, source));
}
// static
void SubstitutionWriter::ApplyListToSources(
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<SourceFile>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSource(settings, list, source, output);
}
// static
void SubstitutionWriter::ApplyListToSourcesAsString(
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<std::string>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSourceAsString(settings, list, source, output);
}
// static
void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<OutputFile>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSourceAsOutputFile(settings, list, source, output);
}
// static
void SubstitutionWriter::WriteNinjaVariablesForSource(
const Settings* settings,
const SourceFile& source,
const std::vector<SubstitutionType>& types,
const EscapeOptions& escape_options,
std::ostream& out) {
for (const auto& type : types) {
// Don't write SOURCE since that just maps to Ninja's $in variable, which
// is implicit in the rule. RESPONSE_FILE_NAME is written separately
// only when writing target rules since it can never be used in any
// other context (like process_file_template).
if (type != SUBSTITUTION_SOURCE && type != SUBSTITUTION_RSP_FILE_NAME) {
out << " " << kSubstitutionNinjaNames[type] << " = ";
EscapeStringToStream(
out,
GetSourceSubstitution(settings, source, type, OUTPUT_RELATIVE,
settings->build_settings()->build_dir()),
escape_options);
out << std::endl;
}
}
}
// static
std::string SubstitutionWriter::GetSourceSubstitution(
const Settings* settings,
const SourceFile& source,
SubstitutionType type,
OutputStyle output_style,
const SourceDir& relative_to) {
std::string to_rebase;
switch (type) {
case SUBSTITUTION_SOURCE:
if (source.is_system_absolute())
return source.value();
to_rebase = source.value();
break;
case SUBSTITUTION_SOURCE_NAME_PART:
return FindFilenameNoExtension(&source.value()).as_string();
case SUBSTITUTION_SOURCE_FILE_PART:
return source.GetName();
case SUBSTITUTION_SOURCE_DIR:
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
to_rebase = DirectoryWithNoLastSlash(source.GetDir());
break;
case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
return RebasePath(
DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"),
settings->build_settings()->root_path_utf8());
case SUBSTITUTION_SOURCE_GEN_DIR:
to_rebase = DirectoryWithNoLastSlash(
GetGenDirForSourceDir(settings, source.GetDir()));
break;
case SUBSTITUTION_SOURCE_OUT_DIR:
to_rebase = DirectoryWithNoLastSlash(
GetOutputDirForSourceDir(settings, source.GetDir()));
break;
default:
NOTREACHED()
<< "Unsupported substitution for this function: "
<< kSubstitutionNames[type];
return std::string();
}
// If we get here, the result is a path that should be made relative or
// absolute according to the output_style. Other cases (just file name or
// extension extraction) will have been handled via early return above.
if (output_style == OUTPUT_ABSOLUTE)
return to_rebase;
return RebasePath(to_rebase, relative_to,
settings->build_settings()->root_path_utf8());
}
// static
OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile(
const Target* target,
const Tool* tool,
const SubstitutionPattern& pattern) {
std::string result_value;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == SUBSTITUTION_LITERAL) {
result_value.append(subrange.literal);
} else {
std::string subst;
CHECK(GetTargetSubstitution(target, subrange.type, &subst));
result_value.append(subst);
}
}
return OutputFile(result_value);
}
// static
void SubstitutionWriter::ApplyListToTargetAsOutputFile(
const Target* target,
const Tool* tool,
const SubstitutionList& list,
std::vector<OutputFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToTargetAsOutputFile(target, tool, item));
}
// static
bool SubstitutionWriter::GetTargetSubstitution(
const Target* target,
SubstitutionType type,
std::string* result) {
switch (type) {
case SUBSTITUTION_LABEL:
// Only include the toolchain for non-default toolchains.
*result = target->label().GetUserVisibleName(
!target->settings()->is_default());
break;
case SUBSTITUTION_LABEL_NAME:
*result = target->label().name();
break;
case SUBSTITUTION_ROOT_GEN_DIR:
SetDirOrDotWithNoSlash(
GetToolchainGenDirAsOutputFile(target->settings()).value(),
result);
break;
case SUBSTITUTION_ROOT_OUT_DIR:
SetDirOrDotWithNoSlash(
target->settings()->toolchain_output_subdir().value(),
result);
break;
case SUBSTITUTION_TARGET_GEN_DIR:
SetDirOrDotWithNoSlash(
GetTargetGenDirAsOutputFile(target).value(),
result);
break;
case SUBSTITUTION_TARGET_OUT_DIR:
SetDirOrDotWithNoSlash(
GetTargetOutputDirAsOutputFile(target).value(),
result);
break;
case SUBSTITUTION_TARGET_OUTPUT_NAME:
*result = target->GetComputedOutputName(true);
break;
default:
return false;
}
return true;
}
// static
std::string SubstitutionWriter::GetTargetSubstitution(
const Target* target,
SubstitutionType type) {
std::string result;
GetTargetSubstitution(target, type, &result);
return result;
}
// static
OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
const Target* target,
const SourceFile& source,
const SubstitutionPattern& pattern) {
OutputFile result;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == SUBSTITUTION_LITERAL) {
result.value().append(subrange.literal);
} else {
result.value().append(
GetCompilerSubstitution(target, source, subrange.type));
}
}
return result;
}
// static
void SubstitutionWriter::ApplyListToCompilerAsOutputFile(
const Target* target,
const SourceFile& source,
const SubstitutionList& list,
std::vector<OutputFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToCompilerAsOutputFile(target, source, item));
}
// static
std::string SubstitutionWriter::GetCompilerSubstitution(
const Target* target,
const SourceFile& source,
SubstitutionType type) {
// First try the common tool ones.
std::string result;
if (GetTargetSubstitution(target, type, &result))
return result;
// Fall-through to the source ones.
return GetSourceSubstitution(
target->settings(), source, type, OUTPUT_RELATIVE,
target->settings()->build_settings()->build_dir());
}
// static
OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
const Target* target,
const Tool* tool,
const SubstitutionPattern& pattern) {
OutputFile result;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == SUBSTITUTION_LITERAL) {
result.value().append(subrange.literal);
} else {
result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
}
}
return result;
}
// static
void SubstitutionWriter::ApplyListToLinkerAsOutputFile(
const Target* target,
const Tool* tool,
const SubstitutionList& list,
std::vector<OutputFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToLinkerAsOutputFile(target, tool, item));
}
// static
std::string SubstitutionWriter::GetLinkerSubstitution(
const Target* target,
const Tool* tool,
SubstitutionType type) {
// First try the common tool ones.
std::string result;
if (GetTargetSubstitution(target, type, &result))
return result;
// Fall-through to the linker-specific ones.
switch (type) {
case SUBSTITUTION_OUTPUT_EXTENSION:
// Use the extension provided on the target if nonempty, otherwise
// fall back on the default. Note that the target's output extension
// does not include the dot but the tool's does.
if (target->output_extension().empty())
return tool->default_output_extension();
return std::string(".") + target->output_extension();
default:
NOTREACHED();
return std::string();
}
}