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 const char FileTemplate::kSourceDir[] = "{{source_dir}}";
19 const char FileTemplate::kRootRelDir[] = "{{source_root_relative_dir}}";
20 const char FileTemplate::kSourceGenDir[] = "{{source_gen_dir}}";
21 const char FileTemplate::kSourceOutDir[] = "{{source_out_dir}}";
22
23 const 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
FileTemplate(const Settings * settings,const Value & t,Err * err)115 FileTemplate::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
FileTemplate(const Settings * settings,const std::vector<std::string> & t)122 FileTemplate::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
FileTemplate(const Settings * settings,const std::vector<SourceFile> & t)131 FileTemplate::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
~FileTemplate()140 FileTemplate::~FileTemplate() {
141 }
142
143 // static
GetForTargetOutputs(const Target * target)144 FileTemplate 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
IsTypeUsed(Subrange::Type type) const152 bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
153 DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
154 return types_required_[type];
155 }
156
Apply(const SourceFile & source,std::vector<std::string> * output) const157 void 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
WriteWithNinjaExpansions(std::ostream & out) const185 void 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
WriteNinjaVariablesForSubstitution(std::ostream & out,const Settings * settings,const SourceFile & source,const EscapeOptions & escape_options) const228 void 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
GetNinjaVariableNameForType(Subrange::Type type)245 const 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
GetSubstitution(const Settings * settings,const SourceFile & source,Subrange::Type type)269 std::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
ParseInput(const Value & value,Err * err)316 void 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
ParseOneTemplateString(const std::string & str)334 void 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