Merge "AAPT2: XML configuration file parser."

This commit is contained in:
TreeHugger Robot
2017-06-01 07:20:54 +00:00
committed by Android (Google) Code Review
4 changed files with 1054 additions and 1 deletions

View File

@@ -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"],
}

View 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

View 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

View 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