1 // Copyright 2014 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/substitution_writer.h"
6
7 #include "tools/gn/build_settings.h"
8 #include "tools/gn/escape.h"
9 #include "tools/gn/filesystem_utils.h"
10 #include "tools/gn/output_file.h"
11 #include "tools/gn/settings.h"
12 #include "tools/gn/source_file.h"
13 #include "tools/gn/string_utils.h"
14 #include "tools/gn/substitution_list.h"
15 #include "tools/gn/substitution_pattern.h"
16 #include "tools/gn/target.h"
17
18 namespace {
19
20 // Sets the given directory string to the destination, trimming any trailing
21 // slash from the directory (SourceDirs and OutputFiles representing
22 // directories will end in a trailing slash). If the directory is empty,
23 // it will be replaced with a ".".
SetDirOrDotWithNoSlash(const std::string & dir,std::string * dest)24 void SetDirOrDotWithNoSlash(const std::string& dir, std::string* dest) {
25 if (!dir.empty() && dir[dir.size() - 1] == '/')
26 dest->assign(dir.data(), dir.size() - 1);
27 else
28 dest->assign(dir);
29
30 if (dest->empty())
31 dest->push_back('.');
32 }
33
34 } // namespace
35
36 const char kSourceExpansion_Help[] =
37 "How Source Expansion Works\n"
38 "\n"
39 " Source expansion is used for the action_foreach and copy target types\n"
40 " to map source file names to output file names or arguments.\n"
41 "\n"
42 " To perform source expansion in the outputs, GN maps every entry in the\n"
43 " sources to every entry in the outputs list, producing the cross\n"
44 " product of all combinations, expanding placeholders (see below).\n"
45 "\n"
46 " Source expansion in the args works similarly, but performing the\n"
47 " placeholder substitution produces a different set of arguments for\n"
48 " each invocation of the script.\n"
49 "\n"
50 " If no placeholders are found, the outputs or args list will be treated\n"
51 " as a static list of literal file names that do not depend on the\n"
52 " sources.\n"
53 "\n"
54 " See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
55 " this is applied.\n"
56 "\n"
57 "Placeholders\n"
58 "\n"
59 " {{source}}\n"
60 " The name of the source file including directory (*). This will\n"
61 " generally be used for specifying inputs to a script in the\n"
62 " \"args\" variable.\n"
63 " \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
64 "\n"
65 " {{source_file_part}}\n"
66 " The file part of the source including the extension.\n"
67 " \"//foo/bar/baz.txt\" => \"baz.txt\"\n"
68 "\n"
69 " {{source_name_part}}\n"
70 " The filename part of the source file with no directory or\n"
71 " extension. This will generally be used for specifying a\n"
72 " transformation from a soruce file to a destination file with the\n"
73 " same name but different extension.\n"
74 " \"//foo/bar/baz.txt\" => \"baz\"\n"
75 "\n"
76 " {{source_dir}}\n"
77 " The directory (*) containing the source file with no\n"
78 " trailing slash.\n"
79 " \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
80 "\n"
81 " {{source_root_relative_dir}}\n"
82 " The path to the source file's directory relative to the source\n"
83 " root, with no leading \"//\" or trailing slashes. If the path is\n"
84 " system-absolute, (beginning in a single slash) this will just\n"
85 " return the path with no trailing slash. This value will always\n"
86 " be the same, regardless of whether it appears in the \"outputs\"\n"
87 " or \"args\" section.\n"
88 " \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
89 "\n"
90 " {{source_gen_dir}}\n"
91 " The generated file directory (*) corresponding to the source\n"
92 " file's path. This will be different than the target's generated\n"
93 " file directory if the source file is in a different directory\n"
94 " than the BUILD.gn file.\n"
95 " \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
96 "\n"
97 " {{source_out_dir}}\n"
98 " The object file directory (*) corresponding to the source file's\n"
99 " path, relative to the build directory. this us be different than\n"
100 " the target's out directory if the source file is in a different\n"
101 " directory than the build.gn file.\n"
102 " \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
103 "\n"
104 "(*) Note on directories\n"
105 "\n"
106 " Paths containing directories (except the source_root_relative_dir)\n"
107 " will be different depending on what context the expansion is evaluated\n"
108 " in. Generally it should \"just work\" but it means you can't\n"
109 " concatenate strings containing these values with reasonable results.\n"
110 "\n"
111 " Details: source expansions can be used in the \"outputs\" variable,\n"
112 " the \"args\" variable, and in calls to \"process_file_template\". The\n"
113 " \"args\" are passed to a script which is run from the build directory,\n"
114 " so these directories will relative to the build directory for the\n"
115 " script to find. In the other cases, the directories will be source-\n"
116 " absolute (begin with a \"//\") because the results of those expansions\n"
117 " will be handled by GN internally.\n"
118 "\n"
119 "Examples\n"
120 "\n"
121 " Non-varying outputs:\n"
122 " action(\"hardcoded_outputs\") {\n"
123 " sources = [ \"input1.idl\", \"input2.idl\" ]\n"
124 " outputs = [ \"$target_out_dir/output1.dat\",\n"
125 " \"$target_out_dir/output2.dat\" ]\n"
126 " }\n"
127 " The outputs in this case will be the two literal files given.\n"
128 "\n"
129 " Varying outputs:\n"
130 " action_foreach(\"varying_outputs\") {\n"
131 " sources = [ \"input1.idl\", \"input2.idl\" ]\n"
132 " outputs = [ \"{{source_gen_dir}}/{{source_name_part}}.h\",\n"
133 " \"{{source_gen_dir}}/{{source_name_part}}.cc\" ]\n"
134 " }\n"
135 " Performing source expansion will result in the following output names:\n"
136 " //out/Debug/obj/mydirectory/input1.h\n"
137 " //out/Debug/obj/mydirectory/input1.cc\n"
138 " //out/Debug/obj/mydirectory/input2.h\n"
139 " //out/Debug/obj/mydirectory/input2.cc\n";
140
141 // static
WriteWithNinjaVariables(const SubstitutionPattern & pattern,const EscapeOptions & escape_options,std::ostream & out)142 void SubstitutionWriter::WriteWithNinjaVariables(
143 const SubstitutionPattern& pattern,
144 const EscapeOptions& escape_options,
145 std::ostream& out) {
146 // The result needs to be quoted as if it was one string, but the $ for
147 // the inserted Ninja variables can't be escaped. So write to a buffer with
148 // no quoting, and then quote the whole thing if necessary.
149 EscapeOptions no_quoting(escape_options);
150 no_quoting.inhibit_quoting = true;
151
152 bool needs_quotes = false;
153 std::string result;
154 for (size_t i = 0; i < pattern.ranges().size(); i++) {
155 const SubstitutionPattern::Subrange range = pattern.ranges()[i];
156 if (range.type == SUBSTITUTION_LITERAL) {
157 result.append(EscapeString(range.literal, no_quoting, &needs_quotes));
158 } else {
159 result.append("${");
160 result.append(kSubstitutionNinjaNames[range.type]);
161 result.append("}");
162 }
163 }
164
165 if (needs_quotes && !escape_options.inhibit_quoting)
166 out << "\"" << result << "\"";
167 else
168 out << result;
169 }
170
171 // static
GetListAsSourceFiles(const SubstitutionList & list,std::vector<SourceFile> * output)172 void SubstitutionWriter::GetListAsSourceFiles(
173 const SubstitutionList& list,
174 std::vector<SourceFile>* output) {
175 for (size_t i = 0; i < list.list().size(); i++) {
176 const SubstitutionPattern& pattern = list.list()[i];
177 CHECK(pattern.ranges().size() == 1 &&
178 pattern.ranges()[0].type == SUBSTITUTION_LITERAL)
179 << "The substitution patterm \""
180 << pattern.AsString()
181 << "\" was expected to be a literal with no {{substitutions}}.";
182 const std::string& literal = pattern.ranges()[0].literal;
183 CHECK(literal.size() >= 1 && literal[0] == '/')
184 << "The result of the pattern \""
185 << pattern.AsString()
186 << "\" was not an absolute path.";
187 output->push_back(SourceFile(literal));
188 }
189 }
190
191 // static
GetListAsOutputFiles(const Settings * settings,const SubstitutionList & list,std::vector<OutputFile> * output)192 void SubstitutionWriter::GetListAsOutputFiles(
193 const Settings* settings,
194 const SubstitutionList& list,
195 std::vector<OutputFile>* output) {
196 std::vector<SourceFile> output_as_sources;
197 GetListAsSourceFiles(list, &output_as_sources);
198 for (size_t i = 0; i < output_as_sources.size(); i++) {
199 output->push_back(OutputFile(settings->build_settings(),
200 output_as_sources[i]));
201 }
202 }
203
204 // static
ApplyPatternToSource(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)205 SourceFile SubstitutionWriter::ApplyPatternToSource(
206 const Settings* settings,
207 const SubstitutionPattern& pattern,
208 const SourceFile& source) {
209 std::string result_value = ApplyPatternToSourceAsString(
210 settings, pattern, source);
211 CHECK(!result_value.empty() && result_value[0] == '/')
212 << "The result of the pattern \""
213 << pattern.AsString()
214 << "\" was not a path beginning in \"/\" or \"//\".";
215 return SourceFile(SourceFile::SWAP_IN, &result_value);
216 }
217
218 // static
ApplyPatternToSourceAsString(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)219 std::string SubstitutionWriter::ApplyPatternToSourceAsString(
220 const Settings* settings,
221 const SubstitutionPattern& pattern,
222 const SourceFile& source) {
223 std::string result_value;
224 for (size_t i = 0; i < pattern.ranges().size(); i++) {
225 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
226 if (subrange.type == SUBSTITUTION_LITERAL) {
227 result_value.append(subrange.literal);
228 } else {
229 result_value.append(
230 GetSourceSubstitution(settings, source, subrange.type,
231 OUTPUT_ABSOLUTE, SourceDir()));
232 }
233 }
234 return result_value;
235 }
236
237 // static
ApplyPatternToSourceAsOutputFile(const Settings * settings,const SubstitutionPattern & pattern,const SourceFile & source)238 OutputFile SubstitutionWriter::ApplyPatternToSourceAsOutputFile(
239 const Settings* settings,
240 const SubstitutionPattern& pattern,
241 const SourceFile& source) {
242 SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source);
243 CHECK(result_as_source.is_source_absolute())
244 << "The result of the pattern \""
245 << pattern.AsString()
246 << "\" was not an absolute path beginning in \"//\".";
247 return OutputFile(settings->build_settings(), result_as_source);
248 }
249
250 // static
ApplyListToSource(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<SourceFile> * output)251 void SubstitutionWriter::ApplyListToSource(
252 const Settings* settings,
253 const SubstitutionList& list,
254 const SourceFile& source,
255 std::vector<SourceFile>* output) {
256 for (size_t i = 0; i < list.list().size(); i++) {
257 output->push_back(ApplyPatternToSource(
258 settings, list.list()[i], source));
259 }
260 }
261
262 // static
ApplyListToSourceAsString(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<std::string> * output)263 void SubstitutionWriter::ApplyListToSourceAsString(
264 const Settings* settings,
265 const SubstitutionList& list,
266 const SourceFile& source,
267 std::vector<std::string>* output) {
268 for (size_t i = 0; i < list.list().size(); i++) {
269 output->push_back(ApplyPatternToSourceAsString(
270 settings, list.list()[i], source));
271 }
272 }
273
274 // static
ApplyListToSourceAsOutputFile(const Settings * settings,const SubstitutionList & list,const SourceFile & source,std::vector<OutputFile> * output)275 void SubstitutionWriter::ApplyListToSourceAsOutputFile(
276 const Settings* settings,
277 const SubstitutionList& list,
278 const SourceFile& source,
279 std::vector<OutputFile>* output) {
280 for (size_t i = 0; i < list.list().size(); i++) {
281 output->push_back(ApplyPatternToSourceAsOutputFile(
282 settings, list.list()[i], source));
283 }
284 }
285
286 // static
ApplyListToSources(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<SourceFile> * output)287 void SubstitutionWriter::ApplyListToSources(
288 const Settings* settings,
289 const SubstitutionList& list,
290 const std::vector<SourceFile>& sources,
291 std::vector<SourceFile>* output) {
292 output->clear();
293 for (size_t i = 0; i < sources.size(); i++)
294 ApplyListToSource(settings, list, sources[i], output);
295 }
296
297 // static
ApplyListToSourcesAsString(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<std::string> * output)298 void SubstitutionWriter::ApplyListToSourcesAsString(
299 const Settings* settings,
300 const SubstitutionList& list,
301 const std::vector<SourceFile>& sources,
302 std::vector<std::string>* output) {
303 output->clear();
304 for (size_t i = 0; i < sources.size(); i++)
305 ApplyListToSourceAsString(settings, list, sources[i], output);
306 }
307
308 // static
ApplyListToSourcesAsOutputFile(const Settings * settings,const SubstitutionList & list,const std::vector<SourceFile> & sources,std::vector<OutputFile> * output)309 void SubstitutionWriter::ApplyListToSourcesAsOutputFile(
310 const Settings* settings,
311 const SubstitutionList& list,
312 const std::vector<SourceFile>& sources,
313 std::vector<OutputFile>* output) {
314 output->clear();
315 for (size_t i = 0; i < sources.size(); i++)
316 ApplyListToSourceAsOutputFile(settings, list, sources[i], output);
317 }
318
319 // static
WriteNinjaVariablesForSource(const Settings * settings,const SourceFile & source,const std::vector<SubstitutionType> & types,const EscapeOptions & escape_options,std::ostream & out)320 void SubstitutionWriter::WriteNinjaVariablesForSource(
321 const Settings* settings,
322 const SourceFile& source,
323 const std::vector<SubstitutionType>& types,
324 const EscapeOptions& escape_options,
325 std::ostream& out) {
326 for (size_t i = 0; i < types.size(); i++) {
327 // Don't write SOURCE since that just maps to Ninja's $in variable, which
328 // is implicit in the rule.
329 if (types[i] != SUBSTITUTION_SOURCE) {
330 out << " " << kSubstitutionNinjaNames[types[i]] << " = ";
331 EscapeStringToStream(
332 out,
333 GetSourceSubstitution(settings, source, types[i], OUTPUT_RELATIVE,
334 settings->build_settings()->build_dir()),
335 escape_options);
336 out << std::endl;
337 }
338 }
339 }
340
341 // static
GetSourceSubstitution(const Settings * settings,const SourceFile & source,SubstitutionType type,OutputStyle output_style,const SourceDir & relative_to)342 std::string SubstitutionWriter::GetSourceSubstitution(
343 const Settings* settings,
344 const SourceFile& source,
345 SubstitutionType type,
346 OutputStyle output_style,
347 const SourceDir& relative_to) {
348 std::string to_rebase;
349 switch (type) {
350 case SUBSTITUTION_SOURCE:
351 if (source.is_system_absolute())
352 return source.value();
353 to_rebase = source.value();
354 break;
355
356 case SUBSTITUTION_SOURCE_NAME_PART:
357 return FindFilenameNoExtension(&source.value()).as_string();
358
359 case SUBSTITUTION_SOURCE_FILE_PART:
360 return source.GetName();
361
362 case SUBSTITUTION_SOURCE_DIR:
363 if (source.is_system_absolute())
364 return DirectoryWithNoLastSlash(source.GetDir());
365 to_rebase = DirectoryWithNoLastSlash(source.GetDir());
366 break;
367
368 case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
369 if (source.is_system_absolute())
370 return DirectoryWithNoLastSlash(source.GetDir());
371 return RebaseSourceAbsolutePath(
372 DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
373
374 case SUBSTITUTION_SOURCE_GEN_DIR:
375 to_rebase = DirectoryWithNoLastSlash(
376 GetGenDirForSourceDir(settings, source.GetDir()));
377 break;
378
379 case SUBSTITUTION_SOURCE_OUT_DIR:
380 to_rebase = DirectoryWithNoLastSlash(
381 GetOutputDirForSourceDir(settings, source.GetDir()));
382 break;
383
384 default:
385 NOTREACHED()
386 << "Unsupported substitution for this function: "
387 << kSubstitutionNames[type];
388 return std::string();
389 }
390
391 // If we get here, the result is a path that should be made relative or
392 // absolute according to the output_style. Other cases (just file name or
393 // extension extraction) will have been handled via early return above.
394 if (output_style == OUTPUT_ABSOLUTE)
395 return to_rebase;
396 return RebaseSourceAbsolutePath(to_rebase, relative_to);
397 }
398
399 // static
ApplyPatternToTargetAsOutputFile(const Target * target,const Tool * tool,const SubstitutionPattern & pattern)400 OutputFile SubstitutionWriter::ApplyPatternToTargetAsOutputFile(
401 const Target* target,
402 const Tool* tool,
403 const SubstitutionPattern& pattern) {
404 std::string result_value;
405 for (size_t i = 0; i < pattern.ranges().size(); i++) {
406 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
407 if (subrange.type == SUBSTITUTION_LITERAL) {
408 result_value.append(subrange.literal);
409 } else {
410 std::string subst;
411 CHECK(GetTargetSubstitution(target, subrange.type, &subst));
412 result_value.append(subst);
413 }
414 }
415 return OutputFile(result_value);
416 }
417
418 // static
ApplyListToTargetAsOutputFile(const Target * target,const Tool * tool,const SubstitutionList & list,std::vector<OutputFile> * output)419 void SubstitutionWriter::ApplyListToTargetAsOutputFile(
420 const Target* target,
421 const Tool* tool,
422 const SubstitutionList& list,
423 std::vector<OutputFile>* output) {
424 for (size_t i = 0; i < list.list().size(); i++) {
425 output->push_back(ApplyPatternToTargetAsOutputFile(
426 target, tool, list.list()[i]));
427 }
428 }
429
430 // static
GetTargetSubstitution(const Target * target,SubstitutionType type,std::string * result)431 bool SubstitutionWriter::GetTargetSubstitution(
432 const Target* target,
433 SubstitutionType type,
434 std::string* result) {
435 switch (type) {
436 case SUBSTITUTION_LABEL:
437 // Only include the toolchain for non-default toolchains.
438 *result = target->label().GetUserVisibleName(
439 !target->settings()->is_default());
440 break;
441 case SUBSTITUTION_ROOT_GEN_DIR:
442 SetDirOrDotWithNoSlash(
443 GetToolchainGenDirAsOutputFile(target->settings()).value(),
444 result);
445 break;
446 case SUBSTITUTION_ROOT_OUT_DIR:
447 SetDirOrDotWithNoSlash(
448 target->settings()->toolchain_output_subdir().value(),
449 result);
450 break;
451 case SUBSTITUTION_TARGET_GEN_DIR:
452 SetDirOrDotWithNoSlash(
453 GetTargetGenDirAsOutputFile(target).value(),
454 result);
455 break;
456 case SUBSTITUTION_TARGET_OUT_DIR:
457 SetDirOrDotWithNoSlash(
458 GetTargetOutputDirAsOutputFile(target).value(),
459 result);
460 break;
461 case SUBSTITUTION_TARGET_OUTPUT_NAME:
462 *result = target->GetComputedOutputName(true);
463 break;
464 default:
465 return false;
466 }
467 return true;
468 }
469
470 // static
GetTargetSubstitution(const Target * target,SubstitutionType type)471 std::string SubstitutionWriter::GetTargetSubstitution(
472 const Target* target,
473 SubstitutionType type) {
474 std::string result;
475 GetTargetSubstitution(target, type, &result);
476 return result;
477 }
478
479 // static
ApplyPatternToCompilerAsOutputFile(const Target * target,const SourceFile & source,const SubstitutionPattern & pattern)480 OutputFile SubstitutionWriter::ApplyPatternToCompilerAsOutputFile(
481 const Target* target,
482 const SourceFile& source,
483 const SubstitutionPattern& pattern) {
484 OutputFile result;
485 for (size_t i = 0; i < pattern.ranges().size(); i++) {
486 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
487 if (subrange.type == SUBSTITUTION_LITERAL) {
488 result.value().append(subrange.literal);
489 } else {
490 result.value().append(
491 GetCompilerSubstitution(target, source, subrange.type));
492 }
493 }
494 return result;
495 }
496
497 // static
ApplyListToCompilerAsOutputFile(const Target * target,const SourceFile & source,const SubstitutionList & list,std::vector<OutputFile> * output)498 void SubstitutionWriter::ApplyListToCompilerAsOutputFile(
499 const Target* target,
500 const SourceFile& source,
501 const SubstitutionList& list,
502 std::vector<OutputFile>* output) {
503 for (size_t i = 0; i < list.list().size(); i++) {
504 output->push_back(ApplyPatternToCompilerAsOutputFile(
505 target, source, list.list()[i]));
506 }
507 }
508
509 // static
GetCompilerSubstitution(const Target * target,const SourceFile & source,SubstitutionType type)510 std::string SubstitutionWriter::GetCompilerSubstitution(
511 const Target* target,
512 const SourceFile& source,
513 SubstitutionType type) {
514 // First try the common tool ones.
515 std::string result;
516 if (GetTargetSubstitution(target, type, &result))
517 return result;
518
519 // Fall-through to the source ones.
520 return GetSourceSubstitution(
521 target->settings(), source, type, OUTPUT_RELATIVE,
522 target->settings()->build_settings()->build_dir());
523 }
524
525 // static
ApplyPatternToLinkerAsOutputFile(const Target * target,const Tool * tool,const SubstitutionPattern & pattern)526 OutputFile SubstitutionWriter::ApplyPatternToLinkerAsOutputFile(
527 const Target* target,
528 const Tool* tool,
529 const SubstitutionPattern& pattern) {
530 OutputFile result;
531 for (size_t i = 0; i < pattern.ranges().size(); i++) {
532 const SubstitutionPattern::Subrange& subrange = pattern.ranges()[i];
533 if (subrange.type == SUBSTITUTION_LITERAL) {
534 result.value().append(subrange.literal);
535 } else {
536 result.value().append(GetLinkerSubstitution(target, tool, subrange.type));
537 }
538 }
539 return result;
540 }
541
542 // static
ApplyListToLinkerAsOutputFile(const Target * target,const Tool * tool,const SubstitutionList & list,std::vector<OutputFile> * output)543 void SubstitutionWriter::ApplyListToLinkerAsOutputFile(
544 const Target* target,
545 const Tool* tool,
546 const SubstitutionList& list,
547 std::vector<OutputFile>* output) {
548 for (size_t i = 0; i < list.list().size(); i++) {
549 output->push_back(ApplyPatternToLinkerAsOutputFile(
550 target, tool, list.list()[i]));
551 }
552 }
553
554 // static
GetLinkerSubstitution(const Target * target,const Tool * tool,SubstitutionType type)555 std::string SubstitutionWriter::GetLinkerSubstitution(
556 const Target* target,
557 const Tool* tool,
558 SubstitutionType type) {
559 // First try the common tool ones.
560 std::string result;
561 if (GetTargetSubstitution(target, type, &result))
562 return result;
563
564 // Fall-through to the linker-specific ones.
565 switch (type) {
566 case SUBSTITUTION_OUTPUT_EXTENSION:
567 // Use the extension provided on the target if nonempty, otherwise
568 // fall back on the default. Note that the target's output extension
569 // does not include the dot but the tool's does.
570 if (target->output_extension().empty())
571 return tool->default_output_extension();
572 return std::string(".") + target->output_extension();
573
574 default:
575 NOTREACHED();
576 return std::string();
577 }
578 }
579