|  | // 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[] = | 
|  | 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 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 == 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 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(SourceFile::SWAP_IN, &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 == SUBSTITUTION_LITERAL) { | 
|  | 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<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(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, | 
|  | 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(GetSubBuildDirAsSourceDir( | 
|  | BuildDirContext(settings), source.GetDir(), BuildDirType::GEN)); | 
|  | break; | 
|  |  | 
|  | case SUBSTITUTION_SOURCE_OUT_DIR: | 
|  | to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir( | 
|  | BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ)); | 
|  | break; | 
|  |  | 
|  | case SUBSTITUTION_SOURCE_TARGET_RELATIVE: | 
|  | if (target) { | 
|  | return RebasePath(source.value(), target->label().dir(), | 
|  | settings->build_settings()->root_path_utf8()); | 
|  | } | 
|  | NOTREACHED() | 
|  | << "Cannot use substitution " << kSubstitutionNames[type] | 
|  | << " without target"; | 
|  | return std::string(); | 
|  |  | 
|  | 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( | 
|  | GetBuildDirAsOutputFile(BuildDirContext(target), | 
|  | BuildDirType::GEN).value(), | 
|  | result); | 
|  | break; | 
|  | case SUBSTITUTION_ROOT_OUT_DIR: | 
|  | SetDirOrDotWithNoSlash( | 
|  | target->settings()->toolchain_output_subdir().value(), | 
|  | result); | 
|  | break; | 
|  | case SUBSTITUTION_TARGET_GEN_DIR: | 
|  | SetDirOrDotWithNoSlash( | 
|  | GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(), | 
|  | result); | 
|  | break; | 
|  | case SUBSTITUTION_TARGET_OUT_DIR: | 
|  | SetDirOrDotWithNoSlash( | 
|  | GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(), | 
|  | result); | 
|  | break; | 
|  | case SUBSTITUTION_TARGET_OUTPUT_NAME: | 
|  | *result = target->GetComputedOutputName(); | 
|  | 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, 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_DIR: | 
|  | // 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; | 
|  |  | 
|  | case SUBSTITUTION_OUTPUT_EXTENSION: | 
|  | // 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(); | 
|  |  | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return std::string(); | 
|  | } | 
|  | } |