1 // Copyright 2016 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/create_bundle_target_generator.h"
6
7 #include <map>
8
9 #include "base/logging.h"
10 #include "base/strings/stringprintf.h"
11 #include "gn/filesystem_utils.h"
12 #include "gn/label_pattern.h"
13 #include "gn/parse_tree.h"
14 #include "gn/scope.h"
15 #include "gn/substitution_type.h"
16 #include "gn/target.h"
17 #include "gn/value.h"
18 #include "gn/value_extractors.h"
19 #include "gn/variables.h"
20
21 namespace {
22
23 // Retrieves value from `scope` named `name` or `old_name`. If the value comes
24 // from the `old_name` a warning is emitted to inform the name is obsolete.
GetValueFromScope(Scope * scope,std::string_view name,std::string_view old_name)25 const Value* GetValueFromScope(Scope* scope,
26 std::string_view name,
27 std::string_view old_name) {
28 const Value* value = scope->GetValue(name, true);
29 if (value)
30 return value;
31
32 value = scope->GetValue(old_name, true);
33 if (value) {
34 // If there is a value found with the old name, print a warning to the
35 // console and use that value. This is to avoid breaking the existing
36 // build rules in the wild.
37 Err err(*value, "Deprecated variable name",
38 base::StringPrintf(
39 "The name \"%s\" is deprecated, use \"%s\" instead.",
40 std::string(old_name).c_str(), std::string(name).c_str()));
41 err.PrintNonfatalToStdout();
42 }
43
44 return value;
45 }
46
47 } // namespace
48
CreateBundleTargetGenerator(Target * target,Scope * scope,const FunctionCallNode * function_call,Err * err)49 CreateBundleTargetGenerator::CreateBundleTargetGenerator(
50 Target* target,
51 Scope* scope,
52 const FunctionCallNode* function_call,
53 Err* err)
54 : TargetGenerator(target, scope, function_call, err) {}
55
56 CreateBundleTargetGenerator::~CreateBundleTargetGenerator() = default;
57
DoRun()58 void CreateBundleTargetGenerator::DoRun() {
59 target_->set_output_type(Target::CREATE_BUNDLE);
60
61 BundleData& bundle_data = target_->bundle_data();
62 if (!FillBundleDir(SourceDir(), variables::kBundleRootDir,
63 &bundle_data.root_dir()))
64 return;
65 if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleContentsDir,
66 &bundle_data.contents_dir()))
67 return;
68 if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleResourcesDir,
69 &bundle_data.resources_dir()))
70 return;
71 if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleExecutableDir,
72 &bundle_data.executable_dir()))
73 return;
74
75 if (!FillXcodeExtraAttributes())
76 return;
77
78 if (!FillProductType())
79 return;
80
81 if (!FillPartialInfoPlist())
82 return;
83
84 if (!FillXcodeTestApplicationName())
85 return;
86
87 if (!FillPostProcessingScript())
88 return;
89
90 if (!FillPostProcessingSources())
91 return;
92
93 if (!FillPostProcessingOutputs())
94 return;
95
96 if (!FillPostProcessingArgs())
97 return;
98
99 if (!FillBundleDepsFilter())
100 return;
101
102 if (!FillXcassetCompilerFlags())
103 return;
104
105 if (!FillTransparent())
106 return;
107 }
108
FillBundleDir(const SourceDir & bundle_root_dir,std::string_view name,SourceDir * bundle_dir)109 bool CreateBundleTargetGenerator::FillBundleDir(
110 const SourceDir& bundle_root_dir,
111 std::string_view name,
112 SourceDir* bundle_dir) {
113 // All bundle_foo_dir properties are optional. They are only required if they
114 // are used in an expansion. The check is performed there.
115 const Value* value = scope_->GetValue(name, true);
116 if (!value)
117 return true;
118 if (!value->VerifyTypeIs(Value::STRING, err_))
119 return false;
120 std::string str = value->string_value();
121 if (!str.empty() && str[str.size() - 1] != '/')
122 str.push_back('/');
123 if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(), str,
124 value->origin(), err_))
125 return false;
126 if (str != bundle_root_dir.value() &&
127 !IsStringInOutputDir(bundle_root_dir, str)) {
128 *err_ =
129 Err(value->origin(), "Path is not in bundle root dir.",
130 base::StringPrintf("The given file should be in the bundle root "
131 "directory or below.Normally you would do "
132 "\"$bundle_root_dir/foo\". I interpreted this "
133 "as \"%s\".",
134 str.c_str()));
135 return false;
136 }
137 *bundle_dir = SourceDir(std::move(str));
138 return true;
139 }
140
FillXcodeExtraAttributes()141 bool CreateBundleTargetGenerator::FillXcodeExtraAttributes() {
142 // Need to get a mutable value to mark all values in the scope as used. This
143 // cannot be done on a const Scope.
144 Value* value = scope_->GetMutableValue(variables::kXcodeExtraAttributes,
145 Scope::SEARCH_CURRENT, true);
146 if (!value)
147 return true;
148
149 if (!value->VerifyTypeIs(Value::SCOPE, err_))
150 return false;
151
152 Scope* scope_value = value->scope_value();
153
154 Scope::KeyValueMap value_map;
155 scope_value->GetCurrentScopeValues(&value_map);
156 scope_value->MarkAllUsed();
157
158 std::map<std::string, std::string> xcode_extra_attributes;
159 for (const auto& iter : value_map) {
160 if (!iter.second.VerifyTypeIs(Value::STRING, err_))
161 return false;
162
163 xcode_extra_attributes.insert(
164 std::make_pair(std::string(iter.first), iter.second.string_value()));
165 }
166
167 target_->bundle_data().xcode_extra_attributes() =
168 std::move(xcode_extra_attributes);
169 return true;
170 }
171
FillProductType()172 bool CreateBundleTargetGenerator::FillProductType() {
173 const Value* value = scope_->GetValue(variables::kProductType, true);
174 if (!value)
175 return true;
176
177 if (!value->VerifyTypeIs(Value::STRING, err_))
178 return false;
179
180 target_->bundle_data().product_type().assign(value->string_value());
181 return true;
182 }
183
FillPartialInfoPlist()184 bool CreateBundleTargetGenerator::FillPartialInfoPlist() {
185 const Value* value = scope_->GetValue(variables::kPartialInfoPlist, true);
186 if (!value)
187 return true;
188
189 if (!value->VerifyTypeIs(Value::STRING, err_))
190 return false;
191
192 const BuildSettings* build_settings = scope_->settings()->build_settings();
193 SourceFile path = scope_->GetSourceDir().ResolveRelativeFile(
194 *value, err_, build_settings->root_path_utf8());
195
196 if (err_->has_error())
197 return false;
198
199 if (!EnsureStringIsInOutputDir(build_settings->build_dir(), path.value(),
200 value->origin(), err_))
201 return false;
202
203 target_->bundle_data().set_partial_info_plist(path);
204 return true;
205 }
206
FillXcodeTestApplicationName()207 bool CreateBundleTargetGenerator::FillXcodeTestApplicationName() {
208 const Value* value =
209 scope_->GetValue(variables::kXcodeTestApplicationName, true);
210 if (!value)
211 return true;
212
213 if (!value->VerifyTypeIs(Value::STRING, err_))
214 return false;
215
216 target_->bundle_data().xcode_test_application_name().assign(
217 value->string_value());
218 return true;
219 }
220
FillPostProcessingScript()221 bool CreateBundleTargetGenerator::FillPostProcessingScript() {
222 const Value* value = GetValueFromScope(
223 scope_, variables::kPostProcessingScript, variables::kCodeSigningScript);
224 if (!value)
225 return true;
226
227 if (!value->VerifyTypeIs(Value::STRING, err_))
228 return false;
229
230 SourceFile script_file = scope_->GetSourceDir().ResolveRelativeFile(
231 *value, err_, scope_->settings()->build_settings()->root_path_utf8());
232 if (err_->has_error())
233 return false;
234
235 target_->bundle_data().set_post_processing_script(script_file);
236 return true;
237 }
238
FillPostProcessingSources()239 bool CreateBundleTargetGenerator::FillPostProcessingSources() {
240 const Value* value =
241 GetValueFromScope(scope_, variables::kPostProcessingSources,
242 variables::kCodeSigningSources);
243 if (!value)
244 return true;
245
246 if (target_->bundle_data().post_processing_script().is_null()) {
247 *err_ = Err(function_call_, "No post-processing script.",
248 "You must define post_processing_script if you use "
249 "post_processing_sources.");
250 return false;
251 }
252
253 Target::FileList script_sources;
254 if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
255 scope_->GetSourceDir(), &script_sources,
256 err_))
257 return false;
258
259 target_->bundle_data().post_processing_sources() = std::move(script_sources);
260 return true;
261 }
262
FillPostProcessingOutputs()263 bool CreateBundleTargetGenerator::FillPostProcessingOutputs() {
264 const Value* value =
265 GetValueFromScope(scope_, variables::kPostProcessingOutputs,
266 variables::kCodeSigningOutputs);
267 if (!value)
268 return true;
269
270 if (target_->bundle_data().post_processing_script().is_null()) {
271 *err_ = Err(function_call_, "No post-processing script.",
272 "You must define post_processing_script if you use "
273 "post_processing_outputs.");
274 return false;
275 }
276
277 if (!value->VerifyTypeIs(Value::LIST, err_))
278 return false;
279
280 SubstitutionList& outputs = target_->bundle_data().post_processing_outputs();
281 if (!outputs.Parse(*value, err_))
282 return false;
283
284 if (outputs.list().empty()) {
285 *err_ = Err(function_call_, "Post-processing script has no output.",
286 "If you have no outputs, the build system can not tell when "
287 "your post-processing script needs to be run.");
288 return false;
289 }
290
291 // Validate that outputs are in the output dir.
292 CHECK_EQ(value->list_value().size(), outputs.list().size());
293 for (size_t i = 0; i < value->list_value().size(); ++i) {
294 if (!EnsureSubstitutionIsInOutputDir(outputs.list()[i],
295 value->list_value()[i]))
296 return false;
297 }
298
299 return true;
300 }
301
FillPostProcessingArgs()302 bool CreateBundleTargetGenerator::FillPostProcessingArgs() {
303 const Value* value = GetValueFromScope(scope_, variables::kPostProcessingArgs,
304 variables::kCodeSigningArgs);
305 if (!value)
306 return true;
307
308 if (target_->bundle_data().post_processing_script().is_null()) {
309 *err_ = Err(function_call_, "No post-processing script.",
310 "You must define post_processing_script if you use "
311 "post_processing_args.");
312 return false;
313 }
314
315 if (!value->VerifyTypeIs(Value::LIST, err_))
316 return false;
317
318 return target_->bundle_data().post_processing_args().Parse(*value, err_);
319 }
320
FillBundleDepsFilter()321 bool CreateBundleTargetGenerator::FillBundleDepsFilter() {
322 const Value* value = scope_->GetValue(variables::kBundleDepsFilter, true);
323 if (!value)
324 return true;
325
326 if (!value->VerifyTypeIs(Value::LIST, err_))
327 return false;
328
329 const SourceDir& current_dir = scope_->GetSourceDir();
330 std::vector<LabelPattern>& bundle_deps_filter =
331 target_->bundle_data().bundle_deps_filter();
332 for (const auto& item : value->list_value()) {
333 bundle_deps_filter.push_back(LabelPattern::GetPattern(
334 current_dir, scope_->settings()->build_settings()->root_path_utf8(),
335 item, err_));
336 if (err_->has_error())
337 return false;
338 }
339
340 return true;
341 }
342
FillXcassetCompilerFlags()343 bool CreateBundleTargetGenerator::FillXcassetCompilerFlags() {
344 const Value* value = scope_->GetValue(variables::kXcassetCompilerFlags, true);
345 if (!value)
346 return true;
347
348 if (!value->VerifyTypeIs(Value::LIST, err_))
349 return false;
350
351 return target_->bundle_data().xcasset_compiler_flags().Parse(*value, err_);
352 }
353
FillTransparent()354 bool CreateBundleTargetGenerator::FillTransparent() {
355 const Value* value = scope_->GetValue(variables::kTransparent, true);
356 if (!value)
357 return true;
358
359 if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
360 return false;
361
362 target_->bundle_data().set_transparent(value->boolean_value());
363 return true;
364 }
365