summaryrefslogtreecommitdiff
path: root/gn/src/gn/substitution_writer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gn/src/gn/substitution_writer.cc')
-rw-r--r--gn/src/gn/substitution_writer.cc587
1 files changed, 587 insertions, 0 deletions
diff --git a/gn/src/gn/substitution_writer.cc b/gn/src/gn/substitution_writer.cc
new file mode 100644
index 00000000000..c9624d75624
--- /dev/null
+++ b/gn/src/gn/substitution_writer.cc
@@ -0,0 +1,587 @@
+// 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 "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 {
+
+// 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[] =
+ 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
+)";
+
+// 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 == &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;
+}
+
+// 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 == &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));
+ }
+}
+
+// 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 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));
+}
+
+// static
+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;
+}
+
+// static
+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);
+}
+
+// static
+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));
+}
+
+// static
+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));
+}
+
+// static
+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));
+}
+
+// static
+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);
+}
+
+// static
+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);
+}
+
+// static
+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);
+}
+
+// static
+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) {
+ // 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 != &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;
+ }
+ }
+}
+
+// static
+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 {
+ NOTREACHED() << "Unsupported substitution for this function: "
+ << type->name;
+ 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 == &SubstitutionLiteral) {
+ 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,
+ const Substitution* type,
+ std::string* result) {
+ if (type == &SubstitutionLabel) {
+ // Only include the toolchain for non-default toolchains.
+ *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;
+}
+
+// static
+std::string SubstitutionWriter::GetTargetSubstitution(
+ const Target* target,
+ const Substitution* 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 == &SubstitutionLiteral) {
+ 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,
+ const Substitution* 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, 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 == &SubstitutionLiteral) {
+ 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,
+ const Substitution* type) {
+ // First try the common tool ones.
+ std::string result;
+ if (GetTargetSubstitution(target, type, &result))
+ return result;
+
+ // Fall-through to the linker-specific ones.
+ if (type == &SubstitutionOutputDir) {
+ // Use the target's value if there is one (it will have no expansion
+ // patterns since it can directly use GN variables to compute whatever
+ // path it wants), or the tool's default (which will contain further
+ // expansions).
+ 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) {
+ // Use the extension provided on the target if specified, 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_set())
+ return tool->default_output_extension();
+ if (target->output_extension().empty())
+ return std::string(); // Explicitly set to no extension.
+ return std::string(".") + target->output_extension();
+ } else if (type == &kRustSubstitutionCrateName) {
+ // Only include the toolchain for non-default toolchains.
+ return target->rust_values().crate_name();
+ } else if (type == &CSubstitutionSwiftModuleName) {
+ return target->swift_values().module_name();
+ } else {
+ NOTREACHED();
+ return std::string();
+ }
+}