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: 78 void Log(Level level, DiagMessageActual& actualMsg) override {} 79 }; 80 NoopDiagnostics noop_; 81 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: 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 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 */ 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 139 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. */ 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 180 ConfigurationParser::ConfigurationParser(std::string contents) 181 : contents_(std::move(contents)), 182 diag_(&noop_) { 183 } 184 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_ = 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_ = 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_ = 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_ = 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_ = 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_ = 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_ = 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_ = 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