blob: 555cb35c0bb9a34272d7a0aa479c366d2e96b68e [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 Farmer74cdea32017-05-12 16:22:36 -070032#include "util/Util.h"
33#include "xml/XmlActionExecutor.h"
34#include "xml/XmlDom.h"
35#include "xml/XmlUtil.h"
36
37namespace aapt {
38
39namespace {
40
41using ::aapt::configuration::Abi;
42using ::aapt::configuration::AndroidManifest;
43using ::aapt::configuration::AndroidSdk;
44using ::aapt::configuration::Artifact;
45using ::aapt::configuration::Configuration;
46using ::aapt::configuration::GlTexture;
47using ::aapt::configuration::Group;
48using ::aapt::configuration::Locale;
Shane Farmerb1027272017-06-14 09:10:28 -070049using ::aapt::io::IFile;
50using ::aapt::io::RegularFile;
Shane Farmer74cdea32017-05-12 16:22:36 -070051using ::aapt::util::TrimWhitespace;
52using ::aapt::xml::Element;
53using ::aapt::xml::FindRootElement;
54using ::aapt::xml::NodeCast;
55using ::aapt::xml::XmlActionExecutor;
56using ::aapt::xml::XmlActionExecutorPolicy;
57using ::aapt::xml::XmlNodeAction;
Shane Farmerb1027272017-06-14 09:10:28 -070058using ::android::base::ReadFileToString;
Shane Farmer74cdea32017-05-12 16:22:36 -070059
Shane Farmer57669432017-06-19 12:52:04 -070060const std::unordered_map<std::string, Abi> kStringToAbiMap = {
61 {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
62 {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips},
63 {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal},
64};
65const std::map<Abi, std::string> kAbiToStringMap = {
66 {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"},
67 {Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"},
68 {Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"},
Shane Farmer74cdea32017-05-12 16:22:36 -070069};
70
71constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
72
73/** A default noop diagnostics context. */
74class NoopDiagnostics : public IDiagnostics {
75 public:
76 void Log(Level level, DiagMessageActual& actualMsg) override {}
77};
78NoopDiagnostics noop_;
79
80std::string GetLabel(const Element* element, IDiagnostics* diag) {
81 std::string label;
82 for (const auto& attr : element->attributes) {
83 if (attr.name == "label") {
84 label = attr.value;
85 break;
86 }
87 }
88
89 if (label.empty()) {
90 diag->Error(DiagMessage() << "No label found for element " << element->name);
91 }
92 return label;
93}
94
95/** XML node visitor that removes all of the namespace URIs from the node and all children. */
96class NamespaceVisitor : public xml::Visitor {
97 public:
98 void Visit(xml::Element* node) override {
99 node->namespace_uri.clear();
100 VisitChildren(node);
101 }
102};
103
104} // namespace
105
Shane Farmer57669432017-06-19 12:52:04 -0700106namespace configuration {
Shane Farmerb1027272017-06-14 09:10:28 -0700107
Shane Farmer57669432017-06-19 12:52:04 -0700108const std::string& AbiToString(Abi abi) {
109 return kAbiToStringMap.find(abi)->second;
110}
111
112} // namespace configuration
Shane Farmerb1027272017-06-14 09:10:28 -0700113
114/** Returns a ConfigurationParser for the file located at the provided path. */
115Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
116 std::string contents;
117 if (!ReadFileToString(path, &contents, true)) {
118 return {};
119 }
120 return ConfigurationParser(contents);
121}
122
Shane Farmer74cdea32017-05-12 16:22:36 -0700123ConfigurationParser::ConfigurationParser(std::string contents)
124 : contents_(std::move(contents)),
125 diag_(&noop_) {
126}
127
128Maybe<Configuration> ConfigurationParser::Parse() {
129 std::istringstream in(contents_);
130
131 auto doc = xml::Inflate(&in, diag_, Source("config.xml"));
132 if (!doc) {
133 return {};
134 }
135
136 // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
137 auto* root = FindRootElement(doc.get());
138 if (root == nullptr) {
139 diag_->Error(DiagMessage() << "Could not find the root element in the XML document");
140 return {};
141 }
142
143 std::string& xml_ns = root->namespace_uri;
144 if (!xml_ns.empty()) {
145 if (xml_ns != kAaptXmlNs) {
146 diag_->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
147 return {};
148 }
149
150 xml_ns.clear();
151 NamespaceVisitor visitor;
152 root->Accept(&visitor);
153 }
154
155 XmlActionExecutor executor;
156 XmlNodeAction& root_action = executor["post-process"];
157 XmlNodeAction& artifacts_action = root_action["artifacts"];
158 XmlNodeAction& groups_action = root_action["groups"];
159
160 Configuration config;
161
162 // Helper to bind a static method to an action handler in the DOM executor.
163 auto bind_handler = [&config](std::function<bool(Configuration*, Element*, IDiagnostics*)> h)
164 -> XmlNodeAction::ActionFuncWithDiag {
165 return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
166 };
167
168 // Parse the artifact elements.
169 artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
170 artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
171
172 // Parse the different configuration groups.
173 groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
174 groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
175 groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
176 groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
177 groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
178 groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
179
180 if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
181 diag_->Error(DiagMessage() << "Could not process XML document");
182 return {};
183 }
184
Shane Farmer57669432017-06-19 12:52:04 -0700185 // TODO: Validate all references in the configuration are valid. It should be safe to assume from
186 // this point on that any references from one section to another will be present.
187
Shane Farmer74cdea32017-05-12 16:22:36 -0700188 return {config};
189}
190
191ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
192 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
193 Artifact artifact{};
194 for (const auto& attr : root_element->attributes) {
195 if (attr.name == "name") {
196 artifact.name = attr.value;
197 } else if (attr.name == "abi-group") {
198 artifact.abi_group = {attr.value};
199 } else if (attr.name == "screen-density-group") {
200 artifact.screen_density_group = {attr.value};
201 } else if (attr.name == "locale-group") {
202 artifact.locale_group = {attr.value};
203 } else if (attr.name == "android-sdk-group") {
204 artifact.android_sdk_group = {attr.value};
205 } else if (attr.name == "gl-texture-group") {
206 artifact.gl_texture_group = {attr.value};
207 } else if (attr.name == "device-feature-group") {
208 artifact.device_feature_group = {attr.value};
209 } else {
210 diag->Note(
211 DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
212 }
213 }
Shane Farmer57669432017-06-19 12:52:04 -0700214 config->artifacts.push_back(artifact);
Shane Farmer74cdea32017-05-12 16:22:36 -0700215 return true;
216 };
217
218ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
219 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
220 for (auto& node : root_element->children) {
221 xml::Text* t;
222 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
223 config->artifact_format = TrimWhitespace(t->text).to_string();
224 break;
225 }
226 }
227 return true;
228 };
229
230ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
231 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
232 std::string label = GetLabel(root_element, diag);
233 if (label.empty()) {
234 return false;
235 }
236
237 auto& group = config->abi_groups[label];
238 bool valid = true;
239
240 for (auto* child : root_element->GetChildElements()) {
241 if (child->name != "abi") {
242 diag->Error(
243 DiagMessage() << "Unexpected element in ABI group: " << child->name);
244 valid = false;
245 } else {
246 for (auto& node : child->children) {
247 xml::Text* t;
248 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
Shane Farmer57669432017-06-19 12:52:04 -0700249 group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
Shane Farmer74cdea32017-05-12 16:22:36 -0700250 break;
251 }
252 }
253 }
254 }
255
256 return valid;
257 };
258
259ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
260 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
261 std::string label = GetLabel(root_element, diag);
262 if (label.empty()) {
263 return false;
264 }
265
266 auto& group = config->screen_density_groups[label];
267 bool valid = true;
268
269 for (auto* child : root_element->GetChildElements()) {
270 if (child->name != "screen-density") {
271 diag->Error(
272 DiagMessage() << "Unexpected root_element in screen density group: "
273 << child->name);
274 valid = false;
275 } else {
276 for (auto& node : child->children) {
277 xml::Text* t;
278 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
279 ConfigDescription config_descriptor;
280 const android::StringPiece& text = TrimWhitespace(t->text);
281 if (ConfigDescription::Parse(text, &config_descriptor)) {
282 // Copy the density with the minimum SDK version stripped out.
283 group.push_back(config_descriptor.CopyWithoutSdkVersion());
284 } else {
285 diag->Error(
286 DiagMessage() << "Could not parse config descriptor for screen-density: "
287 << text);
288 valid = false;
289 }
290 break;
291 }
292 }
293 }
294 }
295
296 return valid;
297 };
298
299ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
300 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
301 std::string label = GetLabel(root_element, diag);
302 if (label.empty()) {
303 return false;
304 }
305
306 auto& group = config->locale_groups[label];
307 bool valid = true;
308
309 for (auto* child : root_element->GetChildElements()) {
310 if (child->name != "locale") {
311 diag->Error(
312 DiagMessage() << "Unexpected root_element in screen density group: "
313 << child->name);
314 valid = false;
315 } else {
316 Locale entry;
317 for (const auto& attr : child->attributes) {
318 if (attr.name == "lang") {
319 entry.lang = {attr.value};
320 } else if (attr.name == "region") {
321 entry.region = {attr.value};
322 } else {
323 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
324 << " = " << attr.value);
325 }
326 }
327 group.push_back(entry);
328 }
329 }
330
331 return valid;
332 };
333
334ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
335 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
336 std::string label = GetLabel(root_element, diag);
337 if (label.empty()) {
338 return false;
339 }
340
341 auto& group = config->android_sdk_groups[label];
342 bool valid = true;
343
344 for (auto* child : root_element->GetChildElements()) {
345 if (child->name != "android-sdk") {
346 diag->Error(
347 DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
348 valid = false;
349 } else {
350 AndroidSdk entry;
351 for (const auto& attr : child->attributes) {
352 if (attr.name == "minSdkVersion") {
353 entry.min_sdk_version = {attr.value};
354 } else if (attr.name == "targetSdkVersion") {
355 entry.target_sdk_version = {attr.value};
356 } else if (attr.name == "maxSdkVersion") {
357 entry.max_sdk_version = {attr.value};
358 } else {
359 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
360 << " = " << attr.value);
361 }
362 }
363
364 // TODO: Fill in the manifest details when they are finalised.
365 for (auto node : child->GetChildElements()) {
366 if (node->name == "manifest") {
367 if (entry.manifest) {
368 diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
369 continue;
370 }
371 entry.manifest = {AndroidManifest()};
372 }
373 }
374
375 group.push_back(entry);
376 }
377 }
378
379 return valid;
380 };
381
382ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
383 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
384 std::string label = GetLabel(root_element, diag);
385 if (label.empty()) {
386 return false;
387 }
388
389 auto& group = config->gl_texture_groups[label];
390 bool valid = true;
391
392 GlTexture result;
393 for (auto* child : root_element->GetChildElements()) {
394 if (child->name != "gl-texture") {
395 diag->Error(
396 DiagMessage() << "Unexpected element in GL texture group: "
397 << child->name);
398 valid = false;
399 } else {
400 for (const auto& attr : child->attributes) {
401 if (attr.name == "name") {
402 result.name = attr.value;
403 break;
404 }
405 }
406
407 for (auto* element : child->GetChildElements()) {
408 if (element->name != "texture-path") {
409 diag->Error(
410 DiagMessage() << "Unexpected element in gl-texture element: "
411 << child->name);
412 valid = false;
413 continue;
414 }
415 for (auto& node : element->children) {
416 xml::Text* t;
417 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
418 result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
419 }
420 }
421 }
422 }
423 group.push_back(result);
424 }
425
426 return valid;
427 };
428
429ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
430 [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
431 std::string label = GetLabel(root_element, diag);
432 if (label.empty()) {
433 return false;
434 }
435
436 auto& group = config->device_feature_groups[label];
437 bool valid = true;
438
439 for (auto* child : root_element->GetChildElements()) {
440 if (child->name != "supports-feature") {
441 diag->Error(
442 DiagMessage() << "Unexpected root_element in device feature group: "
443 << child->name);
444 valid = false;
445 } else {
446 for (auto& node : child->children) {
447 xml::Text* t;
448 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
449 group.push_back(TrimWhitespace(t->text).to_string());
450 break;
451 }
452 }
453 }
454 }
455
456 return valid;
457 };
458
459} // namespace aapt