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.
5 #include "tools/gn/file_template.h"
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"
15 const char FileTemplate::kSource
[] = "{{source}}";
16 const char FileTemplate::kSourceNamePart
[] = "{{source_name_part}}";
17 const char FileTemplate::kSourceFilePart
[] = "{{source_file_part}}";
19 const char kSourceExpansion_Help
[] =
20 "How Source Expansion Works\n"
22 " Source expansion is used for the action_foreach and copy target types\n"
23 " to map source file names to output file names or arguments.\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"
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"
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"
37 " See \"gn help copy\" and \"gn help action_foreach\" for more on how\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"
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"
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"
61 " Non-varying outputs:\n"
62 " action(\"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"
67 " The outputs in this case will be the two literal files given.\n"
70 " action_foreach(\"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"
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";
81 FileTemplate::FileTemplate(const Value
& t
, Err
* err
)
82 : has_substitutions_(false) {
83 std::fill(types_required_
, &types_required_
[Subrange::NUM_TYPES
], false);
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
]);
94 FileTemplate::~FileTemplate() {
98 FileTemplate
FileTemplate::GetForTargetOutputs(const Target
* target
) {
99 const Target::FileList
& outputs
= target
->action_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
);
106 bool FileTemplate::IsTypeUsed(Subrange::Type type
) const {
107 DCHECK(type
> Subrange::LITERAL
&& type
< Subrange::NUM_TYPES
);
108 return types_required_
[type
];
111 void FileTemplate::Apply(const Value
& sources
,
112 const ParseNode
* origin
,
113 std::vector
<Value
>* dest
,
115 if (!sources
.VerifyTypeIs(Value::LIST
, err
))
117 dest
->reserve(sources
.list_value().size() * templates_
.container().size());
119 // Temporary holding place, allocate outside to re-use- buffer.
120 std::vector
<std::string
> string_output
;
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
))
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
[out_i
]));
133 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
));
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();
150 if (t
[subrange_i
].type
== Subrange::LITERAL
)
151 (*output
)[template_i
].append(t
[subrange_i
].literal
);
153 (*output
)[template_i
].append(subst
[t
[subrange_i
].type
]);
158 void FileTemplate::WriteWithNinjaExpansions(std::ostream
& out
) const {
159 EscapeOptions escape_options
;
160 escape_options
.mode
= ESCAPE_NINJA_SHELL
;
161 escape_options
.inhibit_quoting
= true;
163 for (size_t template_i
= 0;
164 template_i
< templates_
.container().size(); template_i
++) {
165 out
<< " "; // Separate args with spaces.
167 const Template
& t
= templates_
[template_i
];
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
173 bool needs_quoting
= false;
174 std::string item_str
;
175 for (size_t subrange_i
= 0; subrange_i
< t
.container().size();
177 if (t
[subrange_i
].type
== Subrange::LITERAL
) {
178 item_str
.append(EscapeString(t
[subrange_i
].literal
, escape_options
,
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("}");
189 // Need to shell quote the whole string.
190 out
<< '"' << item_str
<< '"';
197 void FileTemplate::WriteNinjaVariablesForSubstitution(
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
);
212 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type
) {
214 case Subrange::SOURCE
:
216 case Subrange::NAME_PART
:
217 return "source_name_part";
218 case Subrange::FILE_PART
:
219 return "source_file_part";
227 std::string
FileTemplate::GetSubstitution(const std::string
& source
,
228 Subrange::Type type
) {
230 case Subrange::SOURCE
:
232 case Subrange::NAME_PART
:
233 return FindFilenameNoExtension(&source
).as_string();
234 case Subrange::FILE_PART
:
235 return FindFilename(&source
).as_string();
239 return std::string();
242 void FileTemplate::ParseInput(const Value
& value
, Err
* err
) {
243 switch (value
.type()) {
245 ParseOneTemplateString(value
.string_value());
248 for (size_t i
= 0; i
< value
.list_value().size(); i
++) {
249 if (!value
.list_value()[i
].VerifyTypeIs(Value::STRING
, err
))
251 ParseOneTemplateString(value
.list_value()[i
].string_value());
255 *err
= Err(value
, "File template must be a string or list.",
256 "A sarcastic comment about your skills goes here.");
260 void FileTemplate::ParseOneTemplateString(const std::string
& str
) {
261 templates_
.container().resize(templates_
.container().size() + 1);
262 Template
& t
= templates_
[templates_
.container().size() - 1];
266 size_t next
= str
.find("{{", cur
);
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
)));
273 } else if (next
> cur
) {
274 t
.container().push_back(
275 Subrange(Subrange::LITERAL
, str
.substr(cur
, next
- cur
)));
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;
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
, "{"));