| // 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(); |
| } |
| } |