blob: d051120b9445de640093810dd099fd885c08fc7f [file] [log] [blame]
Shane Farmer74cdea32017-05-12 16:22:36 -07001/*
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>
Shane Farmer57669432017-06-19 12:52:04 -070021#include <map>
Shane Farmer74cdea32017-05-12 16:22:36 -070022#include <memory>
23#include <utility>
24
Shane Farmerb1027272017-06-14 09:10:28 -070025#include <android-base/file.h>
Shane Farmer74cdea32017-05-12 16:22:36 -070026#include <android-base/logging.h>
27
28#include "ConfigDescription.h"
29#include "Diagnostics.h"
Shane Farmerb1027272017-06-14 09:10:28 -070030#include "io/File.h"
31#include "io/FileSystem.h"
Shane Farmer9f0e7f12017-06-22 12:26:44 -070032#include "util/Maybe.h"
Shane Farmer74cdea32017-05-12 16:22:36 -070033#include "util/Util.h"
34#include "xml/XmlActionExecutor.h"
35#include "xml/XmlDom.h"
36#include "xml/XmlUtil.h"
37
38namespace aapt {
39
40namespace {
41
42using ::aapt::configuration::Abi;
43using ::aapt::configuration::AndroidManifest;
44using ::aapt::configuration::AndroidSdk;
45using ::aapt::configuration::Artifact;
Shane Farmer280be342017-06-21 15:20:15 -070046using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer74cdea32017-05-12 16:22:36 -070047using ::aapt::configuration::GlTexture;
48using ::aapt::configuration::Group;
49using ::aapt::configuration::Locale;
Shane Farmerb1027272017-06-14 09:10:28 -070050using ::aapt::io::IFile;
51using ::aapt::io::RegularFile;
Shane Farmer74cdea32017-05-12 16:22:36 -070052using ::aapt::util::TrimWhitespace;
53using ::aapt::xml::Element;
54using ::aapt::xml::FindRootElement;
55using ::aapt::xml::NodeCast;
56using ::aapt::xml::XmlActionExecutor;
57using ::aapt::xml::XmlActionExecutorPolicy;
58using ::aapt::xml::XmlNodeAction;
Shane Farmerb1027272017-06-14 09:10:28 -070059using ::android::base::ReadFileToString;
Shane Farmer74cdea32017-05-12 16:22:36 -070060
Shane Farmer57669432017-06-19 12:52:04 -070061const std::unordered_map<std::string, Abi> kStringToAbiMap = {
62 {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
63 {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips},
64 {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal},
65};
66const std::map<Abi, std::string> kAbiToStringMap = {
67 {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"},
68 {Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"},
69 {Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"},
Shane Farmer74cdea32017-05-12 16:22:36 -070070};
71
72constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
73
74/** A default noop diagnostics context. */
75class NoopDiagnostics : public IDiagnostics {
76 public:
77 void Log(Level level, DiagMessageActual& actualMsg) override {}
78};
79NoopDiagnostics noop_;
80
81std::string GetLabel(const Element* element, IDiagnostics* diag) {
82 std::string label;
83 for (const auto& attr : element->attributes) {
84 if (attr.name == "label") {
85 label = attr.value;
86 break;
87 }
88 }
89
90 if (label.empty()) {
91 diag->Error(DiagMessage() << "No label found for element " << element->name);
92 }
93 return label;
94}
95
96/** XML node visitor that removes all of the namespace URIs from the node and all children. */
97class NamespaceVisitor : public xml::Visitor {
98 public:
99 void Visit(xml::Element* node) override {
100 node->namespace_uri.clear();
101 VisitChildren(node);
102 }
103};
104
105} // namespace
106
Shane Farmer57669432017-06-19 12:52:04 -0700107namespace configuration {
Shane Farmerb1027272017-06-14 09:10:28 -0700108
Shane Farmer57669432017-06-19 12:52:04 -0700109const std::string& AbiToString(Abi abi) {
110 return kAbiToStringMap.find(abi)->second;
111}
112
Shane Farmer9f0e7f12017-06-22 12:26:44 -0700113/**
114 * Attempts to replace the placeholder in the name string with the provided value. Returns true on
115 * success, or false if the either the placeholder is not found in the name, or the value is not
116 * present and the placeholder was.
117 */
118static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value,
119 std::string* name, IDiagnostics* diag) {
120 size_t offset = name->find(placeholder);
121 if (value) {
122 if (offset == std::string::npos) {
123 diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder);
124 return false;
125 }
126 name->replace(offset, placeholder.length(), value.value());
127 return true;
128 }
129
130 // Make sure the placeholder was not present if the desired value was not present.
131 bool result = (offset == std::string::npos);
132 if (!result) {
133 diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder);
134 }
135 return result;
136}
137
138Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const {
139 std::string result = format;
140
141 if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) {
142 return {};
143 }
144
145 if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) {
146 return {};
147 }
148
149 if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) {
150 return {};
151 }
152
153 if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) {
154 return {};
155 }
156
157 if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) {
158 return {};
159 }
160
161 if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) {
162 return {};
163 }
164
165 return result;
166}
167
Shane Farmer57669432017-06-19 12:52:04 -0700168} // namespace configuration
Shane Farmerb1027272017-06-14 09:10:28 -0700169
170/** Returns a ConfigurationParser for the file located at the provided path. */
171Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
172 std::string contents;
173 if (!ReadFileToString(path, &contents, true)) {
174 return {};
175 }
176 return ConfigurationParser(contents);
177}
178
Shane Farmer74cdea32017-05-12 16:22:36 -0700179ConfigurationParser::ConfigurationParser(std::string contents)
180 : contents_(std::move(contents)),
181 diag_(&noop_) {
182}
183
Shane Farmer280be342017-06-21 15:20:15 -0700184Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() {
Shane Farmer74cdea32017-05-12 16:22:36 -0700185 std::istringstream in(contents_);
186
187 auto 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 auto* root = FindRootElement(doc.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
Shane Farmer280be342017-06-21 15:20:15 -0700216 PostProcessingConfiguration config;
Shane Farmer74cdea32017-05-12 16:22:36 -0700217
218 // Helper to bind a static method to an action handler in the DOM executor.
Shane Farmer280be342017-06-21 15:20:15 -0700219 auto bind_handler =
220 [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h)
Shane Farmer74cdea32017-05-12 16:22:36 -0700221 -> 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
Shane Farmer57669432017-06-19 12:52:04 -0700242 // 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
Shane Farmer74cdea32017-05-12 16:22:36 -0700245 return {config};
246}
247
248ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700249 [](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};
Shane Farmer74cdea32017-05-12 16:22:36 -0700274
275ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700276 [](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
287ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
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) {
Shane Farmer74cdea32017-05-12 16:22:36 -0700303 xml::Text* t;
304 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
Shane Farmer280be342017-06-21 15:20:15 -0700305 group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
Shane Farmer74cdea32017-05-12 16:22:36 -0700306 break;
307 }
308 }
Shane Farmer280be342017-06-21 15:20:15 -0700309 }
310 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700311
Shane Farmer280be342017-06-21 15:20:15 -0700312 return valid;
313};
Shane Farmer74cdea32017-05-12 16:22:36 -0700314
315ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700316 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
317 std::string label = GetLabel(root_element, diag);
318 if (label.empty()) {
319 return false;
320 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700321
Shane Farmer280be342017-06-21 15:20:15 -0700322 auto& group = config->screen_density_groups[label];
323 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700324
Shane Farmer280be342017-06-21 15:20:15 -0700325 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;
Shane Farmer74cdea32017-05-12 16:22:36 -0700343 }
Shane Farmer280be342017-06-21 15:20:15 -0700344 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700345 }
346 }
Shane Farmer280be342017-06-21 15:20:15 -0700347 }
348 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700349
Shane Farmer280be342017-06-21 15:20:15 -0700350 return valid;
351};
Shane Farmer74cdea32017-05-12 16:22:36 -0700352
353ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700354 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
355 std::string label = GetLabel(root_element, diag);
356 if (label.empty()) {
357 return false;
358 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700359
Shane Farmer280be342017-06-21 15:20:15 -0700360 auto& group = config->locale_groups[label];
361 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700362
Shane Farmer280be342017-06-21 15:20:15 -0700363 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};
Shane Farmer74cdea32017-05-12 16:22:36 -0700375 } else {
Shane Farmer280be342017-06-21 15:20:15 -0700376 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
Shane Farmer74cdea32017-05-12 16:22:36 -0700377 }
378 }
Shane Farmer280be342017-06-21 15:20:15 -0700379 group.push_back(entry);
380 }
381 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700382
Shane Farmer280be342017-06-21 15:20:15 -0700383 return valid;
384};
Shane Farmer74cdea32017-05-12 16:22:36 -0700385
386ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700387 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
388 std::string label = GetLabel(root_element, diag);
389 if (label.empty()) {
390 return false;
391 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700392
Shane Farmer280be342017-06-21 15:20:15 -0700393 auto& group = config->android_sdk_groups[label];
394 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700395
Shane Farmer280be342017-06-21 15:20:15 -0700396 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};
Shane Farmer74cdea32017-05-12 16:22:36 -0700409 } else {
Shane Farmer280be342017-06-21 15:20:15 -0700410 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
Shane Farmer74cdea32017-05-12 16:22:36 -0700411 }
412 }
413
Shane Farmer280be342017-06-21 15:20:15 -0700414 // 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};
Shane Farmer74cdea32017-05-12 16:22:36 -0700431
432ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700433 [](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 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700453 }
454
Shane Farmer280be342017-06-21 15:20:15 -0700455 for (auto* element : child->GetChildElements()) {
456 if (element->name != "texture-path") {
457 diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name);
Shane Farmer74cdea32017-05-12 16:22:36 -0700458 valid = false;
Shane Farmer280be342017-06-21 15:20:15 -0700459 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());
Shane Farmer74cdea32017-05-12 16:22:36 -0700465 }
466 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700467 }
Shane Farmer280be342017-06-21 15:20:15 -0700468 }
469 group.push_back(result);
470 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700471
Shane Farmer280be342017-06-21 15:20:15 -0700472 return valid;
473};
Shane Farmer74cdea32017-05-12 16:22:36 -0700474
475ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700476 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
477 std::string label = GetLabel(root_element, diag);
478 if (label.empty()) {
479 return false;
480 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700481
Shane Farmer280be342017-06-21 15:20:15 -0700482 auto& group = config->device_feature_groups[label];
483 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700484
Shane Farmer280be342017-06-21 15:20:15 -0700485 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;
Shane Farmer74cdea32017-05-12 16:22:36 -0700496 }
497 }
Shane Farmer280be342017-06-21 15:20:15 -0700498 }
499 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700500
Shane Farmer280be342017-06-21 15:20:15 -0700501 return valid;
502};
Shane Farmer74cdea32017-05-12 16:22:36 -0700503
504} // namespace aapt