• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "tools/gn/substitution_writer.h"
6 
7 #include "tools/gn/build_settings.h"
8 #include "tools/gn/escape.h"
9 #include "tools/gn/filesystem_utils.h"
10 #include "tools/gn/output_file.h"
11 #include "tools/gn/settings.h"
12 #include "tools/gn/source_file.h"
13 #include "tools/gn/string_utils.h"
14 #include "tools/gn/substitution_list.h"
15 #include "tools/gn/substitution_pattern.h"
16 #include "tools/gn/target.h"
17 
18 namespace {
19 
20 // Sets the given directory string to the destination, trimming any trailing
21 // slash from the directory (SourceDirs and OutputFiles representing
22 // directories will end in a trailing slash). If the directory is empty,
23 // it will be replaced with a ".".
SetDirOrDotWithNoSlash(const std::string & dir,std::string * dest)24 void SetDirOrDotWithNoSlash(const std::string& dir, std::string* dest) {
25   if (!dir.empty() && dir[dir.size() - 1] == '/')
26     dest->assign(dir.data(), dir.size() - 1);
27   else
28     dest->assign(dir);
29 
30   if (dest->empty())
31     dest->push_back('.');
32 }
33 
34 }  // namespace
35 
36 const char kSourceExpansion_Help[] =
37     "How Source Expansion Works\n"
38     "\n"
39     "  Source expansion is used for the action_foreach and copy target types\n"
40     "  to map source file names to output file names or arguments.\n"
41     "\n"
42     "  To perform source expansion in the outputs, GN maps every entry in the\n"
43     "  sources to every entry in the outputs list, producing the cross\n"
44     "  product of all combinations, expanding placeholders (see below).\n"
45     "\n"
46     "  Source expansion in the args works similarly, but performing the\n"
47     "  placeholder substitution produces a different set of arguments for\n"
48     "  each invocation of the script.\n"
49     "\n"
50     "  If no placeholders are found, the outputs or args list will be treated\n"
51     "  as a static list of literal file names that do not depend on the\n"
52     "  sources.\n"
53     "\n"
54     "  See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
55     "  this is applied.\n"
56     "\n"
57     "Placeholders\n"
58     "\n"
59     "  {{source}}\n"
60     "      The name of the source file including directory (*). This will\n"
61     "      generally be used for specifying inputs to a script in the\n"
62     "      \"args\" variable.\n"
63     "        \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
64     "\n"
65     "  {{source_file_part}}\n"
66     "      The file part of the source including the extension.\n"
67     "        \"//foo/bar/baz.txt\" => \"baz.txt\"\n"
68     "\n"
69     "  {{source_name_part}}\n"
70     "      The filename part of the source file with no directory or\n"
71     "      extension. This will generally be used for specifying a\n"
72     "      transformation from a soruce file to a destination file with the\n"
73     "      same name but different extension.\n"
74     "        \"//foo/bar/baz.txt\" => \"baz\"\n"
75     "\n"
76     "  {{source_dir}}\n"
77     "      The directory (*) containing the source file with no\n"
78     "      trailing slash.\n"
79     "        \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
80     "\n"
81     "  {{source_root_relative_dir}}\n"
82     "      The path to the source file's directory relative to the source\n"
83     "      root, with no leading \"//\" or trailing slashes. If the path is\n"
84     "      system-absolute, (beginning in a single slash) this will just\n"
85     "      return the path with no trailing slash. This value will always\n"
86     "      be the same, regardless of whether it appears in the \"outputs\"\n"
87     "      or \"args\" section.\n"
88     "        \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
89     "\n"
90     "  {{source_gen_dir}}\n"
91     "      The generated file directory (*) corresponding to the source\n"
92     "      file's path. This will be different than the target's generated\n"
93     "      file directory if the source file is in a different directory\n"
94     "      than the BUILD.gn file.\n"
95     "        \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
96     "\n"
97     "  {{source_out_dir}}\n"
98     "      The object file directory (*) corresponding to the source file's\n"
99     "      path, relative to the build directory. this us be different than\n"
100     "      the target's out directory if the source file is in a different\n"
101     "      directory than the build.gn file.\n"
102     "        \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
103     "\n"
104     "(*) Note on directories\n"
105     "\n"
106     "  Paths containing directories (except the source_root_relative_dir)\n"
107     "  will be different depending on what context the expansion is evaluated\n"
108     "  in. Generally it should \"just work\" but it means you can't\n"
109     "  concatenate strings containing these values with reasonable results.\n"
110     "\n"
111     "  Details: source expansions can be used in the \"outputs\" variable,\n"
112     "  the \"args\" variable, and in calls to \"process_file_template\". The\n"
113     "  \"args\" are passed to a script which is run from the build directory,\n"
114     "  so these directories will relative to the build directory for the\n"
115     "  script to find. In the other cases, the directories will be source-\n"
116     "  absolute (begin with a \"//\") because the results of those expansions\n"
117     "  will be handled by GN internally.\n"
118     "\n"
119     "Examples\n"
120     "\n"
121     "  Non-varying outputs:\n"
122     "    action(\"hardcoded_outputs\") {\n"
123     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
124     "      outputs = [ \"$target_out_dir/output1.dat\",\n"
125     "                  \"$target_out_dir/output2.dat\" ]\n"
126     "    }\n"
127     "  The outputs in this case will be the two literal files given.\n"
128     "\n"
129     "  Varying outputs:\n"
130     "    action_foreach(\"varying_outputs\") {\n"
131     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
132     "      outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n"
133     "                  \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n"
134     "    }\n"
135     "  Performing source expansion will result in the following output names:\n"
136     "    //out/Debug/obj/mydirectory/input1.h\n"
137     "    //out/Debug/obj/mydirectory/input1.cc\n"
138     "    //out/Debug/obj/mydirectory/input2.h\n"
139     "    //out/Debug/obj/mydirectory/input2.cc\n";
140 
141 // static
WriteWithNinjaVariables(const SubstitutionPattern & pattern,const EscapeOptions & escape_options,std::ostream & out)142 void SubstitutionWriter::WriteWithNinjaVariables(
143     const SubstitutionPattern& pattern,
144     const EscapeOptions& escape_options,
145     std::ostream& out) {
146   // The result needs to be quoted as if it was one string, but the $ for
147   // the inserted Ninja variables can't be escaped. So write to a buffer with
148   // no quoting, and then quote the whole thing if necessary.
149   EscapeOptions no_quoting(escape_options);
150   no_quoting.inhibit_quoting = true;
151 
152   bool needs_quotes = false;
153   std::string result;
154   for (size_t i = 0; i < pattern.ranges().size(); i++) {
155     const SubstitutionPattern::Subrange range = pattern.ranges()[i];
156     if (range.type == SUBSTITUTION_LITERAL) {
157       result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
158     } else {
159       result.append("${");
160       result.append(kSubstitutionNinjaNames[range.type]);
161       result.append("}");
162     }
163   }
164 
165   if (needs_quotes && !escape_options.inhibit_quoting)
166     out << "\"" << result << "\"";
167   else
168     out << result;
169 }
170 
171 // static
GetListAsSourceFiles(const SubstitutionList & list,std::vector<SourceFile> * output)172 void SubstitutionWriter::GetListAsSourceFiles(
173     const SubstitutionList& list,
174     std::vector<SourceFile>* output) {
175   for (size_t i = 0; i < list.list().size(); i++) {
176     const SubstitutionPattern& pattern = list.list()[i];
177     CHECK(pattern.ranges().size() == 1 &&
178           pattern.ranges()[0].type == SUBSTITUTION_LITERAL)
179         << "The substitution patterm \""
180         << pattern.AsString()
181         << "\" was expected to be a literal with no {{substitutions}}.";
182     const std::string& literal = pattern.ranges()[0].literal;
183     CHECK(literal.size() >= 1 && literal[0] == '/')
184         << "The result of the pattern \""
185         << pattern.AsString()
186         << "\" was not an absolute path.";
187     output->push_back(SourceFile(literal));
188   }
189 }
190 
191 // static
GetListAsOutputFiles(const Settings * settings,const SubstitutionList & list,std::vector<OutputFile> * output)192 void SubstitutionWriter::GetListAsOutputFiles(
193     const Settings* settings,
194     const SubstitutionList& list,
195     std::vector<OutputFile>* output) {
196   std::vector<SourceFile> output_as_sources;
197   GetListAsSourceFiles(list, &output_as_sources);
198   for (size_t i = 0; i < output_as_sources.size(); i++) {
199     output->push_back(OutputFile(settings->build_settings(),
200                                  output_as_sources[i]));
201   }
202 }
203 
204 // static
ApplyPatternToSource(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)205 SourceFile SubstitutionWriter::ApplyPatternToSource(
206       const Settings* settings,
207       const SubstitutionPattern& pattern,
208       const SourceFile& source) {
209   std::string result_value = ApplyPatternToSourceAsString(
210       settings, pattern, source);
211   CHECK(!result_value.empty() && result_value[0] == '/')
212       << "The result of the pattern \""
213       << pattern.AsString()
214       << "\" was not a path beginning in \"/\" or \"//\".";
215   return SourceFile(SourceFile::SWAP_IN, &result_value);
216 }
217 
218 // static
ApplyPatternToSourceAsString(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)219 std::string SubstitutionWriter::ApplyPatternToSourceAsString(
220     const Settings* settings,
221     const SubstitutionPattern& pattern,
222     const SourceFile& source) {
223   std::string result_value;
224   for (size_t i = 0; i < pattern.ranges().size(); i++) {
225     const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
226     if (subrange.type == SUBSTITUTION_LITERAL) {
227       result_value.append(subrange.literal);
228     } else {
229       result_value.append(
230           GetSourceSubstitution(settings, source, subrange.type,
231                                 OUTPUT_ABSOLUTE, SourceDir()));
232     }
233   }
234   return result_value;
235 }
236 
237 // static
ApplyPatternToSourceAsOutputFile(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)238 OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
239     const Settings* settings,
240     const SubstitutionPattern& pattern,
241     const SourceFile& source) {
242   SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source);
243   CHECK(result_as_source.is_source_absolute())
244       << "The result of the pattern \""
245       << pattern.AsString()
246       << "\" was not an absolute path beginning in \"//\".";
247   return OutputFile(settings->build_settings(), result_as_source);
248 }
249 
250 // static
ApplyListToSource(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<SourceFile> * output)251 void SubstitutionWriter::ApplyListToSource(
252     const Settings* settings,
253     const SubstitutionList& list,
254     const SourceFile& source,
255     std::vector<SourceFile>* output) {
256   for (size_t i = 0; i < list.list().size(); i++) {
257     output->push_back(ApplyPatternToSource(
258         settings, list.list()[i], source));
259   }
260 }
261 
262 // static
ApplyListToSourceAsString(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<std::string> * output)263 void SubstitutionWriter::ApplyListToSourceAsString(
264     const Settings* settings,
265     const SubstitutionList& list,
266     const SourceFile& source,
267     std::vector<std::string>* output) {
268   for (size_t i = 0; i < list.list().size(); i++) {
269     output->push_back(ApplyPatternToSourceAsString(
270         settings, list.list()[i], source));
271   }
272 }
273 
274 // static
ApplyListToSourceAsOutputFile(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<OutputFile> * output)275 void SubstitutionWriter::ApplyListToSourceAsOutputFile(
276     const Settings* settings,
277     const SubstitutionList& list,
278     const SourceFile& source,
279     std::vector<OutputFile>* output) {
280   for (size_t i = 0; i < list.list().size(); i++) {
281     output->push_back(ApplyPatternToSourceAsOutputFile(
282         settings, list.list()[i], source));
283   }
284 }
285 
286 // static
ApplyListToSources(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<SourceFile> * output)287 void SubstitutionWriter::ApplyListToSources(
288     const Settings* settings,
289     const SubstitutionList& list,
290     const std::vector<SourceFile>& sources,
291     std::vector<SourceFile>* output) {
292   output->clear();
293   for (size_t i = 0; i < sources.size(); i++)
294     ApplyListToSource(settings, list, sources[i], output);
295 }
296 
297 // static
ApplyListToSourcesAsString(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<std::string> * output)298 void SubstitutionWriter::ApplyListToSourcesAsString(
299     const Settings* settings,
300     const SubstitutionList& list,
301     const std::vector<SourceFile>& sources,
302     std::vector<std::string>* output) {
303   output->clear();
304   for (size_t i = 0; i < sources.size(); i++)
305     ApplyListToSourceAsString(settings, list, sources[i], output);
306 }
307 
308 // static
ApplyListToSourcesAsOutputFile(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<OutputFile> * output)309 void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
310     const Settings* settings,
311     const SubstitutionList& list,
312     const std::vector<SourceFile>& sources,
313     std::vector<OutputFile>* output) {
314   output->clear();
315   for (size_t i = 0; i < sources.size(); i++)
316     ApplyListToSourceAsOutputFile(settings, list, sources[i], output);
317 }
318 
319 // static
WriteNinjaVariablesForSource(const Settings * settings,const SourceFile & source,const std::vector<SubstitutionType> & types,const EscapeOptions & escape_options,std::ostream & out)320 void SubstitutionWriter::WriteNinjaVariablesForSource(
321     const Settings* settings,
322     const SourceFile& source,
323     const std::vector<SubstitutionType>& types,
324     const EscapeOptions& escape_options,
325     std::ostream& out) {
326   for (size_t i = 0; i < types.size(); i++) {
327     // Don't write SOURCE since that just maps to Ninja's $in variable, which
328     // is implicit in the rule.
329     if (types[i] != SUBSTITUTION_SOURCE) {
330       out << "  " << kSubstitutionNinjaNames[types[i]] << " = ";
331         EscapeStringToStream(
332             out,
333             GetSourceSubstitution(settings, source, types[i], OUTPUT_RELATIVE,
334                                   settings->build_settings()->build_dir()),
335             escape_options);
336       out << std::endl;
337     }
338   }
339 }
340 
341 // static
GetSourceSubstitution(const Settings * settings,const SourceFile & source,SubstitutionType type,OutputStyle output_style,const SourceDir & relative_to)342 std::string SubstitutionWriter::GetSourceSubstitution(
343     const Settings* settings,
344     const SourceFile& source,
345     SubstitutionType type,
346     OutputStyle output_style,
347     const SourceDir& relative_to) {
348   std::string to_rebase;
349   switch (type) {
350     case SUBSTITUTION_SOURCE:
351       if (source.is_system_absolute())
352         return source.value();
353       to_rebase = source.value();
354       break;
355 
356     case SUBSTITUTION_SOURCE_NAME_PART:
357       return FindFilenameNoExtension(&source.value()).as_string();
358 
359     case SUBSTITUTION_SOURCE_FILE_PART:
360       return source.GetName();
361 
362     case SUBSTITUTION_SOURCE_DIR:
363       if (source.is_system_absolute())
364         return DirectoryWithNoLastSlash(source.GetDir());
365       to_rebase = DirectoryWithNoLastSlash(source.GetDir());
366       break;
367 
368     case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
369       if (source.is_system_absolute())
370         return DirectoryWithNoLastSlash(source.GetDir());
371       return RebaseSourceAbsolutePath(
372           DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
373 
374     case SUBSTITUTION_SOURCE_GEN_DIR:
375       to_rebase = DirectoryWithNoLastSlash(
376           GetGenDirForSourceDir(settings, source.GetDir()));
377       break;
378 
379     case SUBSTITUTION_SOURCE_OUT_DIR:
380       to_rebase = DirectoryWithNoLastSlash(
381           GetOutputDirForSourceDir(settings, source.GetDir()));
382       break;
383 
384     default:
385       NOTREACHED()
386           << "Unsupported substitution for this function: "
387           << kSubstitutionNames[type];
388       return std::string();
389   }
390 
391   // If we get here, the result is a path that should be made relative or
392   // absolute according to the output_style. Other cases (just file name or
393   // extension extraction) will have been handled via early return above.
394   if (output_style == OUTPUT_ABSOLUTE)
395     return to_rebase;
396   return RebaseSourceAbsolutePath(to_rebase, relative_to);
397 }
398 
399 // static
ApplyPatternToTargetAsOutputFile(const Target * target,const Tool * tool,const SubstitutionPattern & pattern)400 OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile(
401     const Target* target,
402     const Tool* tool,
403     const SubstitutionPattern& pattern) {
404   std::string result_value;
405   for (size_t i = 0; i < pattern.ranges().size(); i++) {
406     const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
407     if (subrange.type == SUBSTITUTION_LITERAL) {
408       result_value.append(subrange.literal);
409     } else {
410       std::string subst;
411       CHECK(GetTargetSubstitution(target, subrange.type, &subst));
412       result_value.append(subst);
413     }
414   }
415   return OutputFile(result_value);
416 }
417 
418 // static
ApplyListToTargetAsOutputFile(const Target * target,const Tool * tool,const SubstitutionList & list,std::vector<OutputFile> * output)419 void SubstitutionWriter::ApplyListToTargetAsOutputFile(
420     const Target* target,
421     const Tool* tool,
422     const SubstitutionList& list,
423     std::vector<OutputFile>* output) {
424   for (size_t i = 0; i < list.list().size(); i++) {
425     output->push_back(ApplyPatternToTargetAsOutputFile(
426         target, tool, list.list()[i]));
427   }
428 }
429 
430 // static
GetTargetSubstitution(const Target * target,SubstitutionType type,std::string * result)431 bool SubstitutionWriter::GetTargetSubstitution(
432     const Target* target,
433     SubstitutionType type,
434     std::string* result) {
435   switch (type) {
436     case SUBSTITUTION_LABEL:
437       // Only include the toolchain for non-default toolchains.
438       *result = target->label().GetUserVisibleName(
439           !target->settings()->is_default());
440       break;
441     case SUBSTITUTION_ROOT_GEN_DIR:
442       SetDirOrDotWithNoSlash(
443           GetToolchainGenDirAsOutputFile(target->settings()).value(),
444           result);
445       break;
446     case SUBSTITUTION_ROOT_OUT_DIR:
447       SetDirOrDotWithNoSlash(
448           target->settings()->toolchain_output_subdir().value(),
449           result);
450       break;
451     case SUBSTITUTION_TARGET_GEN_DIR:
452       SetDirOrDotWithNoSlash(
453           GetTargetGenDirAsOutputFile(target).value(),
454           result);
455       break;
456     case SUBSTITUTION_TARGET_OUT_DIR:
457       SetDirOrDotWithNoSlash(
458           GetTargetOutputDirAsOutputFile(target).value(),
459           result);
460       break;
461     case SUBSTITUTION_TARGET_OUTPUT_NAME:
462       *result = target->GetComputedOutputName(true);
463       break;
464     default:
465       return false;
466   }
467   return true;
468 }
469 
470 // static
GetTargetSubstitution(const Target * target,SubstitutionType type)471 std::string SubstitutionWriter::GetTargetSubstitution(
472     const Target* target,
473     SubstitutionType type) {
474   std::string result;
475   GetTargetSubstitution(target, type, &result);
476   return result;
477 }
478 
479 // static
ApplyPatternToCompilerAsOutputFile(const Target * target,const SourceFile & source,const SubstitutionPattern & pattern)480 OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
481     const Target* target,
482     const SourceFile& source,
483     const SubstitutionPattern& pattern) {
484   OutputFile result;
485   for (size_t i = 0; i < pattern.ranges().size(); i++) {
486     const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
487     if (subrange.type == SUBSTITUTION_LITERAL) {
488       result.value().append(subrange.literal);
489     } else {
490       result.value().append(
491           GetCompilerSubstitution(target, source, subrange.type));
492     }
493   }
494   return result;
495 }
496 
497 // static
ApplyListToCompilerAsOutputFile(const Target * target,const SourceFile & source,const SubstitutionList & list,std::vector<OutputFile> * output)498 void SubstitutionWriter::ApplyListToCompilerAsOutputFile(
499     const Target* target,
500     const SourceFile& source,
501     const SubstitutionList& list,
502     std::vector<OutputFile>* output) {
503   for (size_t i = 0; i < list.list().size(); i++) {
504     output->push_back(ApplyPatternToCompilerAsOutputFile(
505         target, source, list.list()[i]));
506   }
507 }
508 
509 // static
GetCompilerSubstitution(const Target * target,const SourceFile & source,SubstitutionType type)510 std::string SubstitutionWriter::GetCompilerSubstitution(
511     const Target* target,
512     const SourceFile& source,
513     SubstitutionType type) {
514   // First try the common tool ones.
515   std::string result;
516   if (GetTargetSubstitution(target, type, &result))
517     return result;
518 
519   // Fall-through to the source ones.
520   return GetSourceSubstitution(
521       target->settings(), source, type, OUTPUT_RELATIVE,
522       target->settings()->build_settings()->build_dir());
523 }
524 
525 // static
ApplyPatternToLinkerAsOutputFile(const Target * target,const Tool * tool,const SubstitutionPattern & pattern)526 OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
527     const Target* target,
528     const Tool* tool,
529     const SubstitutionPattern& pattern) {
530   OutputFile result;
531   for (size_t i = 0; i < pattern.ranges().size(); i++) {
532     const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
533     if (subrange.type == SUBSTITUTION_LITERAL) {
534       result.value().append(subrange.literal);
535     } else {
536       result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
537     }
538   }
539   return result;
540 }
541 
542 // static
ApplyListToLinkerAsOutputFile(const Target * target,const Tool * tool,const SubstitutionList & list,std::vector<OutputFile> * output)543 void SubstitutionWriter::ApplyListToLinkerAsOutputFile(
544     const Target* target,
545     const Tool* tool,
546     const SubstitutionList& list,
547     std::vector<OutputFile>* output) {
548   for (size_t i = 0; i < list.list().size(); i++) {
549     output->push_back(ApplyPatternToLinkerAsOutputFile(
550         target, tool, list.list()[i]));
551   }
552 }
553 
554 // static
GetLinkerSubstitution(const Target * target,const Tool * tool,SubstitutionType type)555 std::string SubstitutionWriter::GetLinkerSubstitution(
556     const Target* target,
557     const Tool* tool,
558     SubstitutionType type) {
559   // First try the common tool ones.
560   std::string result;
561   if (GetTargetSubstitution(target, type, &result))
562     return result;
563 
564   // Fall-through to the linker-specific ones.
565   switch (type) {
566     case SUBSTITUTION_OUTPUT_EXTENSION:
567       // Use the extension provided on the target if nonempty, otherwise
568       // fall back on the default. Note that the target's output extension
569       // does not include the dot but the tool's does.
570       if (target->output_extension().empty())
571         return tool->default_output_extension();
572       return std::string(".") + target->output_extension();
573 
574     default:
575       NOTREACHED();
576       return std::string();
577   }
578 }
579