// 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* 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* output) { std::vector 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* 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* 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* 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& sources, std::vector* 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& sources, std::vector* 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& sources, std::vector* 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& 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* 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* 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* 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(); } }