Home | History | Annotate | Download | only in configuration
      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