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
15 const char FileTemplate::kSource[] = "{{source}}";
16 const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
17 const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";
18
19 const 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
FileTemplate(const Value & t,Err * err)81 FileTemplate::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
FileTemplate(const std::vector<std::string> & t)87 FileTemplate::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
~FileTemplate()94 FileTemplate::~FileTemplate() {
95 }
96
97 // static
GetForTargetOutputs(const Target * target)98 FileTemplate 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
IsTypeUsed(Subrange::Type type) const106 bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
107 DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
108 return types_required_[type];
109 }
110
Apply(const Value & sources,const ParseNode * origin,std::vector<Value> * dest,Err * err) const111 void 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
ApplyString(const std::string & str,std::vector<std::string> * output) const133 void 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
WriteWithNinjaExpansions(std::ostream & out) const158 void 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
WriteNinjaVariablesForSubstitution(std::ostream & out,const std::string & source,const EscapeOptions & escape_options) const197 void 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
GetNinjaVariableNameForType(Subrange::Type type)212 const 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
GetSubstitution(const std::string & source,Subrange::Type type)227 std::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
ParseInput(const Value & value,Err * err)242 void 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
ParseOneTemplateString(const std::string & str)260 void 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