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