blob: 0b6743c4c3b648465b9edf6e1fe84a614077cdd3 [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;
Shane Farmer280be342017-06-21 15:20:15 -070045using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer74cdea32017-05-12 16:22:36 -070046using ::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
Shane Farmer280be342017-06-21 15:20:15 -0700128Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() {
Shane Farmer74cdea32017-05-12 16:22:36 -0700129 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
Shane Farmer280be342017-06-21 15:20:15 -0700160 PostProcessingConfiguration config;
Shane Farmer74cdea32017-05-12 16:22:36 -0700161
162 // Helper to bind a static method to an action handler in the DOM executor.
Shane Farmer280be342017-06-21 15:20:15 -0700163 auto bind_handler =
164 [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h)
Shane Farmer74cdea32017-05-12 16:22:36 -0700165 -> XmlNodeAction::ActionFuncWithDiag {
166 return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
167 };
168
169 // Parse the artifact elements.
170 artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
171 artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
172
173 // Parse the different configuration groups.
174 groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
175 groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
176 groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
177 groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
178 groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
179 groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
180
181 if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
182 diag_->Error(DiagMessage() << "Could not process XML document");
183 return {};
184 }
185
Shane Farmer57669432017-06-19 12:52:04 -0700186 // TODO: Validate all references in the configuration are valid. It should be safe to assume from
187 // this point on that any references from one section to another will be present.
188
Shane Farmer74cdea32017-05-12 16:22:36 -0700189 return {config};
190}
191
192ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700193 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
194 Artifact artifact{};
195 for (const auto& attr : root_element->attributes) {
196 if (attr.name == "name") {
197 artifact.name = attr.value;
198 } else if (attr.name == "abi-group") {
199 artifact.abi_group = {attr.value};
200 } else if (attr.name == "screen-density-group") {
201 artifact.screen_density_group = {attr.value};
202 } else if (attr.name == "locale-group") {
203 artifact.locale_group = {attr.value};
204 } else if (attr.name == "android-sdk-group") {
205 artifact.android_sdk_group = {attr.value};
206 } else if (attr.name == "gl-texture-group") {
207 artifact.gl_texture_group = {attr.value};
208 } else if (attr.name == "device-feature-group") {
209 artifact.device_feature_group = {attr.value};
210 } else {
211 diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = "
212 << attr.value);
213 }
214 }
215 config->artifacts.push_back(artifact);
216 return true;
217};
Shane Farmer74cdea32017-05-12 16:22:36 -0700218
219ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700220 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
221 for (auto& node : root_element->children) {
222 xml::Text* t;
223 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
224 config->artifact_format = TrimWhitespace(t->text).to_string();
225 break;
226 }
227 }
228 return true;
229};
230
231ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
232 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
233 std::string label = GetLabel(root_element, diag);
234 if (label.empty()) {
235 return false;
236 }
237
238 auto& group = config->abi_groups[label];
239 bool valid = true;
240
241 for (auto* child : root_element->GetChildElements()) {
242 if (child->name != "abi") {
243 diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
244 valid = false;
245 } else {
246 for (auto& node : child->children) {
Shane Farmer74cdea32017-05-12 16:22:36 -0700247 xml::Text* t;
248 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
Shane Farmer280be342017-06-21 15:20:15 -0700249 group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
Shane Farmer74cdea32017-05-12 16:22:36 -0700250 break;
251 }
252 }
Shane Farmer280be342017-06-21 15:20:15 -0700253 }
254 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700255
Shane Farmer280be342017-06-21 15:20:15 -0700256 return valid;
257};
Shane Farmer74cdea32017-05-12 16:22:36 -0700258
259ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700260 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
261 std::string label = GetLabel(root_element, diag);
262 if (label.empty()) {
263 return false;
264 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700265
Shane Farmer280be342017-06-21 15:20:15 -0700266 auto& group = config->screen_density_groups[label];
267 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700268
Shane Farmer280be342017-06-21 15:20:15 -0700269 for (auto* child : root_element->GetChildElements()) {
270 if (child->name != "screen-density") {
271 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
272 << child->name);
273 valid = false;
274 } else {
275 for (auto& node : child->children) {
276 xml::Text* t;
277 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
278 ConfigDescription config_descriptor;
279 const android::StringPiece& text = TrimWhitespace(t->text);
280 if (ConfigDescription::Parse(text, &config_descriptor)) {
281 // Copy the density with the minimum SDK version stripped out.
282 group.push_back(config_descriptor.CopyWithoutSdkVersion());
283 } else {
284 diag->Error(DiagMessage()
285 << "Could not parse config descriptor for screen-density: " << text);
286 valid = false;
Shane Farmer74cdea32017-05-12 16:22:36 -0700287 }
Shane Farmer280be342017-06-21 15:20:15 -0700288 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700289 }
290 }
Shane Farmer280be342017-06-21 15:20:15 -0700291 }
292 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700293
Shane Farmer280be342017-06-21 15:20:15 -0700294 return valid;
295};
Shane Farmer74cdea32017-05-12 16:22:36 -0700296
297ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700298 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
299 std::string label = GetLabel(root_element, diag);
300 if (label.empty()) {
301 return false;
302 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700303
Shane Farmer280be342017-06-21 15:20:15 -0700304 auto& group = config->locale_groups[label];
305 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700306
Shane Farmer280be342017-06-21 15:20:15 -0700307 for (auto* child : root_element->GetChildElements()) {
308 if (child->name != "locale") {
309 diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
310 << child->name);
311 valid = false;
312 } else {
313 Locale entry;
314 for (const auto& attr : child->attributes) {
315 if (attr.name == "lang") {
316 entry.lang = {attr.value};
317 } else if (attr.name == "region") {
318 entry.region = {attr.value};
Shane Farmer74cdea32017-05-12 16:22:36 -0700319 } else {
Shane Farmer280be342017-06-21 15:20:15 -0700320 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
Shane Farmer74cdea32017-05-12 16:22:36 -0700321 }
322 }
Shane Farmer280be342017-06-21 15:20:15 -0700323 group.push_back(entry);
324 }
325 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700326
Shane Farmer280be342017-06-21 15:20:15 -0700327 return valid;
328};
Shane Farmer74cdea32017-05-12 16:22:36 -0700329
330ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700331 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
332 std::string label = GetLabel(root_element, diag);
333 if (label.empty()) {
334 return false;
335 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700336
Shane Farmer280be342017-06-21 15:20:15 -0700337 auto& group = config->android_sdk_groups[label];
338 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700339
Shane Farmer280be342017-06-21 15:20:15 -0700340 for (auto* child : root_element->GetChildElements()) {
341 if (child->name != "android-sdk") {
342 diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
343 valid = false;
344 } else {
345 AndroidSdk entry;
346 for (const auto& attr : child->attributes) {
347 if (attr.name == "minSdkVersion") {
348 entry.min_sdk_version = {attr.value};
349 } else if (attr.name == "targetSdkVersion") {
350 entry.target_sdk_version = {attr.value};
351 } else if (attr.name == "maxSdkVersion") {
352 entry.max_sdk_version = {attr.value};
Shane Farmer74cdea32017-05-12 16:22:36 -0700353 } else {
Shane Farmer280be342017-06-21 15:20:15 -0700354 diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
Shane Farmer74cdea32017-05-12 16:22:36 -0700355 }
356 }
357
Shane Farmer280be342017-06-21 15:20:15 -0700358 // TODO: Fill in the manifest details when they are finalised.
359 for (auto node : child->GetChildElements()) {
360 if (node->name == "manifest") {
361 if (entry.manifest) {
362 diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
363 continue;
364 }
365 entry.manifest = {AndroidManifest()};
366 }
367 }
368
369 group.push_back(entry);
370 }
371 }
372
373 return valid;
374};
Shane Farmer74cdea32017-05-12 16:22:36 -0700375
376ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700377 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
378 std::string label = GetLabel(root_element, diag);
379 if (label.empty()) {
380 return false;
381 }
382
383 auto& group = config->gl_texture_groups[label];
384 bool valid = true;
385
386 GlTexture result;
387 for (auto* child : root_element->GetChildElements()) {
388 if (child->name != "gl-texture") {
389 diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name);
390 valid = false;
391 } else {
392 for (const auto& attr : child->attributes) {
393 if (attr.name == "name") {
394 result.name = attr.value;
395 break;
396 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700397 }
398
Shane Farmer280be342017-06-21 15:20:15 -0700399 for (auto* element : child->GetChildElements()) {
400 if (element->name != "texture-path") {
401 diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name);
Shane Farmer74cdea32017-05-12 16:22:36 -0700402 valid = false;
Shane Farmer280be342017-06-21 15:20:15 -0700403 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());
Shane Farmer74cdea32017-05-12 16:22:36 -0700409 }
410 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700411 }
Shane Farmer280be342017-06-21 15:20:15 -0700412 }
413 group.push_back(result);
414 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700415
Shane Farmer280be342017-06-21 15:20:15 -0700416 return valid;
417};
Shane Farmer74cdea32017-05-12 16:22:36 -0700418
419ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
Shane Farmer280be342017-06-21 15:20:15 -0700420 [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
421 std::string label = GetLabel(root_element, diag);
422 if (label.empty()) {
423 return false;
424 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700425
Shane Farmer280be342017-06-21 15:20:15 -0700426 auto& group = config->device_feature_groups[label];
427 bool valid = true;
Shane Farmer74cdea32017-05-12 16:22:36 -0700428
Shane Farmer280be342017-06-21 15:20:15 -0700429 for (auto* child : root_element->GetChildElements()) {
430 if (child->name != "supports-feature") {
431 diag->Error(DiagMessage() << "Unexpected root_element in device feature group: "
432 << child->name);
433 valid = false;
434 } else {
435 for (auto& node : child->children) {
436 xml::Text* t;
437 if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
438 group.push_back(TrimWhitespace(t->text).to_string());
439 break;
Shane Farmer74cdea32017-05-12 16:22:36 -0700440 }
441 }
Shane Farmer280be342017-06-21 15:20:15 -0700442 }
443 }
Shane Farmer74cdea32017-05-12 16:22:36 -0700444
Shane Farmer280be342017-06-21 15:20:15 -0700445 return valid;
446};
Shane Farmer74cdea32017-05-12 16:22:36 -0700447
448} // namespace aapt