OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "tools/gn/substitution_writer.h" | 5 #include "tools/gn/substitution_writer.h" |
6 | 6 |
7 #include "tools/gn/build_settings.h" | 7 #include "tools/gn/build_settings.h" |
8 #include "tools/gn/escape.h" | 8 #include "tools/gn/escape.h" |
9 #include "tools/gn/filesystem_utils.h" | 9 #include "tools/gn/filesystem_utils.h" |
10 #include "tools/gn/output_file.h" | 10 #include "tools/gn/output_file.h" |
11 #include "tools/gn/settings.h" | 11 #include "tools/gn/settings.h" |
12 #include "tools/gn/source_file.h" | 12 #include "tools/gn/source_file.h" |
| 13 #include "tools/gn/string_utils.h" |
13 #include "tools/gn/substitution_list.h" | 14 #include "tools/gn/substitution_list.h" |
14 #include "tools/gn/substitution_pattern.h" | 15 #include "tools/gn/substitution_pattern.h" |
| 16 #include "tools/gn/target.h" |
| 17 |
| 18 namespace { |
| 19 |
| 20 // This happens when the output of a substitution looks like |
| 21 // <some_output_dir>/<other_stuff>. and we're computing a file in the output |
| 22 // directory. If <some_output_dir> resolves to the empty string because it |
| 23 // refers to the root build directory, the result will start with a slash which |
| 24 // is wrong. |
| 25 // |
| 26 // There are several possible solutions: |
| 27 // |
| 28 // - Could convert empty directories to a ".". However, this looks weird in the |
| 29 // Ninja file and Ninja doesn't canonicalize this away. |
| 30 // |
| 31 // - Make all substitutions compute SourceFiles and then convert to |
| 32 // OutputFiles. The root_build_dir will never be empty in this case, and the |
| 33 // Rebase function will properly strip the slash away when it is rebased to |
| 34 // be relative to the output directory. However, we never need compiler and |
| 35 // linker outputs as SourceFiles, and we do a lot of these conversions which |
| 36 // requires a lot of unnecessary path rebasing. |
| 37 // |
| 38 // - Detect this as a special case and delete the slash. |
| 39 // |
| 40 // This function implements the special case solution. This problem only arises |
| 41 // in the very specific case where we're appending a literal beginning in a |
| 42 // slash, the result string is empty, and the preceeding pattern identifies |
| 43 // an output directory. |
| 44 // |
| 45 // If we find too many problems with this implementation, it would probably be |
| 46 // cleanest to implement the "round trip through SourceFile" solution for |
| 47 // simplicity and guaranteed correctness, rather than adding even more special |
| 48 // cases. |
| 49 // |
| 50 // This function only needs to be called when computing substitutions as |
| 51 // OutputFiles (which are relative to the build dir) and not round-tripping |
| 52 // through SourceFiles. |
| 53 void AppendLiteralWithPossibleSlashEliding( |
| 54 const std::vector<SubstitutionPattern::Subrange>& ranges, |
| 55 size_t literal_index, |
| 56 std::string* result) { |
| 57 const std::string& literal = ranges[literal_index].literal; |
| 58 |
| 59 if (// When the literal's index is 0 and it begins with a slash the user |
| 60 // must have wanted it to start with a slash. Likewise, if it's 2 or |
| 61 // more, it's impossible to have a length > 1 sequence of substitutions |
| 62 // that both make sense as a path and resolve to the build directory. |
| 63 literal_index != 1 || |
| 64 // When the result is nonempty, appending the slash as a separator is |
| 65 // always OK. |
| 66 !result->empty() || |
| 67 // If the literal doesn't begin in a slash, appending directly is fine. |
| 68 literal.empty() || literal[0] != '/') { |
| 69 result->append(literal); |
| 70 return; |
| 71 } |
| 72 |
| 73 // If we get here, we need to collapse the slash. Assert that the first |
| 74 // substitution should have ended up in the output directory. This should |
| 75 // have already been checked since linker and compiler outputs (which is |
| 76 // what this is used for) should always bein the output directory. |
| 77 DCHECK(SubstitutionIsInOutputDir(ranges[0].type)); |
| 78 result->append(&literal[1], literal.size() - 1); |
| 79 } |
| 80 |
| 81 } // namespace |
15 | 82 |
16 const char kSourceExpansion_Help[] = | 83 const char kSourceExpansion_Help[] = |
17 "How Source Expansion Works\n" | 84 "How Source Expansion Works\n" |
18 "\n" | 85 "\n" |
19 " Source expansion is used for the action_foreach and copy target types\n" | 86 " Source expansion is used for the action_foreach and copy target types\n" |
20 " to map source file names to output file names or arguments.\n" | 87 " to map source file names to output file names or arguments.\n" |
21 "\n" | 88 "\n" |
22 " To perform source expansion in the outputs, GN maps every entry in the\n" | 89 " To perform source expansion in the outputs, GN maps every entry in the\n" |
23 " sources to every entry in the outputs list, producing the cross\n" | 90 " sources to every entry in the outputs list, producing the cross\n" |
24 " product of all combinations, expanding placeholders (see below).\n" | 91 " product of all combinations, expanding placeholders (see below).\n" |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" | 178 " sources = [ \"input1.idl\", \"input2.idl\" ]\n" |
112 " outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n" | 179 " outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n" |
113 " \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n" | 180 " \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n" |
114 " }\n" | 181 " }\n" |
115 " Performing source expansion will result in the following output names:\n" | 182 " Performing source expansion will result in the following output names:\n" |
116 " //out/Debug/obj/mydirectory/input1.h\n" | 183 " //out/Debug/obj/mydirectory/input1.h\n" |
117 " //out/Debug/obj/mydirectory/input1.cc\n" | 184 " //out/Debug/obj/mydirectory/input1.cc\n" |
118 " //out/Debug/obj/mydirectory/input2.h\n" | 185 " //out/Debug/obj/mydirectory/input2.h\n" |
119 " //out/Debug/obj/mydirectory/input2.cc\n"; | 186 " //out/Debug/obj/mydirectory/input2.cc\n"; |
120 | 187 |
121 SubstitutionWriter::SubstitutionWriter() { | 188 // static |
122 } | 189 void SubstitutionWriter::WriteWithNinjaVariables( |
| 190 const SubstitutionPattern& pattern, |
| 191 const EscapeOptions& escape_options, |
| 192 std::ostream& out) { |
| 193 // The result needs to be quoted as if it was one string, but the $ for |
| 194 // the inserted Ninja variables can't be escaped. So write to a buffer with |
| 195 // no quoting, and then quote the whole thing if necessary. |
| 196 EscapeOptions no_quoting(escape_options); |
| 197 no_quoting.inhibit_quoting = true; |
123 | 198 |
124 SubstitutionWriter::~SubstitutionWriter() { | 199 bool needs_quotes = false; |
| 200 std::string result; |
| 201 for (size_t i = 0; i < pattern.ranges().size(); i++) { |
| 202 const SubstitutionPattern::Subrange range = pattern.ranges()[i]; |
| 203 if (range.type == SUBSTITUTION_LITERAL) { |
| 204 result.append(EscapeString(range.literal, no_quoting, &needs_quotes)); |
| 205 } else { |
| 206 result.append("${"); |
| 207 result.append(kSubstitutionNinjaNames[range.type]); |
| 208 result.append("}"); |
| 209 } |
| 210 } |
| 211 |
| 212 if (needs_quotes && !escape_options.inhibit_quoting) |
| 213 out << "\"" << result << "\""; |
| 214 else |
| 215 out << result; |
125 } | 216 } |
126 | 217 |
127 // static | 218 // static |
128 void SubstitutionWriter::GetListAsSourceFiles( | 219 void SubstitutionWriter::GetListAsSourceFiles( |
129 const Settings* settings, | |
130 const SubstitutionList& list, | 220 const SubstitutionList& list, |
131 std::vector<SourceFile>* output) { | 221 std::vector<SourceFile>* output) { |
132 for (size_t i = 0; i < list.list().size(); i++) { | 222 for (size_t i = 0; i < list.list().size(); i++) { |
133 const SubstitutionPattern& pattern = list.list()[i]; | 223 const SubstitutionPattern& pattern = list.list()[i]; |
134 CHECK(pattern.ranges().size() == 1 && | 224 CHECK(pattern.ranges().size() == 1 && |
135 pattern.ranges()[0].type == SUBSTITUTION_LITERAL) | 225 pattern.ranges()[0].type == SUBSTITUTION_LITERAL) |
136 << "The substitution patterm \"" | 226 << "The substitution patterm \"" |
137 << pattern.AsString() | 227 << pattern.AsString() |
138 << "\" was expected to be a literal with no {{substitutions}}."; | 228 << "\" was expected to be a literal with no {{substitutions}}."; |
139 const std::string& literal = pattern.ranges()[0].literal; | 229 const std::string& literal = pattern.ranges()[0].literal; |
140 CHECK(literal.size() >= 1 && literal[0] == '/') | 230 CHECK(literal.size() >= 1 && literal[0] == '/') |
141 << "The result of the pattern \"" | 231 << "The result of the pattern \"" |
142 << pattern.AsString() | 232 << pattern.AsString() |
143 << "\" was not an absolute path."; | 233 << "\" was not an absolute path."; |
144 output->push_back(SourceFile(literal)); | 234 output->push_back(SourceFile(literal)); |
145 } | 235 } |
146 } | 236 } |
147 | 237 |
| 238 // static |
148 void SubstitutionWriter::GetListAsOutputFiles( | 239 void SubstitutionWriter::GetListAsOutputFiles( |
149 const Settings* settings, | 240 const Settings* settings, |
150 const SubstitutionList& list, | 241 const SubstitutionList& list, |
151 std::vector<OutputFile>* output) { | 242 std::vector<OutputFile>* output) { |
152 std::vector<SourceFile> output_as_sources; | 243 std::vector<SourceFile> output_as_sources; |
153 GetListAsSourceFiles(settings, list, &output_as_sources); | 244 GetListAsSourceFiles(list, &output_as_sources); |
154 for (size_t i = 0; i < output_as_sources.size(); i++) { | 245 for (size_t i = 0; i < output_as_sources.size(); i++) { |
155 output->push_back(OutputFile( | 246 output->push_back(OutputFile(settings->build_settings(), |
156 RebaseSourceAbsolutePath(output_as_sources[i].value(), | 247 output_as_sources[i])); |
157 settings->build_settings()->build_dir()))); | |
158 } | 248 } |
159 } | 249 } |
160 | 250 |
161 // static | 251 // static |
162 SourceFile SubstitutionWriter::ApplyPatternToSource( | 252 SourceFile SubstitutionWriter::ApplyPatternToSource( |
163 const Settings* settings, | 253 const Settings* settings, |
164 const SubstitutionPattern& pattern, | 254 const SubstitutionPattern& pattern, |
165 const SourceFile& source) { | 255 const SourceFile& source) { |
166 std::string result_value = ApplyPatternToSourceAsString( | 256 std::string result_value = ApplyPatternToSourceAsString( |
167 settings, pattern, source); | 257 settings, pattern, source); |
(...skipping 26 matching lines...) Expand all Loading... |
194 // static | 284 // static |
195 OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile( | 285 OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile( |
196 const Settings* settings, | 286 const Settings* settings, |
197 const SubstitutionPattern& pattern, | 287 const SubstitutionPattern& pattern, |
198 const SourceFile& source) { | 288 const SourceFile& source) { |
199 SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source); | 289 SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source); |
200 CHECK(result_as_source.is_source_absolute()) | 290 CHECK(result_as_source.is_source_absolute()) |
201 << "The result of the pattern \"" | 291 << "The result of the pattern \"" |
202 << pattern.AsString() | 292 << pattern.AsString() |
203 << "\" was not an absolute path beginning in \"//\"."; | 293 << "\" was not an absolute path beginning in \"//\"."; |
204 return OutputFile( | 294 return OutputFile(settings->build_settings(), result_as_source); |
205 RebaseSourceAbsolutePath(result_as_source.value(), | |
206 settings->build_settings()->build_dir())); | |
207 } | 295 } |
208 | 296 |
209 // static | 297 // static |
210 void SubstitutionWriter::ApplyListToSource( | 298 void SubstitutionWriter::ApplyListToSource( |
211 const Settings* settings, | 299 const Settings* settings, |
212 const SubstitutionList& list, | 300 const SubstitutionList& list, |
213 const SourceFile& source, | 301 const SourceFile& source, |
214 std::vector<SourceFile>* output) { | 302 std::vector<SourceFile>* output) { |
215 for (size_t i = 0; i < list.list().size(); i++) { | 303 for (size_t i = 0; i < list.list().size(); i++) { |
216 output->push_back(ApplyPatternToSource( | 304 output->push_back(ApplyPatternToSource( |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
291 out, | 379 out, |
292 GetSourceSubstitution(settings, source, types[i], OUTPUT_RELATIVE, | 380 GetSourceSubstitution(settings, source, types[i], OUTPUT_RELATIVE, |
293 settings->build_settings()->build_dir()), | 381 settings->build_settings()->build_dir()), |
294 escape_options); | 382 escape_options); |
295 out << std::endl; | 383 out << std::endl; |
296 } | 384 } |
297 } | 385 } |
298 } | 386 } |
299 | 387 |
300 // static | 388 // static |
301 void SubstitutionWriter::WriteWithNinjaVariables( | |
302 const SubstitutionPattern& pattern, | |
303 const EscapeOptions& escape_options, | |
304 std::ostream& out) { | |
305 // The result needs to be quoted as if it was one string, but the $ for | |
306 // the inserted Ninja variables can't be escaped. So write to a buffer with | |
307 // no quoting, and then quote the whole thing if necessary. | |
308 EscapeOptions no_quoting(escape_options); | |
309 no_quoting.inhibit_quoting = true; | |
310 | |
311 bool needs_quotes = false; | |
312 std::string result; | |
313 for (size_t i = 0; i < pattern.ranges().size(); i++) { | |
314 const SubstitutionPattern::Subrange range = pattern.ranges()[i]; | |
315 if (range.type == SUBSTITUTION_LITERAL) { | |
316 result.append(EscapeString(range.literal, no_quoting, &needs_quotes)); | |
317 } else { | |
318 result.append("${"); | |
319 result.append(kSubstitutionNinjaNames[range.type]); | |
320 result.append("}"); | |
321 } | |
322 } | |
323 | |
324 if (needs_quotes && !escape_options.inhibit_quoting) | |
325 out << "\"" << result << "\""; | |
326 else | |
327 out << result; | |
328 } | |
329 | |
330 // static | |
331 std::string SubstitutionWriter::GetSourceSubstitution( | 389 std::string SubstitutionWriter::GetSourceSubstitution( |
332 const Settings* settings, | 390 const Settings* settings, |
333 const SourceFile& source, | 391 const SourceFile& source, |
334 SubstitutionType type, | 392 SubstitutionType type, |
335 OutputStyle output_style, | 393 OutputStyle output_style, |
336 const SourceDir& relative_to) { | 394 const SourceDir& relative_to) { |
337 std::string to_rebase; | 395 std::string to_rebase; |
338 switch (type) { | 396 switch (type) { |
339 case SUBSTITUTION_SOURCE: | 397 case SUBSTITUTION_SOURCE: |
340 if (source.is_system_absolute()) | 398 if (source.is_system_absolute()) |
(...skipping 23 matching lines...) Expand all Loading... |
364 to_rebase = DirectoryWithNoLastSlash( | 422 to_rebase = DirectoryWithNoLastSlash( |
365 GetGenDirForSourceDir(settings, source.GetDir())); | 423 GetGenDirForSourceDir(settings, source.GetDir())); |
366 break; | 424 break; |
367 | 425 |
368 case SUBSTITUTION_SOURCE_OUT_DIR: | 426 case SUBSTITUTION_SOURCE_OUT_DIR: |
369 to_rebase = DirectoryWithNoLastSlash( | 427 to_rebase = DirectoryWithNoLastSlash( |
370 GetOutputDirForSourceDir(settings, source.GetDir())); | 428 GetOutputDirForSourceDir(settings, source.GetDir())); |
371 break; | 429 break; |
372 | 430 |
373 default: | 431 default: |
374 NOTREACHED(); | 432 NOTREACHED() |
| 433 << "Unsupported substitution for this function: " |
| 434 << kSubstitutionNames[type]; |
375 return std::string(); | 435 return std::string(); |
376 } | 436 } |
377 | 437 |
378 // If we get here, the result is a path that should be made relative or | 438 // If we get here, the result is a path that should be made relative or |
379 // absolute according to the output_style. Other cases (just file name or | 439 // absolute according to the output_style. Other cases (just file name or |
380 // extension extraction) will have been handled via early return above. | 440 // extension extraction) will have been handled via early return above. |
381 if (output_style == OUTPUT_ABSOLUTE) | 441 if (output_style == OUTPUT_ABSOLUTE) |
382 return to_rebase; | 442 return to_rebase; |
383 return RebaseSourceAbsolutePath(to_rebase, relative_to); | 443 return RebaseSourceAbsolutePath(to_rebase, relative_to); |
384 } | 444 } |
| 445 |
| 446 // static |
| 447 OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile( |
| 448 const Target* target, |
| 449 const Tool* tool, |
| 450 const SubstitutionPattern& pattern) { |
| 451 std::string result_value; |
| 452 for (size_t i = 0; i < pattern.ranges().size(); i++) { |
| 453 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
| 454 if (subrange.type == SUBSTITUTION_LITERAL) { |
| 455 result_value.append(subrange.literal); |
| 456 } else { |
| 457 std::string subst; |
| 458 CHECK(GetTargetSubstitution(target, subrange.type, &subst)); |
| 459 result_value.append(subst); |
| 460 } |
| 461 } |
| 462 return OutputFile(result_value); |
| 463 } |
| 464 |
| 465 // static |
| 466 void SubstitutionWriter::ApplyListToTargetAsOutputFile( |
| 467 const Target* target, |
| 468 const Tool* tool, |
| 469 const SubstitutionList& list, |
| 470 std::vector<OutputFile>* output) { |
| 471 for (size_t i = 0; i < list.list().size(); i++) { |
| 472 output->push_back(ApplyPatternToTargetAsOutputFile( |
| 473 target, tool, list.list()[i])); |
| 474 } |
| 475 } |
| 476 |
| 477 // static |
| 478 bool SubstitutionWriter::GetTargetSubstitution( |
| 479 const Target* target, |
| 480 SubstitutionType type, |
| 481 std::string* result) { |
| 482 switch (type) { |
| 483 case SUBSTITUTION_LABEL: |
| 484 // Only include the toolchain for non-default toolchains. |
| 485 *result = target->label().GetUserVisibleName( |
| 486 !target->settings()->is_default()); |
| 487 break; |
| 488 case SUBSTITUTION_ROOT_GEN_DIR: |
| 489 *result = GetToolchainGenDirAsOutputFile(target->settings()).value(); |
| 490 TrimTrailingSlash(result); |
| 491 break; |
| 492 case SUBSTITUTION_ROOT_OUT_DIR: |
| 493 *result = target->settings()->toolchain_output_subdir().value(); |
| 494 TrimTrailingSlash(result); |
| 495 break; |
| 496 case SUBSTITUTION_TARGET_GEN_DIR: |
| 497 *result = GetTargetGenDirAsOutputFile(target).value(); |
| 498 TrimTrailingSlash(result); |
| 499 break; |
| 500 case SUBSTITUTION_TARGET_OUT_DIR: |
| 501 *result = GetTargetOutputDirAsOutputFile(target).value(); |
| 502 TrimTrailingSlash(result); |
| 503 break; |
| 504 case SUBSTITUTION_TARGET_OUTPUT_NAME: |
| 505 *result = target->GetComputedOutputName(true); |
| 506 break; |
| 507 default: |
| 508 return false; |
| 509 } |
| 510 return true; |
| 511 } |
| 512 |
| 513 // static |
| 514 std::string SubstitutionWriter::GetTargetSubstitution( |
| 515 const Target* target, |
| 516 SubstitutionType type) { |
| 517 std::string result; |
| 518 GetTargetSubstitution(target, type, &result); |
| 519 return result; |
| 520 } |
| 521 |
| 522 // static |
| 523 OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile( |
| 524 const Target* target, |
| 525 const SourceFile& source, |
| 526 const SubstitutionPattern& pattern) { |
| 527 OutputFile result; |
| 528 for (size_t i = 0; i < pattern.ranges().size(); i++) { |
| 529 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
| 530 if (subrange.type == SUBSTITUTION_LITERAL) { |
| 531 AppendLiteralWithPossibleSlashEliding( |
| 532 pattern.ranges(), i, &result.value()); |
| 533 } else { |
| 534 result.value().append( |
| 535 GetCompilerSubstitution(target, source, subrange.type)); |
| 536 } |
| 537 } |
| 538 return result; |
| 539 } |
| 540 |
| 541 // static |
| 542 void SubstitutionWriter::ApplyListToCompilerAsOutputFile( |
| 543 const Target* target, |
| 544 const SourceFile& source, |
| 545 const SubstitutionList& list, |
| 546 std::vector<OutputFile>* output) { |
| 547 for (size_t i = 0; i < list.list().size(); i++) { |
| 548 output->push_back(ApplyPatternToCompilerAsOutputFile( |
| 549 target, source, list.list()[i])); |
| 550 } |
| 551 } |
| 552 |
| 553 // static |
| 554 std::string SubstitutionWriter::GetCompilerSubstitution( |
| 555 const Target* target, |
| 556 const SourceFile& source, |
| 557 SubstitutionType type) { |
| 558 // First try the common tool ones. |
| 559 std::string result; |
| 560 if (GetTargetSubstitution(target, type, &result)) |
| 561 return result; |
| 562 |
| 563 // Fall-through to the source ones. |
| 564 return GetSourceSubstitution( |
| 565 target->settings(), source, type, OUTPUT_RELATIVE, |
| 566 target->settings()->build_settings()->build_dir()); |
| 567 } |
| 568 |
| 569 // static |
| 570 OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile( |
| 571 const Target* target, |
| 572 const Tool* tool, |
| 573 const SubstitutionPattern& pattern) { |
| 574 OutputFile result; |
| 575 for (size_t i = 0; i < pattern.ranges().size(); i++) { |
| 576 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i]; |
| 577 if (subrange.type == SUBSTITUTION_LITERAL) { |
| 578 AppendLiteralWithPossibleSlashEliding( |
| 579 pattern.ranges(), i, &result.value()); |
| 580 } else { |
| 581 result.value().append(GetLinkerSubstitution(target, tool, subrange.type)); |
| 582 } |
| 583 } |
| 584 return result; |
| 585 } |
| 586 |
| 587 // static |
| 588 void SubstitutionWriter::ApplyListToLinkerAsOutputFile( |
| 589 const Target* target, |
| 590 const Tool* tool, |
| 591 const SubstitutionList& list, |
| 592 std::vector<OutputFile>* output) { |
| 593 for (size_t i = 0; i < list.list().size(); i++) { |
| 594 output->push_back(ApplyPatternToLinkerAsOutputFile( |
| 595 target, tool, list.list()[i])); |
| 596 } |
| 597 } |
| 598 |
| 599 // static |
| 600 std::string SubstitutionWriter::GetLinkerSubstitution( |
| 601 const Target* target, |
| 602 const Tool* tool, |
| 603 SubstitutionType type) { |
| 604 // First try the common tool ones. |
| 605 std::string result; |
| 606 if (GetTargetSubstitution(target, type, &result)) |
| 607 return result; |
| 608 |
| 609 // Fall-through to the linker-specific ones. |
| 610 switch (type) { |
| 611 case SUBSTITUTION_OUTPUT_EXTENSION: |
| 612 // Use the extension provided on the target if nonempty, otherwise |
| 613 // fall back on the default. Note that the target's output extension |
| 614 // does not include the dot but the tool's does. |
| 615 if (target->output_extension().empty()) |
| 616 return tool->default_output_extension(); |
| 617 return std::string(".") + target->output_extension(); |
| 618 |
| 619 default: |
| 620 NOTREACHED(); |
| 621 return std::string(); |
| 622 } |
| 623 } |
OLD | NEW |