| // 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(std::string_view dir, std::string* dest) { | |
| if (!dir.empty() && dir.back() == '/') | |
| dest->assign(dir.data(), dir.size() - 1); | |
| else | |
| dest->assign(dir.data(), dir.size()); | |
| 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(GetSourceDir( | |
| BuildDirContext(settings), source.GetDir(), BuildDirType::GEN)); | |
| } else if (type == &SubstitutionSourceOutDir) { | |
| to_rebase = DirectoryWithNoLastSlash(GetSourceDir( | |
| 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 { | |
| // Do not add NOTREACHED() statement here, as Chromium | |
| // nobuild_source_set() templates rely on passing {{cflags}} | |
| // substitution expressions (and other C++ specific ones) | |
| // to action_foreach(). See https://gn.issues.chromium.org/388100196. | |
| 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( | |
| GetBuildDir(BuildDirContext(target), BuildDirType::GEN), result); | |
| } else if (type == &SubstitutionRootOutDir) { | |
| SetDirOrDotWithNoSlash( | |
| target->settings()->toolchain_output_subdir().value(), result); | |
| } else if (type == &SubstitutionTargetGenDir) { | |
| SetDirOrDotWithNoSlash(GetBuildDir(*target, BuildDirType::GEN), result); | |
| } else if (type == &SubstitutionTargetOutDir) { | |
| SetDirOrDotWithNoSlash(GetBuildDir(*target, BuildDirType::OBJ), 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) { | |
| std::string result_path; | |
| for (const auto& subrange : pattern.ranges()) { | |
| if (subrange.type == &SubstitutionLiteral) { | |
| result_path.append(subrange.literal); | |
| } else { | |
| result_path.append( | |
| GetCompilerSubstitution(target, source, subrange.type)); | |
| } | |
| } | |
| return OutputFile(result_path); | |
| } | |
| // 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) { | |
| std::string result_path; | |
| for (const auto& subrange : pattern.ranges()) { | |
| if (subrange.type == &SubstitutionLiteral) { | |
| result_path.append(subrange.literal); | |
| } else { | |
| result_path.append(GetLinkerSubstitution(target, tool, subrange.type)); | |
| } | |
| } | |
| return OutputFile(result_path); | |
| } | |
| // 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 std::string(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(); | |
| } | |
| } |