Index: tools/gn/substitution_writer.cc |
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc |
index d474381ea289d4f7886dd533261341d45ab43fb6..3a41b380173a62c2b30d5fe0c514eb904cfbffeb 100644 |
--- a/tools/gn/substitution_writer.cc |
+++ b/tools/gn/substitution_writer.cc |
@@ -10,8 +10,75 @@ |
#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 { |
+ |
+// This happens when the output of a substitution looks like |
+// <some_output_dir>/<other_stuff>. and we're computing a file in the output |
+// directory. If <some_output_dir> resolves to the empty string because it |
+// refers to the root build directory, the result will start with a slash which |
+// is wrong. |
+// |
+// There are several possible solutions: |
+// |
+// - Could convert empty directories to a ".". However, this looks weird in the |
+// Ninja file and Ninja doesn't canonicalize this away. |
+// |
+// - Make all substitutions compute SourceFiles and then convert to |
+// OutputFiles. The root_build_dir will never be empty in this case, and the |
+// Rebase function will properly strip the slash away when it is rebased to |
+// be relative to the output directory. However, we never need compiler and |
+// linker outputs as SourceFiles, and we do a lot of these conversions which |
+// requires a lot of unnecessary path rebasing. |
+// |
+// - Detect this as a special case and delete the slash. |
+// |
+// This function implements the special case solution. This problem only arises |
+// in the very specific case where we're appending a literal beginning in a |
+// slash, the result string is empty, and the preceeding pattern identifies |
+// an output directory. |
+// |
+// If we find too many problems with this implementation, it would probably be |
+// cleanest to implement the "round trip through SourceFile" solution for |
+// simplicity and guaranteed correctness, rather than adding even more special |
+// cases. |
+// |
+// This function only needs to be called when computing substitutions as |
+// OutputFiles (which are relative to the build dir) and not round-tripping |
+// through SourceFiles. |
+void AppendLiteralWithPossibleSlashEliding( |
+ const std::vector<SubstitutionPattern::Subrange>& ranges, |
+ size_t literal_index, |
+ std::string* result) { |
+ const std::string& literal = ranges[literal_index].literal; |
+ |
+ if (// When the literal's index is 0 and it begins with a slash the user |
+ // must have wanted it to start with a slash. Likewise, if it's 2 or |
+ // more, it's impossible to have a length > 1 sequence of substitutions |
+ // that both make sense as a path and resolve to the build directory. |
+ literal_index != 1 || |
+ // When the result is nonempty, appending the slash as a separator is |
+ // always OK. |
+ !result->empty() || |
+ // If the literal doesn't begin in a slash, appending directly is fine. |
+ literal.empty() || literal[0] != '/') { |
+ result->append(literal); |
+ return; |
+ } |
+ |
+ // If we get here, we need to collapse the slash. Assert that the first |
+ // substitution should have ended up in the output directory. This should |
+ // have already been checked since linker and compiler outputs (which is |
+ // what this is used for) should always bein the output directory. |
+ DCHECK(SubstitutionIsInOutputDir(ranges[0].type)); |
+ result->append(&literal[1], literal.size() - 1); |
+} |
+ |
+} // namespace |
const char kSourceExpansion_Help[] = |
"How Source Expansion Works\n" |
@@ -118,15 +185,38 @@ const char kSourceExpansion_Help[] = |
" //out/Debug/obj/mydirectory/input2.h\n" |
" //out/Debug/obj/mydirectory/input2.cc\n"; |
-SubstitutionWriter::SubstitutionWriter() { |
-} |
+// 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; |
-SubstitutionWriter::~SubstitutionWriter() { |
+ bool needs_quotes = false; |
+ std::string result; |
+ for (size_t i = 0; i < pattern.ranges().size(); i++) { |
+ const SubstitutionPattern::Subrange range = pattern.ranges()[i]; |
+ 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 Settings* settings, |
const SubstitutionList& list, |
std::vector<SourceFile>* output) { |
for (size_t i = 0; i < list.list().size(); i++) { |
@@ -145,16 +235,16 @@ void SubstitutionWriter::GetListAsSourceFiles( |
} |
} |
+// static |
void SubstitutionWriter::GetListAsOutputFiles( |
const Settings* settings, |
const SubstitutionList& list, |
std::vector<OutputFile>* output) { |
std::vector<SourceFile> output_as_sources; |
- GetListAsSourceFiles(settings, list, &output_as_sources); |
+ GetListAsSourceFiles(list, &output_as_sources); |
for (size_t i = 0; i < output_as_sources.size(); i++) { |
- output->push_back(OutputFile( |
- RebaseSourceAbsolutePath(output_as_sources[i].value(), |
- settings->build_settings()->build_dir()))); |
+ output->push_back(OutputFile(settings->build_settings(), |
+ output_as_sources[i])); |
} |
} |
@@ -201,9 +291,7 @@ OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile( |
<< "The result of the pattern \"" |
<< pattern.AsString() |
<< "\" was not an absolute path beginning in \"//\"."; |
- return OutputFile( |
- RebaseSourceAbsolutePath(result_as_source.value(), |
- settings->build_settings()->build_dir())); |
+ return OutputFile(settings->build_settings(), result_as_source); |
} |
// static |
@@ -298,36 +386,6 @@ void SubstitutionWriter::WriteNinjaVariablesForSource( |
} |
// 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 (size_t i = 0; i < pattern.ranges().size(); i++) { |
- const SubstitutionPattern::Subrange range = pattern.ranges()[i]; |
- 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 |
std::string SubstitutionWriter::GetSourceSubstitution( |
const Settings* settings, |
const SourceFile& source, |
@@ -371,7 +429,9 @@ std::string SubstitutionWriter::GetSourceSubstitution( |
break; |
default: |
- NOTREACHED(); |
+ NOTREACHED() |
+ << "Unsupported substitution for this function: " |
+ << kSubstitutionNames[type]; |
return std::string(); |
} |
@@ -382,3 +442,182 @@ std::string SubstitutionWriter::GetSourceSubstitution( |
return to_rebase; |
return RebaseSourceAbsolutePath(to_rebase, relative_to); |
} |
+ |
+// static |
+OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile( |
+ const Target* target, |
+ const Tool* tool, |
+ const SubstitutionPattern& pattern) { |
+ std::string result_value; |
+ for (size_t i = 0; i < pattern.ranges().size(); i++) { |
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
+ 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 (size_t i = 0; i < list.list().size(); i++) { |
+ output->push_back(ApplyPatternToTargetAsOutputFile( |
+ target, tool, list.list()[i])); |
+ } |
+} |
+ |
+// 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_ROOT_GEN_DIR: |
+ *result = GetToolchainGenDirAsOutputFile(target->settings()).value(); |
+ TrimTrailingSlash(result); |
+ break; |
+ case SUBSTITUTION_ROOT_OUT_DIR: |
+ *result = target->settings()->toolchain_output_subdir().value(); |
+ TrimTrailingSlash(result); |
+ break; |
+ case SUBSTITUTION_TARGET_GEN_DIR: |
+ *result = GetTargetGenDirAsOutputFile(target).value(); |
+ TrimTrailingSlash(result); |
+ break; |
+ case SUBSTITUTION_TARGET_OUT_DIR: |
+ *result = GetTargetOutputDirAsOutputFile(target).value(); |
+ TrimTrailingSlash(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 (size_t i = 0; i < pattern.ranges().size(); i++) { |
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
+ if (subrange.type == SUBSTITUTION_LITERAL) { |
+ AppendLiteralWithPossibleSlashEliding( |
+ pattern.ranges(), i, &result.value()); |
+ } 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 (size_t i = 0; i < list.list().size(); i++) { |
+ output->push_back(ApplyPatternToCompilerAsOutputFile( |
+ target, source, list.list()[i])); |
+ } |
+} |
+ |
+// 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 (size_t i = 0; i < pattern.ranges().size(); i++) { |
+ const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
+ if (subrange.type == SUBSTITUTION_LITERAL) { |
+ AppendLiteralWithPossibleSlashEliding( |
+ pattern.ranges(), i, &result.value()); |
+ } 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 (size_t i = 0; i < list.list().size(); i++) { |
+ output->push_back(ApplyPatternToLinkerAsOutputFile( |
+ target, tool, list.list()[i])); |
+ } |
+} |
+ |
+// 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(); |
+ } |
+} |