Files
frameworks_base/tools/aapt2/configuration/ConfigurationParser.cpp
Shane Farmer 11cdc1cf7a AAPT2: Add order attribute to groups
Require explicit ordering of groups in the configuration file to ensure
that the correct version code is set. Ordering based on a single ABI is
straight forward to ensure Play Store delivers the correct APK, but when
an APK needs more than one ABI things get messy quickly. This also goes
for screen density etc. The only thing that is easily sorted without
this attribute is android-sdk since an artifact can only reference a
single SDK.

Test: unit tests
Test: manually split an APK with update config.xml
Change-Id: I37a2b8b8a8409d6d6ff27c7142d4c8c8065a7a51
2018-02-02 12:11:18 -08:00

860 lines
29 KiB
C++

/*
* 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 <map>
#include <memory>
#include <string>
#include <utility>
#include "android-base/file.h"
#include "android-base/logging.h"
#include "ConfigDescription.h"
#include "Diagnostics.h"
#include "ResourceUtils.h"
#include "configuration/ConfigurationParser.internal.h"
#include "io/File.h"
#include "io/FileSystem.h"
#include "io/StringStream.h"
#include "util/Files.h"
#include "util/Maybe.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::ConfiguredArtifact;
using ::aapt::configuration::DeviceFeature;
using ::aapt::configuration::Entry;
using ::aapt::configuration::ExtractConfiguration;
using ::aapt::configuration::GlTexture;
using ::aapt::configuration::Group;
using ::aapt::configuration::Locale;
using ::aapt::configuration::OrderedEntry;
using ::aapt::configuration::OutputArtifact;
using ::aapt::configuration::PostProcessingConfiguration;
using ::aapt::configuration::handler::AbiGroupTagHandler;
using ::aapt::configuration::handler::AndroidSdkTagHandler;
using ::aapt::configuration::handler::ArtifactFormatTagHandler;
using ::aapt::configuration::handler::ArtifactTagHandler;
using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
using ::aapt::configuration::handler::GlTextureGroupTagHandler;
using ::aapt::configuration::handler::LocaleGroupTagHandler;
using ::aapt::configuration::handler::ScreenDensityGroupTagHandler;
using ::aapt::io::IFile;
using ::aapt::io::RegularFile;
using ::aapt::io::StringInputStream;
using ::aapt::util::TrimWhitespace;
using ::aapt::xml::Element;
using ::aapt::xml::NodeCast;
using ::aapt::xml::XmlActionExecutor;
using ::aapt::xml::XmlActionExecutorPolicy;
using ::aapt::xml::XmlNodeAction;
using ::android::StringPiece;
using ::android::base::ReadFileToString;
const std::unordered_map<StringPiece, Abi> kStringToAbiMap = {
{"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},
};
const std::array<StringPiece, 8> kAbiToStringMap = {
{"armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64", "universal"}};
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_;
/** Returns the value of the label attribute for a given element. */
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;
}
/** Returns the value of the version-code-order attribute for a given element. */
Maybe<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) {
const xml::Attribute* version = element->FindAttribute("", "version-code-order");
if (version == nullptr) {
std::string label = GetLabel(element, diag);
diag->Error(DiagMessage() << "No version-code-order found for element '" << element->name
<< "' with label '" << label << "'");
return {};
}
return std::stoi(version->value);
}
/** 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);
}
};
/** Copies the values referenced in a configuration group to the target list. */
template <typename T>
bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups,
std::vector<T>* target) {
// If there was no item configured, there is nothing to do and no error.
if (!name) {
return true;
}
// If the group could not be found, then something is wrong.
auto group = groups.find(name.value());
if (group == groups.end()) {
return false;
}
for (const T& item : group->second.entry) {
target->push_back(item);
}
return true;
}
/**
* Attempts to replace the placeholder in the name string with the provided value. Returns true on
* success, or false if the either the placeholder is not found in the name, or the value is not
* present and the placeholder was.
*/
bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
std::string* name, IDiagnostics* diag) {
size_t offset = name->find(placeholder.data());
bool found = (offset != std::string::npos);
// Make sure the placeholder was present if the desired value is present.
if (!found) {
if (value) {
diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder);
return false;
}
return true;
}
DCHECK(found) << "Missing return path for placeholder not found";
// Make sure the placeholder was not present if the desired value was not present.
if (!value) {
diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder);
return false;
}
name->replace(offset, placeholder.length(), value.value().data());
// Make sure there was only one instance of the placeholder.
if (name->find(placeholder.data()) != std::string::npos) {
diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder);
return false;
}
return true;
}
/**
* 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::PostProcessingConfiguration* config,
xml::Element* element, IDiagnostics* diag)>;
/** Binds an ActionHandler to the current configuration being populated. */
xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfiguration* config,
const ActionHandler& handler) {
return [config, handler](xml::Element* root_element, SourcePathDiagnostics* diag) {
return handler(config, root_element, diag);
};
}
/** Converts a ConfiguredArtifact into an OutputArtifact. */
Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
const std::string& apk_name,
const PostProcessingConfiguration& config,
IDiagnostics* diag) {
if (!artifact.name && !config.artifact_format) {
diag->Error(
DiagMessage() << "Artifact does not have a name and no global name template defined");
return {};
}
Maybe<std::string> artifact_name =
(artifact.name) ? artifact.Name(apk_name, diag)
: artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag);
if (!artifact_name) {
diag->Error(DiagMessage() << "Could not determine split APK artifact name");
return {};
}
OutputArtifact output_artifact;
output_artifact.name = artifact_name.value();
SourcePathDiagnostics src_diag{{output_artifact.name}, diag};
bool has_errors = false;
if (!CopyXmlReferences(artifact.abi_group, config.abi_groups, &output_artifact.abis)) {
src_diag.Error(DiagMessage() << "Could not lookup required ABIs: "
<< artifact.abi_group.value());
has_errors = true;
}
if (!CopyXmlReferences(artifact.locale_group, config.locale_groups, &output_artifact.locales)) {
src_diag.Error(DiagMessage() << "Could not lookup required locales: "
<< artifact.locale_group.value());
has_errors = true;
}
if (!CopyXmlReferences(artifact.screen_density_group, config.screen_density_groups,
&output_artifact.screen_densities)) {
src_diag.Error(DiagMessage() << "Could not lookup required screen densities: "
<< artifact.screen_density_group.value());
has_errors = true;
}
if (!CopyXmlReferences(artifact.device_feature_group, config.device_feature_groups,
&output_artifact.features)) {
src_diag.Error(DiagMessage() << "Could not lookup required device features: "
<< artifact.device_feature_group.value());
has_errors = true;
}
if (!CopyXmlReferences(artifact.gl_texture_group, config.gl_texture_groups,
&output_artifact.textures)) {
src_diag.Error(DiagMessage() << "Could not lookup required OpenGL texture formats: "
<< artifact.gl_texture_group.value());
has_errors = true;
}
if (artifact.android_sdk) {
auto entry = config.android_sdks.find(artifact.android_sdk.value());
if (entry == config.android_sdks.end()) {
src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
<< artifact.android_sdk.value());
has_errors = true;
} else {
output_artifact.android_sdk = {entry->second};
}
}
if (has_errors) {
return {};
}
return {output_artifact};
}
} // namespace
namespace configuration {
/** Returns the binary reprasentation of the XML configuration. */
Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
const std::string& config_path,
IDiagnostics* diag) {
StringInputStream in(contents);
std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
if (!doc) {
return {};
}
// Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
Element* root = doc->root.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"];
PostProcessingConfiguration config;
// Parse the artifact elements.
artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
// Parse the different configuration groups.
root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
root_action["screen-density-groups"]["screen-density-group"].Action(
Bind(&config, ScreenDensityGroupTagHandler));
root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
root_action["gl-texture-groups"]["gl-texture-group"].Action(
Bind(&config, GlTextureGroupTagHandler));
root_action["device-feature-groups"]["device-feature-group"].Action(
Bind(&config, DeviceFeatureGroupTagHandler));
if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
diag->Error(DiagMessage() << "Could not process XML document");
return {};
}
return {config};
}
const StringPiece& AbiToString(Abi abi) {
return kAbiToStringMap.at(static_cast<size_t>(abi));
}
/**
* Returns the common artifact base name from a template string.
*/
Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) {
const StringPiece ext = file::GetExtension(apk_name);
size_t end_index = apk_name.to_string().rfind(ext.to_string());
const std::string base_name =
(end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
// Base name is optional.
if (result.find("${basename}") != std::string::npos) {
Maybe<StringPiece> maybe_base_name =
base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name};
if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) {
return {};
}
}
// Extension is optional.
if (result.find("${ext}") != std::string::npos) {
// Make sure we disregard the '.' in the extension when replacing the placeholder.
if (!ReplacePlaceholder("${ext}", {ext.substr(1)}, &result, diag)) {
return {};
}
} else {
// If no extension is specified, and the name template does not end in the current extension,
// add the existing extension.
if (!util::EndsWith(result, ext)) {
result.append(ext.to_string());
}
}
return result;
}
Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
const StringPiece& apk_name,
IDiagnostics* diag) const {
Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
if (!base) {
return {};
}
std::string result = std::move(base.value());
if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) {
return {};
}
if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) {
return {};
}
if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) {
return {};
}
if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
return {};
}
if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) {
return {};
}
if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) {
return {};
}
return result;
}
Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const {
if (!name) {
return {};
}
return ToBaseName(name.value(), apk_name, diag);
}
} // namespace configuration
/** Returns a ConfigurationParser for the file located at the provided path. */
Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
std::string contents;
if (!ReadFileToString(path, &contents, true)) {
return {};
}
return ConfigurationParser(contents, path);
}
ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
: contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
}
Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
const android::StringPiece& apk_path) {
Maybe<PostProcessingConfiguration> maybe_config =
ExtractConfiguration(contents_, config_path_, diag_);
if (!maybe_config) {
return {};
}
// Convert from a parsed configuration to a list of artifacts for processing.
const std::string& apk_name = file::GetFilename(apk_path).to_string();
std::vector<OutputArtifact> output_artifacts;
PostProcessingConfiguration& config = maybe_config.value();
bool valid = true;
int version = 1;
for (const ConfiguredArtifact& artifact : config.artifacts) {
Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
if (!output_artifact) {
// Defer return an error condition so that all errors are reported.
valid = false;
} else {
output_artifact.value().version = version++;
output_artifacts.push_back(std::move(output_artifact.value()));
}
}
if (!config.ValidateVersionCodeOrdering(diag_)) {
diag_->Error(DiagMessage() << "could not validate post processing configuration");
valid = false;
}
if (valid) {
// Sorting artifacts requires that all references are valid as it uses them to determine order.
config.SortArtifacts();
}
if (!valid) {
return {};
}
return {output_artifacts};
}
namespace configuration {
namespace handler {
bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
ConfiguredArtifact 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") {
artifact.android_sdk = {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.push_back(artifact);
return true;
};
bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* /* diag */) {
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;
};
bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
std::string label = GetLabel(root_element, diag);
if (label.empty()) {
return false;
}
bool valid = true;
OrderedEntry<Abi>& entry = config->abi_groups[label];
Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag);
if (!order) {
valid = false;
} else {
entry.order = order.value();
}
auto& group = entry.entry;
// Special case for empty abi-group tag. Label will be used as the ABI.
if (root_element->GetChildElements().empty()) {
auto abi = kStringToAbiMap.find(label);
if (abi == kStringToAbiMap.end()) {
return false;
}
group.push_back(abi->second);
return valid;
}
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) {
auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
if (abi != kStringToAbiMap.end()) {
group.push_back(abi->second);
} else {
diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
valid = false;
}
break;
}
}
}
}
return valid;
};
bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
std::string label = GetLabel(root_element, diag);
if (label.empty()) {
return false;
}
bool valid = true;
OrderedEntry<ConfigDescription>& entry = config->screen_density_groups[label];
Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag);
if (!order) {
valid = false;
} else {
entry.order = order.value();
}
auto& group = entry.entry;
// Special case for empty screen-density-group tag. Label will be used as the screen density.
if (root_element->GetChildElements().empty()) {
ConfigDescription config_descriptor;
bool parsed = ConfigDescription::Parse(label, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
android::ResTable_config::CONFIG_DENSITY)) {
// 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 empty screen-density-group: "
<< label);
valid = false;
}
return valid;
}
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);
bool parsed = ConfigDescription::Parse(text, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
android::ResTable_config::CONFIG_DENSITY)) {
// 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;
};
bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
std::string label = GetLabel(root_element, diag);
if (label.empty()) {
return false;
}
bool valid = true;
OrderedEntry<ConfigDescription>& entry = config->locale_groups[label];
Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag);
if (!order) {
valid = false;
} else {
entry.order = order.value();
}
auto& group = entry.entry;
// Special case to auto insert a locale for an empty group. Label will be used for locale.
if (root_element->GetChildElements().empty()) {
ConfigDescription config_descriptor;
bool parsed = ConfigDescription::Parse(label, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
android::ResTable_config::CONFIG_LOCALE)) {
// Copy the locale with the minimum SDK version stripped out.
group.push_back(config_descriptor.CopyWithoutSdkVersion());
} else {
diag->Error(DiagMessage()
<< "Could not parse config descriptor for empty screen-density-group: "
<< label);
valid = false;
}
return valid;
}
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 {
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);
bool parsed = ConfigDescription::Parse(text, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
android::ResTable_config::CONFIG_LOCALE)) {
// Copy the locale 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;
};
bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
bool valid = true;
for (const auto& attr : root_element->attributes) {
bool valid_attr = false;
if (attr.name == "label") {
entry.label = attr.value;
valid_attr = true;
} else if (attr.name == "minSdkVersion") {
Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
if (version) {
valid_attr = true;
entry.min_sdk_version = version.value();
}
} else if (attr.name == "targetSdkVersion") {
Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
if (version) {
valid_attr = true;
entry.target_sdk_version = version;
}
} else if (attr.name == "maxSdkVersion") {
Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
if (version) {
valid_attr = true;
entry.max_sdk_version = version;
}
}
if (!valid_attr) {
diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
valid = false;
}
}
if (entry.min_sdk_version == -1) {
diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
valid = false;
}
// TODO: Fill in the manifest details when they are finalised.
for (auto node : root_element->GetChildElements()) {
if (node->name == "manifest") {
if (entry.manifest) {
diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
continue;
}
entry.manifest = {AndroidManifest()};
}
}
config->android_sdks[entry.label] = entry;
return valid;
};
bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
std::string label = GetLabel(root_element, diag);
if (label.empty()) {
return false;
}
bool valid = true;
OrderedEntry<GlTexture>& entry = config->gl_texture_groups[label];
Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag);
if (!order) {
valid = false;
} else {
entry.order = order.value();
}
auto& group = entry.entry;
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;
};
bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
IDiagnostics* diag) {
std::string label = GetLabel(root_element, diag);
if (label.empty()) {
return false;
}
bool valid = true;
OrderedEntry<DeviceFeature>& entry = config->device_feature_groups[label];
Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag);
if (!order) {
valid = false;
} else {
entry.order = order.value();
}
auto& group = entry.entry;
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 handler
} // namespace configuration
} // namespace aapt