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