summaryrefslogtreecommitdiffstats
path: root/tools/gn/file_template.cc
blob: b27087bbcda5075cff8786752e5fe859475f7426 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/file_template.h"

#include <algorithm>
#include <iostream>

#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/target.h"

const char FileTemplate::kSource[] = "{{source}}";
const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";

const char kSourceExpansion_Help[] =
    "How Source Expansion Works\n"
    "\n"
    "  Source expansion is used for the action_foreach and copy target types\n"
    "  to map source file names to output file names or arguments.\n"
    "\n"
    "  To perform source expansion in the outputs, GN maps every entry in the\n"
    "  sources to every entry in the outputs list, producing the cross\n"
    "  product of all combinations, expanding placeholders (see below).\n"
    "\n"
    "  Source expansion in the args works similarly, but performing the\n"
    "  placeholder substitution produces a different set of arguments for\n"
    "  each invocation of the script.\n"
    "\n"
    "  If no placeholders are found, the outputs or args list will be treated\n"
    "  as a static list of literal file names that do not depend on the\n"
    "  sources.\n"
    "\n"
    "  See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
    "  this is applied.\n"
    "\n"
    "Placeholders\n"
    "\n"
    "  {{source}}\n"
    "      The name of the source file relative to the root build output\n"
    "      directory (which is the current directory when running compilers\n"
    "      and scripts). This will generally be used for specifying inputs\n"
    "      to a script in the \"args\" variable.\n"
    "\n"
    "  {{source_file_part}}\n"
    "      The file part of the source including the extension. For the\n"
    "      source \"foo/bar.txt\" the source file part will be \"bar.txt\".\n"
    "\n"
    "  {{source_name_part}}\n"
    "      The filename part of the source file with no directory or\n"
    "      extension. This will generally be used for specifying a\n"
    "      transformation from a soruce file to a destination file with the\n"
    "      same name but different extension. For the source \"foo/bar.txt\"\n"
    "      the source name part will be \"bar\".\n"
    "\n"
    "Examples\n"
    "\n"
    "  Non-varying outputs:\n"
    "    action(\"hardcoded_outputs\") {\n"
    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
    "      outputs = [ \"$target_out_dir/output1.dat\",\n"
    "                  \"$target_out_dir/output2.dat\" ]\n"
    "    }\n"
    "  The outputs in this case will be the two literal files given.\n"
    "\n"
    "  Varying outputs:\n"
    "    action_foreach(\"varying_outputs\") {\n"
    "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
    "      outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
    "                  \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
    "    }\n"
    "  Performing source expansion will result in the following output names:\n"
    "    //out/Debug/obj/mydirectory/input1.h\n"
    "    //out/Debug/obj/mydirectory/input1.cc\n"
    "    //out/Debug/obj/mydirectory/input2.h\n"
    "    //out/Debug/obj/mydirectory/input2.cc\n";

FileTemplate::FileTemplate(const Value& t, Err* err)
    : has_substitutions_(false) {
  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
  ParseInput(t, err);
}

FileTemplate::FileTemplate(const std::vector<std::string>& t)
    : has_substitutions_(false) {
  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
  for (size_t i = 0; i < t.size(); i++)
    ParseOneTemplateString(t[i]);
}

FileTemplate::FileTemplate(const std::vector<SourceFile>& t)
    : has_substitutions_(false) {
  std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
  for (size_t i = 0; i < t.size(); i++)
    ParseOneTemplateString(t[i].value());
}

FileTemplate::~FileTemplate() {
}

// static
FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
  const Target::FileList& outputs = target->action_values().outputs();
  std::vector<std::string> output_template_args;
  for (size_t i = 0; i < outputs.size(); i++)
    output_template_args.push_back(outputs[i].value());
  return FileTemplate(output_template_args);
}

bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
  DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
  return types_required_[type];
}

void FileTemplate::Apply(const Value& sources,
                         const ParseNode* origin,
                         std::vector<Value>* dest,
                         Err* err) const {
  if (!sources.VerifyTypeIs(Value::LIST, err))
    return;
  dest->reserve(sources.list_value().size() * templates_.container().size());

  // Temporary holding place, allocate outside to re-use- buffer.
  std::vector<std::string> string_output;

  const std::vector<Value>& sources_list = sources.list_value();
  for (size_t i = 0; i < sources_list.size(); i++) {
    string_output.clear();
    if (!sources_list[i].VerifyTypeIs(Value::STRING, err))
      return;

    ApplyString(sources_list[i].string_value(), &string_output);
    for (size_t out_i = 0; out_i < string_output.size(); out_i++)
      dest->push_back(Value(origin, string_output[out_i]));
  }
}

void FileTemplate::ApplyString(const std::string& str,
                               std::vector<std::string>* output) const {
  // Compute all substitutions needed so we can just do substitutions below.
  // We skip the LITERAL one since that varies each time.
  std::string subst[Subrange::NUM_TYPES];
  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    if (types_required_[i])
      subst[i] = GetSubstitution(str, static_cast<Subrange::Type>(i));
  }

  size_t first_output_index = output->size();
  output->resize(output->size() + templates_.container().size());
  for (size_t template_i = 0;
       template_i < templates_.container().size(); template_i++) {
    const Template& t = templates_[template_i];
    std::string& cur_output = (*output)[first_output_index + template_i];
    for (size_t subrange_i = 0; subrange_i < t.container().size();
         subrange_i++) {
      if (t[subrange_i].type == Subrange::LITERAL)
        cur_output.append(t[subrange_i].literal);
      else
        cur_output.append(subst[t[subrange_i].type]);
    }
  }
}

void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
  EscapeOptions escape_options;
  escape_options.mode = ESCAPE_NINJA_SHELL;
  escape_options.inhibit_quoting = true;

  for (size_t template_i = 0;
       template_i < templates_.container().size(); template_i++) {
    out << " ";  // Separate args with spaces.

    const Template& t = templates_[template_i];

    // Escape each subrange into a string. Since we're writing out Ninja
    // variables, we can't quote the whole thing, so we write in pieces, only
    // escaping the literals, and then quoting the whole thing at the end if
    // necessary.
    bool needs_quoting = false;
    std::string item_str;
    for (size_t subrange_i = 0; subrange_i < t.container().size();
         subrange_i++) {
      if (t[subrange_i].type == Subrange::LITERAL) {
        item_str.append(EscapeString(t[subrange_i].literal, escape_options,
                                     &needs_quoting));
      } else {
        // Don't escape this since we need to preserve the $.
        item_str.append("${");
        item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
        item_str.append("}");
      }
    }

    if (needs_quoting) {
      // Need to shell quote the whole string.
      out << '"' << item_str << '"';
    } else {
      out << item_str;
    }
  }
}

void FileTemplate::WriteNinjaVariablesForSubstitution(
    std::ostream& out,
    const std::string& source,
    const EscapeOptions& escape_options) const {
  for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    if (types_required_[i]) {
      Subrange::Type type = static_cast<Subrange::Type>(i);
      out << "  " << GetNinjaVariableNameForType(type) << " = ";
      EscapeStringToStream(out, GetSubstitution(source, type), escape_options);
      out << std::endl;
    }
  }
}

// static
const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
  switch (type) {
    case Subrange::SOURCE:
      return "source";
    case Subrange::NAME_PART:
      return "source_name_part";
    case Subrange::FILE_PART:
      return "source_file_part";
    default:
      NOTREACHED();
  }
  return "";
}

// static
std::string FileTemplate::GetSubstitution(const std::string& source,
                                          Subrange::Type type) {
  switch (type) {
    case Subrange::SOURCE:
      return source;
    case Subrange::NAME_PART:
      return FindFilenameNoExtension(&source).as_string();
    case Subrange::FILE_PART:
      return FindFilename(&source).as_string();
    default:
      NOTREACHED();
  }
  return std::string();
}

void FileTemplate::ParseInput(const Value& value, Err* err) {
  switch (value.type()) {
    case Value::STRING:
      ParseOneTemplateString(value.string_value());
      break;
    case Value::LIST:
      for (size_t i = 0; i < value.list_value().size(); i++) {
        if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
          return;
        ParseOneTemplateString(value.list_value()[i].string_value());
      }
      break;
    default:
      *err = Err(value, "File template must be a string or list.",
                 "A sarcastic comment about your skills goes here.");
  }
}

void FileTemplate::ParseOneTemplateString(const std::string& str) {
  templates_.container().resize(templates_.container().size() + 1);
  Template& t = templates_[templates_.container().size() - 1];

  size_t cur = 0;
  while (true) {
    size_t next = str.find("{{", cur);

    // Pick up everything from the previous spot to here as a literal.
    if (next == std::string::npos) {
      if (cur != str.size())
        t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
      break;
    } else if (next > cur) {
      t.container().push_back(
          Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
    }

    // Decode the template param.
    if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) {
      t.container().push_back(Subrange(Subrange::SOURCE));
      types_required_[Subrange::SOURCE] = true;
      has_substitutions_ = true;
      cur = next + arraysize(kSource) - 1;
    } else if (str.compare(next, arraysize(kSourceNamePart) - 1,
                           kSourceNamePart) == 0) {
      t.container().push_back(Subrange(Subrange::NAME_PART));
      types_required_[Subrange::NAME_PART] = true;
      has_substitutions_ = true;
      cur = next + arraysize(kSourceNamePart) - 1;
    } else if (str.compare(next, arraysize(kSourceFilePart) - 1,
                           kSourceFilePart) == 0) {
      t.container().push_back(Subrange(Subrange::FILE_PART));
      types_required_[Subrange::FILE_PART] = true;
      has_substitutions_ = true;
      cur = next + arraysize(kSourceFilePart) - 1;
    } else {
      // If it's not a match, treat it like a one-char literal (this will be
      // rare, so it's not worth the bother to add to the previous literal) so
      // we can keep going.
      t.container().push_back(Subrange(Subrange::LITERAL, "{"));
      cur = next + 1;
    }
  }
}