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}}";
18
19const char kSourceExpansion_Help[] =
20    "How Source Expansion Works\n"
21    "\n"
22    "  Source expansion is used for the custom script and copy target types\n"
23    "  to map source file names to output file names or arguments.\n"
24    "\n"
25    "  To perform source expansion in the outputs, GN maps every entry in the\n"
26    "  sources to every entry in the outputs list, producing the cross\n"
27    "  product of all combinations, expanding placeholders (see below).\n"
28    "\n"
29    "  Source expansion in the args works similarly, but performing the\n"
30    "  placeholder substitution produces a different set of arguments for\n"
31    "  each invocation of the script.\n"
32    "\n"
33    "  If no placeholders are found, the outputs or args list will be treated\n"
34    "  as a static list of literal file names that do not depend on the\n"
35    "  sources.\n"
36    "\n"
37    "  See \"gn help copy\" and \"gn help custom\" for more on how this is\n"
38    "  applied.\n"
39    "\n"
40    "Placeholders\n"
41    "\n"
42    "  {{source}}\n"
43    "      The name of the source file relative to the root build output\n"
44    "      directory (which is the current directory when running compilers\n"
45    "      and scripts). This will generally be used for specifying inputs\n"
46    "      to a script in the \"args\" variable.\n"
47    "\n"
48    "  {{source_file_part}}\n"
49    "      The file part of the source including the extension. For the\n"
50    "      source \"foo/bar.txt\" the source file part will be \"bar.txt\".\n"
51    "\n"
52    "  {{source_name_part}}\n"
53    "      The filename part of the source file with no directory or\n"
54    "      extension. This will generally be used for specifying a\n"
55    "      transformation from a soruce file to a destination file with the\n"
56    "      same name but different extension. For the source \"foo/bar.txt\"\n"
57    "      the source name part will be \"bar\".\n"
58    "\n"
59    "Examples\n"
60    "\n"
61    "  Non-varying outputs:\n"
62    "    script(\"hardcoded_outputs\") {\n"
63    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
64    "      outputs = [ \"$target_out_dir/output1.dat\",\n"
65    "                  \"$target_out_dir/output2.dat\" ]\n"
66    "    }\n"
67    "  The outputs in this case will be the two literal files given.\n"
68    "\n"
69    "  Varying outputs:\n"
70    "    script(\"varying_outputs\") {\n"
71    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
72    "      outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
73    "                  \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
74    "    }\n"
75    "  Performing source expansion will result in the following output names:\n"
76    "    //out/Debug/obj/mydirectory/input1.h\n"
77    "    //out/Debug/obj/mydirectory/input1.cc\n"
78    "    //out/Debug/obj/mydirectory/input2.h\n"
79    "    //out/Debug/obj/mydirectory/input2.cc\n";
80
81FileTemplate::FileTemplate(const Value& t, Err* err)
82    : has_substitutions_(false) {
83  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
84  ParseInput(t, err);
85}
86
87FileTemplate::FileTemplate(const std::vector<std::string>& t)
88    : has_substitutions_(false) {
89  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
90  for (size_t i = 0; i < t.size(); i++)
91    ParseOneTemplateString(t[i]);
92}
93
94FileTemplate::~FileTemplate() {
95}
96
97// static
98FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
99  const Target::FileList& outputs = target->script_values().outputs();
100  std::vector<std::string> output_template_args;
101  for (size_t i = 0; i < outputs.size(); i++)
102    output_template_args.push_back(outputs[i].value());
103  return FileTemplate(output_template_args);
104}
105
106bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
107  DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
108  return types_required_[type];
109}
110
111void FileTemplate::Apply(const Value& sources,
112                         const ParseNode* origin,
113                         std::vector<Value>* dest,
114                         Err* err) const {
115  if (!sources.VerifyTypeIs(Value::LIST, err))
116    return;
117  dest->reserve(sources.list_value().size() * templates_.container().size());
118
119  // Temporary holding place, allocate outside to re-use- buffer.
120  std::vector<std::string> string_output;
121
122  const std::vector<Value>& sources_list = sources.list_value();
123  for (size_t i = 0; i < sources_list.size(); i++) {
124    if (!sources_list[i].VerifyTypeIs(Value::STRING, err))
125      return;
126
127    ApplyString(sources_list[i].string_value(), &string_output);
128    for (size_t out_i = 0; out_i < string_output.size(); out_i++)
129      dest->push_back(Value(origin, string_output[i]));
130  }
131}
132
133void FileTemplate::ApplyString(const std::string& str,
134                               std::vector<std::string>* output) const {
135  // Compute all substitutions needed so we can just do substitutions below.
136  // We skip the LITERAL one since that varies each time.
137  std::string subst[Subrange::NUM_TYPES];
138  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
139    if (types_required_[i])
140      subst[i] = GetSubstitution(str, static_cast<Subrange::Type>(i));
141  }
142
143  output->resize(templates_.container().size());
144  for (size_t template_i = 0;
145       template_i < templates_.container().size(); template_i++) {
146    const Template& t = templates_[template_i];
147    (*output)[template_i].clear();
148    for (size_t subrange_i = 0; subrange_i < t.container().size();
149         subrange_i++) {
150      if (t[subrange_i].type == Subrange::LITERAL)
151        (*output)[template_i].append(t[subrange_i].literal);
152      else
153        (*output)[template_i].append(subst[t[subrange_i].type]);
154    }
155  }
156}
157
158void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
159  EscapeOptions escape_options;
160  escape_options.mode = ESCAPE_NINJA_SHELL;
161  escape_options.inhibit_quoting = true;
162
163  for (size_t template_i = 0;
164       template_i < templates_.container().size(); template_i++) {
165    out << " ";  // Separate args with spaces.
166
167    const Template& t = templates_[template_i];
168
169    // Escape each subrange into a string. Since we're writing out Ninja
170    // variables, we can't quote the whole thing, so we write in pieces, only
171    // escaping the literals, and then quoting the whole thing at the end if
172    // necessary.
173    bool needs_quoting = false;
174    std::string item_str;
175    for (size_t subrange_i = 0; subrange_i < t.container().size();
176         subrange_i++) {
177      if (t[subrange_i].type == Subrange::LITERAL) {
178        item_str.append(EscapeString(t[subrange_i].literal, escape_options,
179                                     &needs_quoting));
180      } else {
181        // Don't escape this since we need to preserve the $.
182        item_str.append("${");
183        item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
184        item_str.append("}");
185      }
186    }
187
188    if (needs_quoting) {
189      // Need to shell quote the whole string.
190      out << '"' << item_str << '"';
191    } else {
192      out << item_str;
193    }
194  }
195}
196
197void FileTemplate::WriteNinjaVariablesForSubstitution(
198    std::ostream& out,
199    const std::string& source,
200    const EscapeOptions& escape_options) const {
201  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
202    if (types_required_[i]) {
203      Subrange::Type type = static_cast<Subrange::Type>(i);
204      out << "  " << GetNinjaVariableNameForType(type) << " = ";
205      EscapeStringToStream(out, GetSubstitution(source, type), escape_options);
206      out << std::endl;
207    }
208  }
209}
210
211// static
212const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
213  switch (type) {
214    case Subrange::SOURCE:
215      return "source";
216    case Subrange::NAME_PART:
217      return "source_name_part";
218    case Subrange::FILE_PART:
219      return "source_file_part";
220    default:
221      NOTREACHED();
222  }
223  return "";
224}
225
226// static
227std::string FileTemplate::GetSubstitution(const std::string& source,
228                                          Subrange::Type type) {
229  switch (type) {
230    case Subrange::SOURCE:
231      return source;
232    case Subrange::NAME_PART:
233      return FindFilenameNoExtension(&source).as_string();
234    case Subrange::FILE_PART:
235      return FindFilename(&source).as_string();
236    default:
237      NOTREACHED();
238  }
239  return std::string();
240}
241
242void FileTemplate::ParseInput(const Value& value, Err* err) {
243  switch (value.type()) {
244    case Value::STRING:
245      ParseOneTemplateString(value.string_value());
246      break;
247    case Value::LIST:
248      for (size_t i = 0; i < value.list_value().size(); i++) {
249        if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
250          return;
251        ParseOneTemplateString(value.list_value()[i].string_value());
252      }
253      break;
254    default:
255      *err = Err(value, "File template must be a string or list.",
256                 "A sarcastic comment about your skills goes here.");
257  }
258}
259
260void FileTemplate::ParseOneTemplateString(const std::string& str) {
261  templates_.container().resize(templates_.container().size() + 1);
262  Template& t = templates_[templates_.container().size() - 1];
263
264  size_t cur = 0;
265  while (true) {
266    size_t next = str.find("{{", cur);
267
268    // Pick up everything from the previous spot to here as a literal.
269    if (next == std::string::npos) {
270      if (cur != str.size())
271        t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
272      break;
273    } else if (next > cur) {
274      t.container().push_back(
275          Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
276    }
277
278    // Decode the template param.
279    if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) {
280      t.container().push_back(Subrange(Subrange::SOURCE));
281      types_required_[Subrange::SOURCE] = true;
282      has_substitutions_ = true;
283      cur = next + arraysize(kSource) - 1;
284    } else if (str.compare(next, arraysize(kSourceNamePart) - 1,
285                           kSourceNamePart) == 0) {
286      t.container().push_back(Subrange(Subrange::NAME_PART));
287      types_required_[Subrange::NAME_PART] = true;
288      has_substitutions_ = true;
289      cur = next + arraysize(kSourceNamePart) - 1;
290    } else if (str.compare(next, arraysize(kSourceFilePart) - 1,
291                           kSourceFilePart) == 0) {
292      t.container().push_back(Subrange(Subrange::FILE_PART));
293      types_required_[Subrange::FILE_PART] = true;
294      has_substitutions_ = true;
295      cur = next + arraysize(kSourceFilePart) - 1;
296    } else {
297      // If it's not a match, treat it like a one-char literal (this will be
298      // rare, so it's not worth the bother to add to the previous literal) so
299      // we can keep going.
300      t.container().push_back(Subrange(Subrange::LITERAL, "{"));
301      cur = next + 1;
302    }
303  }
304}
305