Merge "AAPT2: XML configuration file parser."
This commit is contained in:
committed by
Android (Google) Code Review
commit
95cad98ad5
@@ -84,6 +84,7 @@ cc_library_host_static {
|
||||
"compile/PseudolocaleGenerator.cpp",
|
||||
"compile/Pseudolocalizer.cpp",
|
||||
"compile/XmlIdCollector.cpp",
|
||||
"configuration/ConfigurationParser.cpp",
|
||||
"filter/ConfigFilter.cpp",
|
||||
"flatten/Archive.cpp",
|
||||
"flatten/TableFlattener.cpp",
|
||||
@@ -160,7 +161,10 @@ cc_library_host_shared {
|
||||
cc_test_host {
|
||||
name: "aapt2_tests",
|
||||
srcs: ["**/*_test.cpp"],
|
||||
static_libs: ["libaapt2", "libgmock"],
|
||||
static_libs: [
|
||||
"libaapt2",
|
||||
"libgmock",
|
||||
],
|
||||
defaults: ["aapt_defaults"],
|
||||
}
|
||||
|
||||
|
||||
432
tools/aapt2/configuration/ConfigurationParser.cpp
Normal file
432
tools/aapt2/configuration/ConfigurationParser.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "configuration/ConfigurationParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "util/Util.h"
|
||||
#include "xml/XmlActionExecutor.h"
|
||||
#include "xml/XmlDom.h"
|
||||
#include "xml/XmlUtil.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::aapt::configuration::Abi;
|
||||
using ::aapt::configuration::AndroidManifest;
|
||||
using ::aapt::configuration::AndroidSdk;
|
||||
using ::aapt::configuration::Artifact;
|
||||
using ::aapt::configuration::Configuration;
|
||||
using ::aapt::configuration::GlTexture;
|
||||
using ::aapt::configuration::Group;
|
||||
using ::aapt::configuration::Locale;
|
||||
using ::aapt::util::TrimWhitespace;
|
||||
using ::aapt::xml::Element;
|
||||
using ::aapt::xml::FindRootElement;
|
||||
using ::aapt::xml::NodeCast;
|
||||
using ::aapt::xml::XmlActionExecutor;
|
||||
using ::aapt::xml::XmlActionExecutorPolicy;
|
||||
using ::aapt::xml::XmlNodeAction;
|
||||
|
||||
const std::unordered_map<std::string, Abi> kAbiMap = {
|
||||
{"armeabi", Abi::kArmeV6},
|
||||
{"armeabi-v7a", Abi::kArmV7a},
|
||||
{"arm64-v8a", Abi::kArm64V8a},
|
||||
{"x86", Abi::kX86},
|
||||
{"x86_64", Abi::kX86_64},
|
||||
{"mips", Abi::kMips},
|
||||
{"mips64", Abi::kMips64},
|
||||
{"universal", Abi::kUniversal},
|
||||
};
|
||||
|
||||
constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
|
||||
|
||||
/** A default noop diagnostics context. */
|
||||
class NoopDiagnostics : public IDiagnostics {
|
||||
public:
|
||||
void Log(Level level, DiagMessageActual& actualMsg) override {}
|
||||
};
|
||||
NoopDiagnostics noop_;
|
||||
|
||||
std::string GetLabel(const Element* element, IDiagnostics* diag) {
|
||||
std::string label;
|
||||
for (const auto& attr : element->attributes) {
|
||||
if (attr.name == "label") {
|
||||
label = attr.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (label.empty()) {
|
||||
diag->Error(DiagMessage() << "No label found for element " << element->name);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
/** XML node visitor that removes all of the namespace URIs from the node and all children. */
|
||||
class NamespaceVisitor : public xml::Visitor {
|
||||
public:
|
||||
void Visit(xml::Element* node) override {
|
||||
node->namespace_uri.clear();
|
||||
VisitChildren(node);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigurationParser::ConfigurationParser(std::string contents)
|
||||
: contents_(std::move(contents)),
|
||||
diag_(&noop_) {
|
||||
}
|
||||
|
||||
Maybe<Configuration> ConfigurationParser::Parse() {
|
||||
std::istringstream in(contents_);
|
||||
|
||||
auto doc = xml::Inflate(&in, diag_, Source("config.xml"));
|
||||
if (!doc) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
|
||||
auto* root = FindRootElement(doc.get());
|
||||
if (root == nullptr) {
|
||||
diag_->Error(DiagMessage() << "Could not find the root element in the XML document");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string& xml_ns = root->namespace_uri;
|
||||
if (!xml_ns.empty()) {
|
||||
if (xml_ns != kAaptXmlNs) {
|
||||
diag_->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
|
||||
return {};
|
||||
}
|
||||
|
||||
xml_ns.clear();
|
||||
NamespaceVisitor visitor;
|
||||
root->Accept(&visitor);
|
||||
}
|
||||
|
||||
XmlActionExecutor executor;
|
||||
XmlNodeAction& root_action = executor["post-process"];
|
||||
XmlNodeAction& artifacts_action = root_action["artifacts"];
|
||||
XmlNodeAction& groups_action = root_action["groups"];
|
||||
|
||||
Configuration config;
|
||||
|
||||
// Helper to bind a static method to an action handler in the DOM executor.
|
||||
auto bind_handler = [&config](std::function<bool(Configuration*, Element*, IDiagnostics*)> h)
|
||||
-> XmlNodeAction::ActionFuncWithDiag {
|
||||
return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
|
||||
};
|
||||
|
||||
// Parse the artifact elements.
|
||||
artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
|
||||
artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
|
||||
|
||||
// Parse the different configuration groups.
|
||||
groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
|
||||
groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
|
||||
groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
|
||||
groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
|
||||
groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
|
||||
groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
|
||||
|
||||
if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
|
||||
diag_->Error(DiagMessage() << "Could not process XML document");
|
||||
return {};
|
||||
}
|
||||
|
||||
return {config};
|
||||
}
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
Artifact artifact{};
|
||||
for (const auto& attr : root_element->attributes) {
|
||||
if (attr.name == "name") {
|
||||
artifact.name = attr.value;
|
||||
} else if (attr.name == "abi-group") {
|
||||
artifact.abi_group = {attr.value};
|
||||
} else if (attr.name == "screen-density-group") {
|
||||
artifact.screen_density_group = {attr.value};
|
||||
} else if (attr.name == "locale-group") {
|
||||
artifact.locale_group = {attr.value};
|
||||
} else if (attr.name == "android-sdk-group") {
|
||||
artifact.android_sdk_group = {attr.value};
|
||||
} else if (attr.name == "gl-texture-group") {
|
||||
artifact.gl_texture_group = {attr.value};
|
||||
} else if (attr.name == "device-feature-group") {
|
||||
artifact.device_feature_group = {attr.value};
|
||||
} else {
|
||||
diag->Note(
|
||||
DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
|
||||
}
|
||||
}
|
||||
config->artifacts[artifact.name] = artifact;
|
||||
return true;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
for (auto& node : root_element->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
config->artifact_format = TrimWhitespace(t->text).to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->abi_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "abi") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected element in ABI group: " << child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
for (auto& node : child->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->screen_density_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "screen-density") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected root_element in screen density group: "
|
||||
<< child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
for (auto& node : child->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
ConfigDescription config_descriptor;
|
||||
const android::StringPiece& text = TrimWhitespace(t->text);
|
||||
if (ConfigDescription::Parse(text, &config_descriptor)) {
|
||||
// Copy the density with the minimum SDK version stripped out.
|
||||
group.push_back(config_descriptor.CopyWithoutSdkVersion());
|
||||
} else {
|
||||
diag->Error(
|
||||
DiagMessage() << "Could not parse config descriptor for screen-density: "
|
||||
<< text);
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->locale_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "locale") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected root_element in screen density group: "
|
||||
<< child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
Locale entry;
|
||||
for (const auto& attr : child->attributes) {
|
||||
if (attr.name == "lang") {
|
||||
entry.lang = {attr.value};
|
||||
} else if (attr.name == "region") {
|
||||
entry.region = {attr.value};
|
||||
} else {
|
||||
diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
|
||||
<< " = " << attr.value);
|
||||
}
|
||||
}
|
||||
group.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->android_sdk_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "android-sdk") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
AndroidSdk entry;
|
||||
for (const auto& attr : child->attributes) {
|
||||
if (attr.name == "minSdkVersion") {
|
||||
entry.min_sdk_version = {attr.value};
|
||||
} else if (attr.name == "targetSdkVersion") {
|
||||
entry.target_sdk_version = {attr.value};
|
||||
} else if (attr.name == "maxSdkVersion") {
|
||||
entry.max_sdk_version = {attr.value};
|
||||
} else {
|
||||
diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
|
||||
<< " = " << attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fill in the manifest details when they are finalised.
|
||||
for (auto node : child->GetChildElements()) {
|
||||
if (node->name == "manifest") {
|
||||
if (entry.manifest) {
|
||||
diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
|
||||
continue;
|
||||
}
|
||||
entry.manifest = {AndroidManifest()};
|
||||
}
|
||||
}
|
||||
|
||||
group.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->gl_texture_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
GlTexture result;
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "gl-texture") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected element in GL texture group: "
|
||||
<< child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
for (const auto& attr : child->attributes) {
|
||||
if (attr.name == "name") {
|
||||
result.name = attr.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* element : child->GetChildElements()) {
|
||||
if (element->name != "texture-path") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected element in gl-texture element: "
|
||||
<< child->name);
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
for (auto& node : element->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
group.push_back(result);
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
|
||||
[](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
|
||||
std::string label = GetLabel(root_element, diag);
|
||||
if (label.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& group = config->device_feature_groups[label];
|
||||
bool valid = true;
|
||||
|
||||
for (auto* child : root_element->GetChildElements()) {
|
||||
if (child->name != "supports-feature") {
|
||||
diag->Error(
|
||||
DiagMessage() << "Unexpected root_element in device feature group: "
|
||||
<< child->name);
|
||||
valid = false;
|
||||
} else {
|
||||
for (auto& node : child->children) {
|
||||
xml::Text* t;
|
||||
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
|
||||
group.push_back(TrimWhitespace(t->text).to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
216
tools/aapt2/configuration/ConfigurationParser.h
Normal file
216
tools/aapt2/configuration/ConfigurationParser.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef AAPT2_CONFIGURATION_H
|
||||
#define AAPT2_CONFIGURATION_H
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <ConfigDescription.h>
|
||||
|
||||
#include "util/Maybe.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace configuration {
|
||||
|
||||
/** A mapping of group labels to group of configuration items. */
|
||||
template<class T>
|
||||
using Group = std::unordered_map<std::string, std::vector<T>>;
|
||||
|
||||
/** Output artifact configuration options. */
|
||||
struct Artifact {
|
||||
/** Name to use for output of processing foo.apk -> foo.<name>.apk. */
|
||||
std::string name;
|
||||
/** If present, uses the ABI group with this name. */
|
||||
Maybe<std::string> abi_group;
|
||||
/** If present, uses the screen density group with this name. */
|
||||
Maybe<std::string> screen_density_group;
|
||||
/** If present, uses the locale group with this name. */
|
||||
Maybe<std::string> locale_group;
|
||||
/** If present, uses the Android SDK group with this name. */
|
||||
Maybe<std::string> android_sdk_group;
|
||||
/** If present, uses the device feature group with this name. */
|
||||
Maybe<std::string> device_feature_group;
|
||||
/** If present, uses the OpenGL texture group with this name. */
|
||||
Maybe<std::string> gl_texture_group;
|
||||
};
|
||||
|
||||
/** Enumeration of currently supported ABIs. */
|
||||
enum class Abi {
|
||||
kArmeV6,
|
||||
kArmV7a,
|
||||
kArm64V8a,
|
||||
kX86,
|
||||
kX86_64,
|
||||
kMips,
|
||||
kMips64,
|
||||
kUniversal
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an individual locale. When a locale is included, it must be
|
||||
* declared from least specific to most specific, as a region does not make
|
||||
* sense without a language. If neither the language or region are specified it
|
||||
* acts as a special case for catch all. This can allow all locales to be kept,
|
||||
* or compressed.
|
||||
*/
|
||||
struct Locale {
|
||||
/** The ISO<?> standard locale language code. */
|
||||
Maybe<std::string> lang;
|
||||
/** The ISO<?> standard locale region code. */
|
||||
Maybe<std::string> region;
|
||||
|
||||
inline friend bool operator==(const Locale& lhs, const Locale& rhs) {
|
||||
return lhs.lang == rhs.lang && lhs.region == rhs.region;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Encapsulate manifest modifications from the configuration file.
|
||||
struct AndroidManifest {
|
||||
inline friend bool operator==(const AndroidManifest& lhs, const AndroidManifest& rhs) {
|
||||
return true; // nothing to compare yet.
|
||||
}
|
||||
};
|
||||
|
||||
struct AndroidSdk {
|
||||
Maybe<std::string> min_sdk_version;
|
||||
Maybe<std::string> target_sdk_version;
|
||||
Maybe<std::string> max_sdk_version;
|
||||
Maybe<AndroidManifest> manifest;
|
||||
|
||||
inline friend bool operator==(const AndroidSdk& lhs, const AndroidSdk& rhs) {
|
||||
return lhs.min_sdk_version == rhs.min_sdk_version &&
|
||||
lhs.target_sdk_version == rhs.target_sdk_version &&
|
||||
lhs.max_sdk_version == rhs.max_sdk_version &&
|
||||
lhs.manifest == rhs.manifest;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Make device features more than just an arbitrary string?
|
||||
using DeviceFeature = std::string;
|
||||
|
||||
/** Represents a mapping of texture paths to a GL texture format. */
|
||||
struct GlTexture {
|
||||
std::string name;
|
||||
std::vector<std::string> texture_paths;
|
||||
|
||||
inline friend bool operator==(const GlTexture& lhs, const GlTexture& rhs) {
|
||||
return lhs.name == rhs.name && lhs.texture_paths == rhs.texture_paths;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* AAPT2 XML configuration binary representation.
|
||||
*/
|
||||
struct Configuration {
|
||||
std::unordered_map<std::string, Artifact> artifacts;
|
||||
Maybe<std::string> artifact_format;
|
||||
|
||||
Group<Abi> abi_groups;
|
||||
Group<ConfigDescription> screen_density_groups;
|
||||
Group<Locale> locale_groups;
|
||||
Group<AndroidSdk> android_sdk_groups;
|
||||
Group<DeviceFeature> device_feature_groups;
|
||||
Group<GlTexture> gl_texture_groups;
|
||||
};
|
||||
|
||||
} // namespace configuration
|
||||
|
||||
// Forward declaration of classes used in the API.
|
||||
struct IDiagnostics;
|
||||
namespace xml {
|
||||
class Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* XML configuration file parser for the split and optimize commands.
|
||||
*/
|
||||
class ConfigurationParser {
|
||||
public:
|
||||
/** Returns a ConfigurationParser for the configuration in the provided file contents. */
|
||||
static ConfigurationParser ForContents(const std::string& contents) {
|
||||
ConfigurationParser parser{contents};
|
||||
return parser;
|
||||
}
|
||||
|
||||
/** Returns a ConfigurationParser for the file located at the provided path. */
|
||||
static ConfigurationParser ForPath(const std::string& path) {
|
||||
// TODO: Read XML file into memory.
|
||||
return ForContents(path);
|
||||
}
|
||||
|
||||
/** Sets the diagnostics context to use when parsing. */
|
||||
ConfigurationParser& WithDiagnostics(IDiagnostics* diagnostics) {
|
||||
diag_ = diagnostics;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the configuration file and returns the results. If the configuration could not be parsed
|
||||
* the result is empty and any errors will be displayed with the provided diagnostics context.
|
||||
*/
|
||||
Maybe<configuration::Configuration> Parse();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Instantiates a new ConfigurationParser with the provided configuration file and a no-op
|
||||
* diagnostics context. The default diagnostics context can be overridden with a call to
|
||||
* WithDiagnostics(IDiagnostics *).
|
||||
*/
|
||||
explicit ConfigurationParser(std::string contents);
|
||||
|
||||
/** Returns the current diagnostics context to any subclasses. */
|
||||
IDiagnostics* diagnostics() {
|
||||
return diag_;
|
||||
}
|
||||
|
||||
/**
|
||||
* An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the
|
||||
* element was successfully processed, otherwise returns false.
|
||||
*/
|
||||
using ActionHandler = std::function<bool(configuration::Configuration* config,
|
||||
xml::Element* element,
|
||||
IDiagnostics* diag)>;
|
||||
|
||||
/** Handler for <artifact> tags. */
|
||||
static ActionHandler artifact_handler_;
|
||||
/** Handler for <artifact-format> tags. */
|
||||
static ActionHandler artifact_format_handler_;
|
||||
/** Handler for <abi-group> tags. */
|
||||
static ActionHandler abi_group_handler_;
|
||||
/** Handler for <screen-density-group> tags. */
|
||||
static ActionHandler screen_density_group_handler_;
|
||||
/** Handler for <locale-group> tags. */
|
||||
static ActionHandler locale_group_handler_;
|
||||
/** Handler for <android-sdk-group> tags. */
|
||||
static ActionHandler android_sdk_group_handler_;
|
||||
/** Handler for <gl-texture-group> tags. */
|
||||
static ActionHandler gl_texture_group_handler_;
|
||||
/** Handler for <device-feature-group> tags. */
|
||||
static ActionHandler device_feature_group_handler_;
|
||||
|
||||
private:
|
||||
/** The contents of the configuration file to parse. */
|
||||
const std::string contents_;
|
||||
/** The diagnostics context to send messages to. */
|
||||
IDiagnostics* diag_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif //AAPT2_CONFIGURATION_H
|
||||
401
tools/aapt2/configuration/ConfigurationParser_test.cpp
Normal file
401
tools/aapt2/configuration/ConfigurationParser_test.cpp
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "configuration/ConfigurationParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "test/Test.h"
|
||||
#include "xml/XmlDom.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace {
|
||||
|
||||
using android::ResTable_config;
|
||||
using configuration::Abi;
|
||||
using configuration::AndroidSdk;
|
||||
using configuration::Configuration;
|
||||
using configuration::DeviceFeature;
|
||||
using configuration::GlTexture;
|
||||
using configuration::Locale;
|
||||
using configuration::AndroidManifest;
|
||||
using testing::ElementsAre;
|
||||
using xml::Element;
|
||||
using xml::NodeCast;
|
||||
|
||||
constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
|
||||
<post-process xmlns="http://schemas.android.com/tools/aapt">
|
||||
<groups>
|
||||
<abi-group label="arm">
|
||||
<abi>armeabi-v7a</abi>
|
||||
<abi>arm64-v8a</abi>
|
||||
</abi-group>
|
||||
<abi-group label="other">
|
||||
<abi>x86</abi>
|
||||
<abi>mips</abi>
|
||||
</abi-group>
|
||||
<screen-density-group label="large">
|
||||
<screen-density>xhdpi</screen-density>
|
||||
<screen-density>xxhdpi</screen-density>
|
||||
<screen-density>xxxhdpi</screen-density>
|
||||
</screen-density-group>
|
||||
<screen-density-group label="alldpi">
|
||||
<screen-density>ldpi</screen-density>
|
||||
<screen-density>mdpi</screen-density>
|
||||
<screen-density>hdpi</screen-density>
|
||||
<screen-density>xhdpi</screen-density>
|
||||
<screen-density>xxhdpi</screen-density>
|
||||
<screen-density>xxxhdpi</screen-density>
|
||||
</screen-density-group>
|
||||
<locale-group label="europe">
|
||||
<locale lang="en"/>
|
||||
<locale lang="es"/>
|
||||
<locale lang="fr"/>
|
||||
<locale lang="de"/>
|
||||
</locale-group>
|
||||
<locale-group label="north-america">
|
||||
<locale lang="en"/>
|
||||
<locale lang="es" region="MX"/>
|
||||
<locale lang="fr" region="CA"/>
|
||||
</locale-group>
|
||||
<locale-group label="all">
|
||||
<locale/>
|
||||
</locale-group>
|
||||
<android-sdk-group label="19">
|
||||
<android-sdk
|
||||
minSdkVersion="19"
|
||||
targetSdkVersion="24"
|
||||
maxSdkVersion="25">
|
||||
<manifest>
|
||||
<!--- manifest additions here XSLT? TODO -->
|
||||
</manifest>
|
||||
</android-sdk>
|
||||
</android-sdk-group>
|
||||
<gl-texture-group label="dxt1">
|
||||
<gl-texture name="GL_EXT_texture_compression_dxt1">
|
||||
<texture-path>assets/dxt1/*</texture-path>
|
||||
</gl-texture>
|
||||
</gl-texture-group>
|
||||
<device-feature-group label="low-latency">
|
||||
<supports-feature>android.hardware.audio.low_latency</supports-feature>
|
||||
</device-feature-group>
|
||||
</groups>
|
||||
<artifacts>
|
||||
<artifact-format>
|
||||
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
|
||||
</artifact-format>
|
||||
<artifact
|
||||
name="art1"
|
||||
abi-group="arm"
|
||||
screen-density-group="large"
|
||||
locale-group="europe"
|
||||
android-sdk-group="19"
|
||||
gl-texture-group="dxt1"
|
||||
device-feature-group="low-latency"/>
|
||||
<artifact
|
||||
name="art2"
|
||||
abi-group="other"
|
||||
screen-density-group="alldpi"
|
||||
locale-group="north-america"
|
||||
android-sdk-group="19"
|
||||
gl-texture-group="dxt1"
|
||||
device-feature-group="low-latency"/>
|
||||
</artifacts>
|
||||
</post-process>
|
||||
)";
|
||||
|
||||
class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
|
||||
public:
|
||||
ConfigurationParserTest() : ConfigurationParser("") {}
|
||||
|
||||
protected:
|
||||
StdErrDiagnostics diag_;
|
||||
};
|
||||
|
||||
TEST_F(ConfigurationParserTest, ValidateFile) {
|
||||
auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
|
||||
auto result = parser.Parse();
|
||||
ASSERT_TRUE(result);
|
||||
Configuration& value = result.value();
|
||||
EXPECT_EQ(2ul, value.artifacts.size());
|
||||
ASSERT_TRUE(value.artifact_format);
|
||||
EXPECT_EQ(
|
||||
"${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release",
|
||||
value.artifact_format.value()
|
||||
);
|
||||
|
||||
EXPECT_EQ(2ul, value.abi_groups.size());
|
||||
EXPECT_EQ(2ul, value.abi_groups["arm"].size());
|
||||
EXPECT_EQ(2ul, value.abi_groups["other"].size());
|
||||
|
||||
EXPECT_EQ(2ul, value.screen_density_groups.size());
|
||||
EXPECT_EQ(3ul, value.screen_density_groups["large"].size());
|
||||
EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size());
|
||||
|
||||
EXPECT_EQ(3ul, value.locale_groups.size());
|
||||
EXPECT_EQ(4ul, value.locale_groups["europe"].size());
|
||||
EXPECT_EQ(3ul, value.locale_groups["north-america"].size());
|
||||
EXPECT_EQ(1ul, value.locale_groups["all"].size());
|
||||
|
||||
EXPECT_EQ(1ul, value.android_sdk_groups.size());
|
||||
EXPECT_EQ(1ul, value.android_sdk_groups["19"].size());
|
||||
|
||||
EXPECT_EQ(1ul, value.gl_texture_groups.size());
|
||||
EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
|
||||
|
||||
EXPECT_EQ(1ul, value.device_feature_groups.size());
|
||||
EXPECT_EQ(1ul, value.device_feature_groups["low-latency"].size());
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, InvalidNamespace) {
|
||||
constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
|
||||
<post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
|
||||
|
||||
auto result = ConfigurationParser::ForContents(invalid_ns).Parse();
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, ArtifactAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<artifact
|
||||
abi-group="arm"
|
||||
screen-density-group="large"
|
||||
locale-group="europe"
|
||||
android-sdk-group="19"
|
||||
gl-texture-group="dxt1"
|
||||
device-feature-group="low-latency"/>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
EXPECT_EQ(1ul, config.artifacts.size());
|
||||
|
||||
auto& artifact = config.artifacts.begin()->second;
|
||||
EXPECT_EQ("", artifact.name); // TODO: make this fail.
|
||||
EXPECT_EQ("arm", artifact.abi_group.value());
|
||||
EXPECT_EQ("large", artifact.screen_density_group.value());
|
||||
EXPECT_EQ("europe", artifact.locale_group.value());
|
||||
EXPECT_EQ("19", artifact.android_sdk_group.value());
|
||||
EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
|
||||
EXPECT_EQ("low-latency", artifact.device_feature_group.value());
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<artifact-format>
|
||||
${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
|
||||
</artifact-format>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
ASSERT_TRUE(config.artifact_format);
|
||||
EXPECT_EQ(
|
||||
"${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release",
|
||||
static_cast<std::string>(config.artifact_format.value())
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, AbiGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<abi-group label="arm">
|
||||
<!-- First comment. -->
|
||||
<abi>
|
||||
armeabi-v7a
|
||||
</abi>
|
||||
<!-- Another comment. -->
|
||||
<abi>arm64-v8a</abi>
|
||||
</abi-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = abi_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
EXPECT_EQ(1ul, config.abi_groups.size());
|
||||
ASSERT_EQ(1u, config.abi_groups.count("arm"));
|
||||
|
||||
auto& out = config.abi_groups["arm"];
|
||||
ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<screen-density-group label="large">
|
||||
<screen-density>xhdpi</screen-density>
|
||||
<screen-density>
|
||||
xxhdpi
|
||||
</screen-density>
|
||||
<screen-density>xxxhdpi</screen-density>
|
||||
</screen-density-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok =
|
||||
screen_density_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
EXPECT_EQ(1ul, config.screen_density_groups.size());
|
||||
ASSERT_EQ(1u, config.screen_density_groups.count("large"));
|
||||
|
||||
ConfigDescription xhdpi;
|
||||
xhdpi.density = ResTable_config::DENSITY_XHIGH;
|
||||
ConfigDescription xxhdpi;
|
||||
xxhdpi.density = ResTable_config::DENSITY_XXHIGH;
|
||||
ConfigDescription xxxhdpi;
|
||||
xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
|
||||
|
||||
auto& out = config.screen_density_groups["large"];
|
||||
ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, LocaleGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<locale-group label="europe">
|
||||
<locale lang="en"/>
|
||||
<locale lang="es"/>
|
||||
<locale lang="fr"/>
|
||||
<locale lang="de"/>
|
||||
</locale-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = locale_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
ASSERT_EQ(1ul, config.locale_groups.size());
|
||||
ASSERT_EQ(1u, config.locale_groups.count("europe"));
|
||||
|
||||
auto& out = config.locale_groups["europe"];
|
||||
|
||||
Locale en;
|
||||
en.lang = std::string("en");
|
||||
Locale es;
|
||||
es.lang = std::string("es");
|
||||
Locale fr;
|
||||
fr.lang = std::string("fr");
|
||||
Locale de;
|
||||
de.lang = std::string("de");
|
||||
|
||||
ASSERT_THAT(out, ElementsAre(en, es, fr, de));
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<android-sdk-group label="19">
|
||||
<android-sdk
|
||||
minSdkVersion="19"
|
||||
targetSdkVersion="24"
|
||||
maxSdkVersion="25">
|
||||
<manifest>
|
||||
<!--- manifest additions here XSLT? TODO -->
|
||||
</manifest>
|
||||
</android-sdk>
|
||||
</android-sdk-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
ASSERT_EQ(1ul, config.android_sdk_groups.size());
|
||||
ASSERT_EQ(1u, config.android_sdk_groups.count("19"));
|
||||
|
||||
auto& out = config.android_sdk_groups["19"];
|
||||
|
||||
AndroidSdk sdk;
|
||||
sdk.min_sdk_version = std::string("19");
|
||||
sdk.target_sdk_version = std::string("24");
|
||||
sdk.max_sdk_version = std::string("25");
|
||||
sdk.manifest = AndroidManifest();
|
||||
|
||||
ASSERT_EQ(1ul, out.size());
|
||||
ASSERT_EQ(sdk, out[0]);
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<gl-texture-group label="dxt1">
|
||||
<gl-texture name="GL_EXT_texture_compression_dxt1">
|
||||
<texture-path>assets/dxt1/main/*</texture-path>
|
||||
<texture-path>
|
||||
assets/dxt1/test/*
|
||||
</texture-path>
|
||||
</gl-texture>
|
||||
</gl-texture-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok = gl_texture_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
EXPECT_EQ(1ul, config.gl_texture_groups.size());
|
||||
ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
|
||||
|
||||
auto& out = config.gl_texture_groups["dxt1"];
|
||||
|
||||
GlTexture texture{
|
||||
std::string("GL_EXT_texture_compression_dxt1"),
|
||||
{"assets/dxt1/main/*", "assets/dxt1/test/*"}
|
||||
};
|
||||
|
||||
ASSERT_EQ(1ul, out.size());
|
||||
ASSERT_EQ(texture, out[0]);
|
||||
}
|
||||
|
||||
TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) {
|
||||
static constexpr const char* xml = R"xml(
|
||||
<device-feature-group label="low-latency">
|
||||
<supports-feature>android.hardware.audio.low_latency</supports-feature>
|
||||
<supports-feature>
|
||||
android.hardware.audio.pro
|
||||
</supports-feature>
|
||||
</device-feature-group>)xml";
|
||||
|
||||
auto doc = test::BuildXmlDom(xml);
|
||||
|
||||
Configuration config;
|
||||
bool ok
|
||||
= device_feature_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
|
||||
ASSERT_TRUE(ok);
|
||||
|
||||
EXPECT_EQ(1ul, config.device_feature_groups.size());
|
||||
ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
|
||||
|
||||
auto& out = config.device_feature_groups["low-latency"];
|
||||
|
||||
DeviceFeature low_latency = "android.hardware.audio.low_latency";
|
||||
DeviceFeature pro = "android.hardware.audio.pro";
|
||||
ASSERT_THAT(out, ElementsAre(low_latency, pro));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace aapt
|
||||
Reference in New Issue
Block a user