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
18namespace {
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 ".".
24void 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
36const 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
142void 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
172void 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
192void 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
205SourceFile 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
219std::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
238OutputFile 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
251void 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
263void 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
275void 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
287void 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
298void 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
309void 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
320void 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
342std::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
400OutputFile 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
419void 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
431bool 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
471std::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
480OutputFile 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
498void 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
510std::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
526OutputFile 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
543void 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
555std::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