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