You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
862 lines
29 KiB
862 lines
29 KiB
/*
|
|
* 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 "androidfw/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"
|
|
|
|
using ::android::ConfigDescription;
|
|
|
|
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
|