1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "configuration/ConfigurationParser.h"
18
19 #include <algorithm>
20 #include <functional>
21 #include <map>
22 #include <memory>
23 #include <utility>
24
25 #include "android-base/file.h"
26 #include "android-base/logging.h"
27
28 #include "ConfigDescription.h"
29 #include "Diagnostics.h"
30 #include "io/File.h"
31 #include "io/FileSystem.h"
32 #include "io/StringInputStream.h"
33 #include "util/Maybe.h"
34 #include "util/Util.h"
35 #include "xml/XmlActionExecutor.h"
36 #include "xml/XmlDom.h"
37 #include "xml/XmlUtil.h"
38
39 namespace aapt {
40
41 namespace {
42
43 using ::aapt::configuration::Abi;
44 using ::aapt::configuration::AndroidManifest;
45 using ::aapt::configuration::AndroidSdk;
46 using ::aapt::configuration::Artifact;
47 using ::aapt::configuration::PostProcessingConfiguration;
48 using ::aapt::configuration::GlTexture;
49 using ::aapt::configuration::Group;
50 using ::aapt::configuration::Locale;
51 using ::aapt::io::IFile;
52 using ::aapt::io::RegularFile;
53 using ::aapt::io::StringInputStream;
54 using ::aapt::util::TrimWhitespace;
55 using ::aapt::xml::Element;
56 using ::aapt::xml::NodeCast;
57 using ::aapt::xml::XmlActionExecutor;
58 using ::aapt::xml::XmlActionExecutorPolicy;
59 using ::aapt::xml::XmlNodeAction;
60 using ::android::base::ReadFileToString;
61
62 const std::unordered_map<std::string, Abi> kStringToAbiMap = {
63 {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
64 {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips},
65 {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal},
66 };
67 const std::map<Abi, std::string> kAbiToStringMap = {
68 {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"},
69 {Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"},
70 {Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"},
71 };
72
73 constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
74
75 /** A default noop diagnostics context. */
76 class NoopDiagnostics : public IDiagnostics {
77 public:
Log(Level level,DiagMessageActual & actualMsg)78 void Log(Level level, DiagMessageActual& actualMsg) override {}
79 };
80 NoopDiagnostics noop_;
81
GetLabel(const Element * element,IDiagnostics * diag)82 std::string GetLabel(const Element* element, IDiagnostics* diag) {
83 std::string label;
84 for (const auto& attr : element->attributes) {
85 if (attr.name == "label") {
86 label = attr.value;
87 break;
88 }
89 }
90
91 if (label.empty()) {
92 diag->Error(DiagMessage() << "No label found for element " << element->name);
93 }
94 return label;
95 }
96
97 /** XML node visitor that removes all of the namespace URIs from the node and all children. */
98 class NamespaceVisitor : public xml::Visitor {
99 public:
Visit(xml::Element * node)100 void Visit(xml::Element* node) override {
101 node->namespace_uri.clear();
102 VisitChildren(node);
103 }
104 };
105
106 } // namespace
107
108 namespace configuration {
109
AbiToString(Abi abi)110 const std::string& AbiToString(Abi abi) {
111 return kAbiToStringMap.find(abi)->second;
112 }
113
114 /**
115 * Attempts to replace the placeholder in the name string with the provided value. Returns true on
116 * success, or false if the either the placeholder is not found in the name, or the value is not
117 * present and the placeholder was.
118 */
ReplacePlaceholder(const std::string & placeholder,const Maybe<std::string> & value,std::string * name,IDiagnostics * diag)119 static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value,
120 std::string* name, IDiagnostics* diag) {
121 size_t offset = name->find(placeholder);
122 if (value) {
123 if (offset == std::string::npos) {
124 diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder);
125 return false;
126 }
127 name->replace(offset, placeholder.length(), value.value());
128 return true;
129 }
130
131 // Make sure the placeholder was not present if the desired value was not present.
132 bool result = (offset == std::string::npos);
133 if (!result) {
134 diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder);
135 }
136 return result;
137 }
138
ToArtifactName(const std::string & format,IDiagnostics * diag) const139 Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const {
140 std::string result = format;
141
142 if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) {
143 return {};
144 }
145
146 if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) {
147 return {};
148 }
149
150 if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) {
151 return {};
152 }
153
154 if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) {
155 return {};
156 }
157
158 if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) {
159 return {};
160 }
161
162 if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) {
163 return {};
164 }
165
166 return result;
167 }
168
169 } // namespace configuration
170
171 /** Returns a ConfigurationParser for the file located at the provided path. */
ForPath(const std::string & path)172 Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
173 std::string contents;
174 if (!ReadFileToString(path, &contents, true)) {
175 return {};
176 }
177 return ConfigurationParser(contents);
178 }
179
ConfigurationParser(std::string contents)180 ConfigurationParser::ConfigurationParser(std::string contents)
181 : contents_(std::move(contents)),
182 diag_(&noop_) {
183 }
184
Parse()185 Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() {
186 StringInputStream in(contents_);
187 std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag_, Source("config.xml"));
188 if (!doc) {
189 return {};
190 }
191
192 // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
193 Element* root = doc->root.get();
194 if (root == nullptr) {
195 diag_->Error(DiagMessage() << "Could not find the root element in the XML document");
196 return {};
197 }
198
199 std::string& xml_ns = root->namespace_uri;
200 if (!xml_ns.empty()) {
201 if (xml_ns != kAaptXmlNs) {
202 diag_->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
203 return {};
204 }
205
206 xml_ns.clear();
207 NamespaceVisitor visitor;
208 root->Accept(&visitor);
209 }
210
211 XmlActionExecutor executor;
212 XmlNodeAction& root_action = executor["post-process"];
213 XmlNodeAction& artifacts_action = root_action["artifacts"];
214 XmlNodeAction& groups_action = root_action["groups"];
215
216 PostProcessingConfiguration config;
217
218 // Helper to bind a static method to an action handler in the DOM executor.
219 auto bind_handler =
220 [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h)
221 -> XmlNodeAction::ActionFuncWithDiag {
222 return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
223 };
224
225 // Parse the artifact elements.
226 artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
227 artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
228
229 // Parse the different configuration groups.
230 groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
231 groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
232 groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
233 groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
234 groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
235 groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
236
237 if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
238 diag_->Error(DiagMessage() << "Could not process XML document");
239 return {};
240 }
241
242 // TODO: Validate all references in the configuration are valid. It should be safe to assume from
243 // this point on that any references from one section to another will be present.
244
245 return {config};
246 }
247
248 ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
__anon84c32c5c0302(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 249 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
250 Artifact artifact{};
251 for (const auto& attr : root_element->attributes) {
252 if (attr.name == "name") {
253 artifact.name = attr.value;
254 } else if (attr.name == "abi-group") {
255 artifact.abi_group = {attr.value};
256 } else if (attr.name == "screen-density-group") {
257 artifact.screen_density_group = {attr.value};
258 } else if (attr.name == "locale-group") {
259 artifact.locale_group = {attr.value};
260 } else if (attr.name == "android-sdk-group") {
261 artifact.android_sdk_group = {attr.value};
262 } else if (attr.name == "gl-texture-group") {
263 artifact.gl_texture_group = {attr.value};
264 } else if (attr.name == "device-feature-group") {
265 artifact.device_feature_group = {attr.value};
266 } else {
267 diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = "
268 << attr.value);
269 }
270 }
271 config->artifacts.push_back(artifact);
272 return true;
273 };
274
275 ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
__anon84c32c5c0402(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 276 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
277 for (auto& node : root_element->children) {
278 xml::Text* t;
279 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
280 config->artifact_format = TrimWhitespace(t->text).to_string();
281 break;
282 }
283 }
284 return true;
285 };
286
287 ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
__anon84c32c5c0502(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 288 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
289 std::string label = GetLabel(root_element, diag);
290 if (label.empty()) {
291 return false;
292 }
293
294 auto& group = config->abi_groups[label];
295 bool valid = true;
296
297 for (auto* child : root_element->GetChildElements()) {
298 if (child->name != "abi") {
299 diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
300 valid = false;
301 } else {
302 for (auto& node : child->children) {
303 xml::Text* t;
304 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
305 group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
306 break;
307 }
308 }
309 }
310 }
311
312 return valid;
313 };
314
315 ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
__anon84c32c5c0602(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 316 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
317 std::string label = GetLabel(root_element, diag);
318 if (label.empty()) {
319 return false;
320 }
321
322 auto& group = config->screen_density_groups[label];
323 bool valid = true;
324
325 for (auto* child : root_element->GetChildElements()) {
326 if (child->name != "screen-density") {
327 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
328 << child->name);
329 valid = false;
330 } else {
331 for (auto& node : child->children) {
332 xml::Text* t;
333 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
334 ConfigDescription config_descriptor;
335 const android::StringPiece& text = TrimWhitespace(t->text);
336 if (ConfigDescription::Parse(text, &config_descriptor)) {
337 // Copy the density with the minimum SDK version stripped out.
338 group.push_back(config_descriptor.CopyWithoutSdkVersion());
339 } else {
340 diag->Error(DiagMessage()
341 << "Could not parse config descriptor for screen-density: " << text);
342 valid = false;
343 }
344 break;
345 }
346 }
347 }
348 }
349
350 return valid;
351 };
352
353 ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
__anon84c32c5c0702(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 354 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
355 std::string label = GetLabel(root_element, diag);
356 if (label.empty()) {
357 return false;
358 }
359
360 auto& group = config->locale_groups[label];
361 bool valid = true;
362
363 for (auto* child : root_element->GetChildElements()) {
364 if (child->name != "locale") {
365 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
366 << child->name);
367 valid = false;
368 } else {
369 Locale entry;
370 for (const auto& attr : child->attributes) {
371 if (attr.name == "lang") {
372 entry.lang = {attr.value};
373 } else if (attr.name == "region") {
374 entry.region = {attr.value};
375 } else {
376 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
377 }
378 }
379 group.push_back(entry);
380 }
381 }
382
383 return valid;
384 };
385
386 ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
__anon84c32c5c0802(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 387 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
388 std::string label = GetLabel(root_element, diag);
389 if (label.empty()) {
390 return false;
391 }
392
393 auto& group = config->android_sdk_groups[label];
394 bool valid = true;
395
396 for (auto* child : root_element->GetChildElements()) {
397 if (child->name != "android-sdk") {
398 diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
399 valid = false;
400 } else {
401 AndroidSdk entry;
402 for (const auto& attr : child->attributes) {
403 if (attr.name == "minSdkVersion") {
404 entry.min_sdk_version = {attr.value};
405 } else if (attr.name == "targetSdkVersion") {
406 entry.target_sdk_version = {attr.value};
407 } else if (attr.name == "maxSdkVersion") {
408 entry.max_sdk_version = {attr.value};
409 } else {
410 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
411 }
412 }
413
414 // TODO: Fill in the manifest details when they are finalised.
415 for (auto node : child->GetChildElements()) {
416 if (node->name == "manifest") {
417 if (entry.manifest) {
418 diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
419 continue;
420 }
421 entry.manifest = {AndroidManifest()};
422 }
423 }
424
425 group.push_back(entry);
426 }
427 }
428
429 return valid;
430 };
431
432 ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
__anon84c32c5c0902(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 433 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
434 std::string label = GetLabel(root_element, diag);
435 if (label.empty()) {
436 return false;
437 }
438
439 auto& group = config->gl_texture_groups[label];
440 bool valid = true;
441
442 GlTexture result;
443 for (auto* child : root_element->GetChildElements()) {
444 if (child->name != "gl-texture") {
445 diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name);
446 valid = false;
447 } else {
448 for (const auto& attr : child->attributes) {
449 if (attr.name == "name") {
450 result.name = attr.value;
451 break;
452 }
453 }
454
455 for (auto* element : child->GetChildElements()) {
456 if (element->name != "texture-path") {
457 diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name);
458 valid = false;
459 continue;
460 }
461 for (auto& node : element->children) {
462 xml::Text* t;
463 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
464 result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
465 }
466 }
467 }
468 }
469 group.push_back(result);
470 }
471
472 return valid;
473 };
474
475 ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
__anon84c32c5c0a02(PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) 476 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
477 std::string label = GetLabel(root_element, diag);
478 if (label.empty()) {
479 return false;
480 }
481
482 auto& group = config->device_feature_groups[label];
483 bool valid = true;
484
485 for (auto* child : root_element->GetChildElements()) {
486 if (child->name != "supports-feature") {
487 diag->Error(DiagMessage() << "Unexpected root_element in device feature group: "
488 << child->name);
489 valid = false;
490 } else {
491 for (auto& node : child->children) {
492 xml::Text* t;
493 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
494 group.push_back(TrimWhitespace(t->text).to_string());
495 break;
496 }
497 }
498 }
499 }
500
501 return valid;
502 };
503
504 } // namespace aapt
505