1// Copyright (c) 2013 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/file_template.h"
6
7#include <algorithm>
8#include <iostream>
9
10#include "tools/gn/escape.h"
11#include "tools/gn/filesystem_utils.h"
12#include "tools/gn/string_utils.h"
13#include "tools/gn/target.h"
14
15const char FileTemplate::kSource[] = "{{source}}";
16const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
17const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";
18const char FileTemplate::kSourceDir[] = "{{source_dir}}";
19const char FileTemplate::kRootRelDir[] = "{{source_root_relative_dir}}";
20const char FileTemplate::kSourceGenDir[] = "{{source_gen_dir}}";
21const char FileTemplate::kSourceOutDir[] = "{{source_out_dir}}";
22
23const char kSourceExpansion_Help[] =
24    "How Source Expansion Works\n"
25    "\n"
26    "  Source expansion is used for the action_foreach and copy target types\n"
27    "  to map source file names to output file names or arguments.\n"
28    "\n"
29    "  To perform source expansion in the outputs, GN maps every entry in the\n"
30    "  sources to every entry in the outputs list, producing the cross\n"
31    "  product of all combinations, expanding placeholders (see below).\n"
32    "\n"
33    "  Source expansion in the args works similarly, but performing the\n"
34    "  placeholder substitution produces a different set of arguments for\n"
35    "  each invocation of the script.\n"
36    "\n"
37    "  If no placeholders are found, the outputs or args list will be treated\n"
38    "  as a static list of literal file names that do not depend on the\n"
39    "  sources.\n"
40    "\n"
41    "  See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
42    "  this is applied.\n"
43    "\n"
44    "Placeholders\n"
45    "\n"
46    "  {{source}}\n"
47    "      The name of the source file relative to the root build output\n"
48    "      directory (which is the current directory when running compilers\n"
49    "      and scripts). This will generally be used for specifying inputs\n"
50    "      to a script in the \"args\" variable.\n"
51    "        \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
52    "\n"
53    "  {{source_file_part}}\n"
54    "      The file part of the source including the extension.\n"
55    "        \"//foo/bar/baz.txt\" => \"baz.txt\"\n"
56    "\n"
57    "  {{source_name_part}}\n"
58    "      The filename part of the source file with no directory or\n"
59    "      extension. This will generally be used for specifying a\n"
60    "      transformation from a soruce file to a destination file with the\n"
61    "      same name but different extension.\n"
62    "        \"//foo/bar/baz.txt\" => \"baz\"\n"
63    "\n"
64    "  {{source_dir}}\n"
65    "      The directory containing the source file, relative to the build\n"
66    "      directory, with no trailing slash.\n"
67    "        \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
68    "\n"
69    "  {{source_root_relative_dir}}\n"
70    "      The path to the source file's directory relative to the source\n"
71    "      root, with no leading \"//\" or trailing slashes. If the path is\n"
72    "      system-absolute, (beginning in a single slash) this will just\n"
73    "      return the path with no trailing slash.\n"
74    "        \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
75    "\n"
76    "  {{source_gen_dir}}\n"
77    "      The generated file directory corresponding to the source file's\n"
78    "      path, relative to the build directory. This will be different than\n"
79    "      the target's generated file directory if the source file is in a\n"
80    "      different directory than the build.gn file. If the input path is\n"
81    "      system absolute, this will return the root generated file\n"
82    "      directory."
83    "        \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
84    "\n"
85    "  {{source_out_dir}}\n"
86    "      The object file directory corresponding to the source file's\n"
87    "      path, relative to the build directory. this us be different than\n"
88    "      the target's out directory if the source file is in a different\n"
89    "      directory than the build.gn file. if the input path is system\n"
90    "      absolute, this will return the root generated file directory.\n"
91    "        \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
92    "\n"
93    "Examples\n"
94    "\n"
95    "  Non-varying outputs:\n"
96    "    action(\"hardcoded_outputs\") {\n"
97    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
98    "      outputs = [ \"$target_out_dir/output1.dat\",\n"
99    "                  \"$target_out_dir/output2.dat\" ]\n"
100    "    }\n"
101    "  The outputs in this case will be the two literal files given.\n"
102    "\n"
103    "  Varying outputs:\n"
104    "    action_foreach(\"varying_outputs\") {\n"
105    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
106    "      outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
107    "                  \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
108    "    }\n"
109    "  Performing source expansion will result in the following output names:\n"
110    "    //out/Debug/obj/mydirectory/input1.h\n"
111    "    //out/Debug/obj/mydirectory/input1.cc\n"
112    "    //out/Debug/obj/mydirectory/input2.h\n"
113    "    //out/Debug/obj/mydirectory/input2.cc\n";
114
115FileTemplate::FileTemplate(const Settings* settings, const Value& t, Err* err)
116    : settings_(settings),
117      has_substitutions_(false) {
118  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
119  ParseInput(t, err);
120}
121
122FileTemplate::FileTemplate(const Settings* settings,
123                           const std::vector<std::string>& t)
124    : settings_(settings),
125      has_substitutions_(false) {
126  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
127  for (size_t i = 0; i < t.size(); i++)
128    ParseOneTemplateString(t[i]);
129}
130
131FileTemplate::FileTemplate(const Settings* settings,
132                           const std::vector<SourceFile>& t)
133    : settings_(settings),
134      has_substitutions_(false) {
135  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
136  for (size_t i = 0; i < t.size(); i++)
137    ParseOneTemplateString(t[i].value());
138}
139
140FileTemplate::~FileTemplate() {
141}
142
143// static
144FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
145  const Target::FileList& outputs = target->action_values().outputs();
146  std::vector<std::string> output_template_args;
147  for (size_t i = 0; i < outputs.size(); i++)
148    output_template_args.push_back(outputs[i].value());
149  return FileTemplate(target->settings(), output_template_args);
150}
151
152bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
153  DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
154  return types_required_[type];
155}
156
157void FileTemplate::Apply(const SourceFile& source,
158                         std::vector<std::string>* output) const {
159  // Compute all substitutions needed so we can just do substitutions below.
160  // We skip the LITERAL one since that varies each time.
161  std::string subst[Subrange::NUM_TYPES];
162  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
163    if (types_required_[i]) {
164      subst[i] =
165          GetSubstitution(settings_, source, static_cast<Subrange::Type>(i));
166    }
167  }
168
169  size_t first_output_index = output->size();
170  output->resize(output->size() + templates_.container().size());
171  for (size_t template_i = 0;
172       template_i < templates_.container().size(); template_i++) {
173    const Template& t = templates_[template_i];
174    std::string& cur_output = (*output)[first_output_index + template_i];
175    for (size_t subrange_i = 0; subrange_i < t.container().size();
176         subrange_i++) {
177      if (t[subrange_i].type == Subrange::LITERAL)
178        cur_output.append(t[subrange_i].literal);
179      else
180        cur_output.append(subst[t[subrange_i].type]);
181    }
182  }
183}
184
185void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
186  EscapeOptions escape_options;
187  escape_options.mode = ESCAPE_NINJA_COMMAND;
188  escape_options.inhibit_quoting = true;
189
190  for (size_t template_i = 0;
191       template_i < templates_.container().size(); template_i++) {
192    out << " ";  // Separate args with spaces.
193
194    const Template& t = templates_[template_i];
195
196    // Escape each subrange into a string. Since we're writing out Ninja
197    // variables, we can't quote the whole thing, so we write in pieces, only
198    // escaping the literals, and then quoting the whole thing at the end if
199    // necessary.
200    bool needs_quoting = false;
201    std::string item_str;
202    for (size_t subrange_i = 0; subrange_i < t.container().size();
203         subrange_i++) {
204      if (t[subrange_i].type == Subrange::LITERAL) {
205        bool cur_needs_quoting = false;
206        item_str.append(EscapeString(t[subrange_i].literal, escape_options,
207                                     &cur_needs_quoting));
208        needs_quoting |= cur_needs_quoting;
209      } else {
210        // Don't escape this since we need to preserve the $.
211        item_str.append("${");
212        item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
213        item_str.append("}");
214      }
215    }
216
217    if (needs_quoting || item_str.empty()) {
218      // Need to shell quote the whole string. We also need to quote empty
219      // strings or it would be impossible to pass "" as a command-line
220      // argument.
221      out << '"' << item_str << '"';
222    } else {
223      out << item_str;
224    }
225  }
226}
227
228void FileTemplate::WriteNinjaVariablesForSubstitution(
229    std::ostream& out,
230    const Settings* settings,
231    const SourceFile& source,
232    const EscapeOptions& escape_options) const {
233  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
234    if (types_required_[i]) {
235      Subrange::Type type = static_cast<Subrange::Type>(i);
236      out << "  " << GetNinjaVariableNameForType(type) << " = ";
237      EscapeStringToStream(out, GetSubstitution(settings, source, type),
238                           escape_options);
239      out << std::endl;
240    }
241  }
242}
243
244// static
245const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
246  switch (type) {
247    case Subrange::SOURCE:
248      return "source";
249    case Subrange::NAME_PART:
250      return "source_name_part";
251    case Subrange::FILE_PART:
252      return "source_file_part";
253    case Subrange::SOURCE_DIR:
254      return "source_dir";
255    case Subrange::ROOT_RELATIVE_DIR:
256      return "source_root_rel_dir";
257    case Subrange::SOURCE_GEN_DIR:
258      return "source_gen_dir";
259    case Subrange::SOURCE_OUT_DIR:
260      return "source_out_dir";
261
262    default:
263      NOTREACHED();
264  }
265  return "";
266}
267
268// static
269std::string FileTemplate::GetSubstitution(const Settings* settings,
270                                          const SourceFile& source,
271                                          Subrange::Type type) {
272  switch (type) {
273    case Subrange::SOURCE:
274      if (source.is_system_absolute())
275        return source.value();
276      return RebaseSourceAbsolutePath(source.value(),
277                                      settings->build_settings()->build_dir());
278
279    case Subrange::NAME_PART:
280      return FindFilenameNoExtension(&source.value()).as_string();
281
282    case Subrange::FILE_PART:
283      return source.GetName();
284
285    case Subrange::SOURCE_DIR:
286      if (source.is_system_absolute())
287        return DirectoryWithNoLastSlash(source.GetDir());
288      return RebaseSourceAbsolutePath(
289          DirectoryWithNoLastSlash(source.GetDir()),
290          settings->build_settings()->build_dir());
291
292    case Subrange::ROOT_RELATIVE_DIR:
293      if (source.is_system_absolute())
294        return DirectoryWithNoLastSlash(source.GetDir());
295      return RebaseSourceAbsolutePath(
296          DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
297
298    case Subrange::SOURCE_GEN_DIR:
299      return RebaseSourceAbsolutePath(
300          DirectoryWithNoLastSlash(
301              GetGenDirForSourceDir(settings, source.GetDir())),
302          settings->build_settings()->build_dir());
303
304    case Subrange::SOURCE_OUT_DIR:
305      return RebaseSourceAbsolutePath(
306          DirectoryWithNoLastSlash(
307              GetOutputDirForSourceDir(settings, source.GetDir())),
308          settings->build_settings()->build_dir());
309
310    default:
311      NOTREACHED();
312  }
313  return std::string();
314}
315
316void FileTemplate::ParseInput(const Value& value, Err* err) {
317  switch (value.type()) {
318    case Value::STRING:
319      ParseOneTemplateString(value.string_value());
320      break;
321    case Value::LIST:
322      for (size_t i = 0; i < value.list_value().size(); i++) {
323        if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
324          return;
325        ParseOneTemplateString(value.list_value()[i].string_value());
326      }
327      break;
328    default:
329      *err = Err(value, "File template must be a string or list.",
330                 "A sarcastic comment about your skills goes here.");
331  }
332}
333
334void FileTemplate::ParseOneTemplateString(const std::string& str) {
335  templates_.container().resize(templates_.container().size() + 1);
336  Template& t = templates_[templates_.container().size() - 1];
337
338  size_t cur = 0;
339  while (true) {
340    size_t next = str.find("{{", cur);
341
342    // Pick up everything from the previous spot to here as a literal.
343    if (next == std::string::npos) {
344      if (cur != str.size())
345        t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
346      break;
347    } else if (next > cur) {
348      t.container().push_back(
349          Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
350    }
351
352    // Given the name of the string constant and enum for a template parameter,
353    // checks for it and stores it. Writing this as a function requires passing
354    // the entire state of this function as arguments, so this actually ends
355    // up being more clear.
356    #define IF_MATCH_THEN_STORE(const_name, enum_name) \
357        if (str.compare(next, arraysize(const_name) - 1, const_name) == 0) { \
358          t.container().push_back(Subrange(Subrange::enum_name)); \
359          types_required_[Subrange::enum_name] = true; \
360          has_substitutions_ = true; \
361          cur = next + arraysize(const_name) - 1; \
362        }
363
364    // Decode the template param.
365    IF_MATCH_THEN_STORE(kSource, SOURCE)
366    else IF_MATCH_THEN_STORE(kSourceNamePart, NAME_PART)
367    else IF_MATCH_THEN_STORE(kSourceFilePart, FILE_PART)
368    else IF_MATCH_THEN_STORE(kSourceDir, SOURCE_DIR)
369    else IF_MATCH_THEN_STORE(kRootRelDir, ROOT_RELATIVE_DIR)
370    else IF_MATCH_THEN_STORE(kSourceGenDir, SOURCE_GEN_DIR)
371    else IF_MATCH_THEN_STORE(kSourceOutDir, SOURCE_OUT_DIR)
372    else {
373      // If it's not a match, treat it like a one-char literal (this will be
374      // rare, so it's not worth the bother to add to the previous literal) so
375      // we can keep going.
376      t.container().push_back(Subrange(Subrange::LITERAL, "{"));
377      cur = next + 1;
378    }
379
380    #undef IF_MATCH_THEN_STORE
381  }
382}
383