#include "gn/substitution_writer.h"
#include "gn/build_settings.h"
#include "gn/c_substitution_type.h"
#include "gn/escape.h"
#include "gn/filesystem_utils.h"
#include "gn/output_file.h"
#include "gn/rust_substitution_type.h"
#include "gn/rust_tool.h"
#include "gn/settings.h"
#include "gn/source_file.h"
#include "gn/string_utils.h"
#include "gn/substitution_list.h"
#include "gn/substitution_pattern.h"
#include "gn/target.h"
namespace {
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('.');
}
}
const char kSourceExpansion_Help[] =
R"(How Source Expansion Works
Source expansion is used for the action_foreach and copy target types to map
source file names to output file names or arguments.
To perform source expansion in the outputs, GN maps every entry in the
sources to every entry in the outputs list, producing the cross product of
all combinations, expanding placeholders (see below).
Source expansion in the args works similarly, but performing the placeholder
substitution produces a different set of arguments for each invocation of the
script.
If no placeholders are found, the outputs or args list will be treated as a
static list of literal file names that do not depend on the sources.
See "gn help copy" and "gn help action_foreach" for more on how this is
applied.
Placeholders
This section discusses only placeholders for actions. There are other
placeholders used in the definition of tools. See "gn help tool" for those.
{{source}}
The name of the source file including directory (*). This will generally
be used for specifying inputs to a script in the "args" variable.
"//foo/bar/baz.txt" => "../../foo/bar/baz.txt"
{{source_file_part}}
The file part of the source including the extension.
"//foo/bar/baz.txt" => "baz.txt"
{{source_name_part}}
The filename part of the source file with no directory or extension. This
will generally be used for specifying a transformation from a source file
to a destination file with the same name but different extension.
"//foo/bar/baz.txt" => "baz"
{{source_dir}}
The directory (*) containing the source file with no trailing slash.
"//foo/bar/baz.txt" => "../../foo/bar"
{{source_root_relative_dir}}
The path to the source file's directory relative to the source root, with
no leading "//" or trailing slashes. If the path is system-absolute,
(beginning in a single slash) this will just return the path with no
trailing slash. This value will always be the same, regardless of whether
it appears in the "outputs" or "args" section.
"//foo/bar/baz.txt" => "foo/bar"
{{source_gen_dir}}
The generated file directory (*) corresponding to the source file's path.
This will be different than the target's generated file directory if the
source file is in a different directory than the BUILD.gn file.
"//foo/bar/baz.txt" => "gen/foo/bar"
{{source_out_dir}}
The object file directory (*) corresponding to the source file's path,
relative to the build directory. this us be different than the target's
out directory if the source file is in a different directory than the
build.gn file.
"//foo/bar/baz.txt" => "obj/foo/bar"
{{source_target_relative}}
The path to the source file relative to the target's directory. This will
generally be used for replicating the source directory layout in the
output directory. This can only be used in actions and bundle_data
targets. It is an error to use in process_file_template where there is no
"target".
"//foo/bar/baz.txt" => "baz.txt"
(*) Note on directories
Paths containing directories (except the source_root_relative_dir) will be
different depending on what context the expansion is evaluated in. Generally
it should "just work" but it means you can't concatenate strings containing
these values with reasonable results.
Details: source expansions can be used in the "outputs" variable, the "args"
variable, and in calls to "process_file_template". The "args" are passed to a
script which is run from the build directory, so these directories will
relative to the build directory for the script to find. In the other cases,
the directories will be source- absolute (begin with a "//") because the
results of those expansions will be handled by GN internally.
Examples
Non-varying outputs:
action("hardcoded_outputs") {
sources = [ "input1.idl", "input2.idl" ]
outputs = [ "$target_out_dir/output1.dat",
"$target_out_dir/output2.dat" ]
}
The outputs in this case will be the two literal files given.
Varying outputs:
action_foreach("varying_outputs") {
sources = [ "input1.idl", "input2.idl" ]
outputs = [ "{{source_gen_dir}}/{{source_name_part}}.h",
"{{source_gen_dir}}/{{source_name_part}}.cc" ]
}
Performing source expansion will result in the following output names:
//out/Debug/obj/mydirectory/input1.h
//out/Debug/obj/mydirectory/input1.cc
//out/Debug/obj/mydirectory/input2.h
//out/Debug/obj/mydirectory/input2.cc
)";
void SubstitutionWriter::WriteWithNinjaVariables(
const SubstitutionPattern& pattern,
const EscapeOptions& escape_options,
std::ostream& out) {
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 == &SubstitutionLiteral) {
result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
} else {
result.append("${");
result.append(range.type->ninja_name);
result.append("}");
}
}
if (needs_quotes && !escape_options.inhibit_quoting)
out << "\"" << result << "\"";
else
out << result;
}
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 == &SubstitutionLiteral)
<< "The substitution pattern \"" << 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));
}
}
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));
}
SourceFile SubstitutionWriter::ApplyPatternToSource(
const Target* target,
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
std::string result_value =
ApplyPatternToSourceAsString(target, 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(std::move(result_value));
}
std::string SubstitutionWriter::ApplyPatternToSourceAsString(
const Target* target,
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
std::string result_value;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == &SubstitutionLiteral) {
result_value.append(subrange.literal);
} else {
result_value.append(GetSourceSubstitution(target, settings, source,
subrange.type, OUTPUT_ABSOLUTE,
SourceDir()));
}
}
return result_value;
}
OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
const Target* target,
const Settings* settings,
const SubstitutionPattern& pattern,
const SourceFile& source) {
SourceFile result_as_source =
ApplyPatternToSource(target, settings, pattern, source);
return OutputFile(settings->build_settings(), result_as_source);
}
void SubstitutionWriter::ApplyListToSource(const Target* target,
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<SourceFile>* output) {
for (const auto& item : list.list())
output->push_back(ApplyPatternToSource(target, settings, item, source));
}
void SubstitutionWriter::ApplyListToSourceAsString(
const Target* target,
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<std::string>* output) {
for (const auto& item : list.list())
output->push_back(
ApplyPatternToSourceAsString(target, settings, item, source));
}
void SubstitutionWriter::ApplyListToSourceAsOutputFile(
const Target* target,
const Settings* settings,
const SubstitutionList& list,
const SourceFile& source,
std::vector<OutputFile>* output) {
for (const auto& item : list.list())
output->push_back(
ApplyPatternToSourceAsOutputFile(target, settings, item, source));
}
void SubstitutionWriter::ApplyListToSources(
const Target* target,
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<SourceFile>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSource(target, settings, list, source, output);
}
void SubstitutionWriter::ApplyListToSourcesAsString(
const Target* target,
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<std::string>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSourceAsString(target, settings, list, source, output);
}
void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
const Target* target,
const Settings* settings,
const SubstitutionList& list,
const std::vector<SourceFile>& sources,
std::vector<OutputFile>* output) {
output->clear();
for (const auto& source : sources)
ApplyListToSourceAsOutputFile(target, settings, list, source, output);
}
void SubstitutionWriter::WriteNinjaVariablesForSource(
const Target* target,
const Settings* settings,
const SourceFile& source,
const std::vector<const Substitution*>& types,
const EscapeOptions& escape_options,
std::ostream& out) {
for (const auto& type : types) {
if (type != &SubstitutionSource && type != &SubstitutionRspFileName) {
out << " " << type->ninja_name << " = ";
EscapeStringToStream(
out,
GetSourceSubstitution(target, settings, source, type, OUTPUT_RELATIVE,
settings->build_settings()->build_dir()),
escape_options);
out << std::endl;
}
}
}
std::string SubstitutionWriter::GetSourceSubstitution(
const Target* target,
const Settings* settings,
const SourceFile& source,
const Substitution* type,
OutputStyle output_style,
const SourceDir& relative_to) {
std::string to_rebase;
if (type == &SubstitutionSource) {
if (source.is_system_absolute())
return source.value();
to_rebase = source.value();
} else if (type == &SubstitutionSourceNamePart) {
return std::string(FindFilenameNoExtension(&source.value()));
} else if (type == &SubstitutionSourceFilePart) {
return source.GetName();
} else if (type == &SubstitutionSourceDir) {
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
to_rebase = DirectoryWithNoLastSlash(source.GetDir());
} else if (type == &SubstitutionSourceRootRelativeDir) {
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
return RebasePath(DirectoryWithNoLastSlash(source.GetDir()),
SourceDir("//"),
settings->build_settings()->root_path_utf8());
} else if (type == &SubstitutionSourceGenDir) {
to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
} else if (type == &SubstitutionSourceOutDir) {
to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
} else if (type == &SubstitutionSourceTargetRelative) {
if (target) {
return RebasePath(source.value(), target->label().dir(),
settings->build_settings()->root_path_utf8());
}
NOTREACHED() << "Cannot use substitution " << type->name
<< " without target";
return std::string();
} else if (IsValidRustSubstitution(type)) {
to_rebase = source.value();
} else {
return std::string();
}
if (output_style == OUTPUT_ABSOLUTE)
return to_rebase;
return RebasePath(to_rebase, relative_to,
settings->build_settings()->root_path_utf8());
}
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 == &SubstitutionLiteral) {
result_value.append(subrange.literal);
} else {
std::string subst;
CHECK(GetTargetSubstitution(target, subrange.type, &subst));
result_value.append(subst);
}
}
return OutputFile(result_value);
}
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));
}
bool SubstitutionWriter::GetTargetSubstitution(const Target* target,
const Substitution* type,
std::string* result) {
if (type == &SubstitutionLabel) {
*result =
target->label().GetUserVisibleName(!target->settings()->is_default());
} else if (type == &SubstitutionLabelName) {
*result = target->label().name();
} else if (type == &SubstitutionLabelNoToolchain) {
*result = target->label().GetUserVisibleName(false);
} else if (type == &SubstitutionRootGenDir) {
SetDirOrDotWithNoSlash(
GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
.value(),
result);
} else if (type == &SubstitutionRootOutDir) {
SetDirOrDotWithNoSlash(
target->settings()->toolchain_output_subdir().value(), result);
} else if (type == &SubstitutionTargetGenDir) {
SetDirOrDotWithNoSlash(
GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
result);
} else if (type == &SubstitutionTargetOutDir) {
SetDirOrDotWithNoSlash(
GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
result);
} else if (type == &SubstitutionTargetOutputName) {
*result = target->GetComputedOutputName();
} else {
return false;
}
return true;
}
std::string SubstitutionWriter::GetTargetSubstitution(
const Target* target,
const Substitution* type) {
std::string result;
GetTargetSubstitution(target, type, &result);
return result;
}
OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
const Target* target,
const SourceFile& source,
const SubstitutionPattern& pattern) {
OutputFile result;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == &SubstitutionLiteral) {
result.value().append(subrange.literal);
} else {
result.value().append(
GetCompilerSubstitution(target, source, subrange.type));
}
}
return result;
}
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));
}
std::string SubstitutionWriter::GetCompilerSubstitution(
const Target* target,
const SourceFile& source,
const Substitution* type) {
std::string result;
if (GetTargetSubstitution(target, type, &result))
return result;
return GetSourceSubstitution(
target, target->settings(), source, type, OUTPUT_RELATIVE,
target->settings()->build_settings()->build_dir());
}
OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
const Target* target,
const Tool* tool,
const SubstitutionPattern& pattern) {
OutputFile result;
for (const auto& subrange : pattern.ranges()) {
if (subrange.type == &SubstitutionLiteral) {
result.value().append(subrange.literal);
} else {
result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
}
}
return result;
}
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));
}
std::string SubstitutionWriter::GetLinkerSubstitution(
const Target* target,
const Tool* tool,
const Substitution* type) {
std::string result;
if (GetTargetSubstitution(target, type, &result))
return result;
if (type == &SubstitutionOutputDir) {
if (target->output_dir().is_null()) {
return ApplyPatternToLinkerAsOutputFile(target, tool,
tool->default_output_dir())
.value();
}
SetDirOrDotWithNoSlash(
RebasePath(target->output_dir().value(),
target->settings()->build_settings()->build_dir()),
&result);
return result;
} else if (type == &SubstitutionOutputExtension) {
if (!target->output_extension_set())
return tool->default_output_extension();
if (target->output_extension().empty())
return std::string();
return std::string(".") + target->output_extension();
} else if (type == &kRustSubstitutionCrateName) {
return target->rust_values().crate_name();
} else if (type == &CSubstitutionSwiftModuleName) {
return target->swift_values().module_name();
} else {
NOTREACHED();
return std::string();
}
}