summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Shane Farmer <safarmer@google.com> 2017-06-22 12:24:12 -0700
committer Shane Farmer <safarmer@google.com> 2017-08-16 19:19:54 +0000
commit0a5b201156f1dd01a7da7b7674798ed57cfafc5a (patch)
treec2682b213bb6f5c3bc390631dbc7e71ea6f527e2
parent8dbbdf0c370c56426ffde06ccdbe55d2ffb9fb8d (diff)
AAPT2: Add a APK filtering.
Allow resource files to be removed from the final artifact based on the density and locale configuration in the config file. The APK is split along the density, locale and ABI axis. Each split is generated from the original APK without modifying the original. The new resource table is written back to the file system with unneeded assets etc removed. Test: Unit tests Test: Manually run optimize command against an APK and inspect results Test: Installed split searchlite APK (after resigning) and ran on N6 Change-Id: If73597dcfd88c02d2616518585d0e25a5c6a84d1
-rw-r--r--tools/aapt2/Android.bp1
-rw-r--r--tools/aapt2/LoadedApk.cpp11
-rw-r--r--tools/aapt2/LoadedApk.h9
-rw-r--r--tools/aapt2/ResourceTable.cpp30
-rw-r--r--tools/aapt2/ResourceTable.h2
-rw-r--r--tools/aapt2/cmd/Optimize.cpp68
-rw-r--r--tools/aapt2/configuration/ConfigurationParser.cpp96
-rw-r--r--tools/aapt2/configuration/ConfigurationParser.h12
-rw-r--r--tools/aapt2/configuration/ConfigurationParser_test.cpp86
-rw-r--r--tools/aapt2/filter/ConfigFilter.h10
-rw-r--r--tools/aapt2/filter/Filter_test.cpp8
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.cpp147
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.h53
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator_test.cpp108
-rw-r--r--tools/aapt2/split/TableSplitter.cpp70
-rw-r--r--tools/aapt2/test/Builders.cpp58
-rw-r--r--tools/aapt2/test/Builders.h33
-rw-r--r--tools/aapt2/test/Common.h2
18 files changed, 618 insertions, 186 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 53794e641b0f..15fb016038e0 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -106,6 +106,7 @@ cc_library_host_static {
"link/XmlCompatVersioner.cpp",
"link/XmlNamespaceRemover.cpp",
"link/XmlReferenceLinker.cpp",
+ "optimize/MultiApkGenerator.cpp",
"optimize/ResourceDeduper.cpp",
"optimize/VersionCollapser.cpp",
"process/SymbolTable.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index abc0e4c1b89d..b80780e33dfa 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -58,14 +58,15 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
IArchiveWriter* writer) {
FilterChain empty;
- return WriteToArchive(context, options, &empty, writer);
+ return WriteToArchive(context, table_.get(), options, &empty, writer);
}
-bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
- FilterChain* filters, IArchiveWriter* writer) {
+bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+ const TableFlattenerOptions& options, FilterChain* filters,
+ IArchiveWriter* writer) {
std::set<std::string> referenced_resources;
// List the files being referenced in the resource table.
- for (auto& pkg : table_->packages) {
+ for (auto& pkg : split_table->packages) {
for (auto& type : pkg->types) {
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
@@ -108,7 +109,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
// with sparse entries) b/35389232.
TableFlattener flattener(options, &buffer);
- if (!flattener.Consume(context, table_.get())) {
+ if (!flattener.Consume(context, split_table)) {
return false;
}
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index 8aa9674aa2ed..dacd0c2130a9 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -47,16 +47,17 @@ class LoadedApk {
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table.
*/
- bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
- IArchiveWriter* writer);
+ virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
+ IArchiveWriter* writer);
/**
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table. The provided filter
* chain is applied to each entry in the APK file.
*/
- bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
- FilterChain* filters, IArchiveWriter* writer);
+ virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
+ const TableFlattenerOptions& options, FilterChain* filters,
+ IArchiveWriter* writer);
static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
const android::StringPiece& path);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index ab59560d33a3..0304e21698df 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -546,4 +546,34 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam
return SearchResult{package, type, entry};
}
+std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
+ std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
+ for (const auto& pkg : packages) {
+ ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
+ for (const auto& type : pkg->types) {
+ ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
+ if (!new_type->id) {
+ new_type->id = type->id;
+ new_type->symbol_status = type->symbol_status;
+ }
+
+ for (const auto& entry : type->entries) {
+ ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
+ if (!new_entry->id) {
+ new_entry->id = entry->id;
+ new_entry->symbol_status = entry->symbol_status;
+ }
+
+ for (const auto& config_value : entry->values) {
+ ResourceConfigValue* new_value =
+ new_entry->FindOrCreateValue(config_value->config, config_value->product);
+ Value* value = config_value->value->Clone(&new_table->string_pool);
+ new_value->value = std::unique_ptr<Value>(value);
+ }
+ }
+ }
+ }
+ return new_table;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 4295d0674774..d5db67e77f51 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -251,6 +251,8 @@ class ResourceTable {
ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
+ std::unique_ptr<ResourceTable> Clone() const;
+
/**
* The string pool used by this resource table. Values that reference strings
* must use
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 9d71775889d4..84b79274b9a6 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -18,6 +18,7 @@
#include <vector>
#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
@@ -33,15 +34,19 @@
#include "flatten/XmlFlattener.h"
#include "io/BigBufferInputStream.h"
#include "io/Util.h"
+#include "optimize/MultiApkGenerator.h"
#include "optimize/ResourceDeduper.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
+#include "util/Util.h"
using ::aapt::configuration::Abi;
using ::aapt::configuration::Artifact;
using ::aapt::configuration::PostProcessingConfiguration;
+using ::android::ResTable_config;
using ::android::StringPiece;
+using ::android::base::StringAppendF;
using ::android::base::StringPrintf;
namespace aapt {
@@ -188,42 +193,10 @@ class OptimizeCommand {
}
if (options_.configuration && options_.output_dir) {
- PostProcessingConfiguration& config = options_.configuration.value();
-
- // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
- for (const Artifact& artifact : config.artifacts) {
- if (artifact.abi_group) {
- const std::string& group = artifact.abi_group.value();
-
- auto abi_group = config.abi_groups.find(group);
- // TODO: Remove validation when configuration parser ensures referential integrity.
- if (abi_group == config.abi_groups.end()) {
- context_->GetDiagnostics()->Note(
- DiagMessage() << "could not find referenced ABI group '" << group << "'");
- return 1;
- }
- FilterChain filters;
- filters.AddFilter(AbiFilter::FromAbiList(abi_group->second));
-
- const std::string& path = apk->GetSource().path;
- const StringPiece ext = file::GetExtension(path);
- const std::string name = path.substr(0, path.rfind(ext.to_string()));
-
- // Name is hard coded for now since only one split dimension is supported.
- // TODO: Incorporate name generation into the configuration objects.
- const std::string file_name =
- StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data());
- std::string out = options_.output_dir.value();
- file::AppendPath(&out, file_name);
-
- std::unique_ptr<IArchiveWriter> writer =
- CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
-
- if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters,
- writer.get())) {
- return 1;
- }
- }
+ MultiApkGenerator generator{apk.get(), context_};
+ if (!generator.FromBaseApk(options_.output_dir.value(), options_.configuration.value(),
+ options_.table_flattener_options)) {
+ return 1;
}
}
@@ -260,7 +233,7 @@ class OptimizeCommand {
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
- FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
+ auto* file_ref = ValueCast<FileReference>(config_value->value.get());
if (file_ref == nullptr) {
continue;
}
@@ -297,11 +270,8 @@ class OptimizeCommand {
}
io::BigBufferInputStream table_buffer_in(&table_buffer);
- if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
- ArchiveEntry::kAlign, writer)) {
- return false;
- }
- return true;
+ return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
+ ArchiveEntry::kAlign, writer);
}
OptimizeOptions options_;
@@ -349,6 +319,7 @@ int Optimize(const std::vector<StringPiece>& args) {
OptimizeOptions options;
Maybe<std::string> config_path;
Maybe<std::string> target_densities;
+ Maybe<std::string> target_abis;
std::vector<std::string> configs;
std::vector<std::string> split_args;
bool verbose = false;
@@ -363,6 +334,12 @@ int Optimize(const std::vector<StringPiece>& args) {
"All the resources that would be unused on devices of the given densities will be \n"
"removed from the APK.",
&target_densities)
+ .OptionalFlag(
+ "--target-abis",
+ "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
+ "All the native libraries that would be unused on devices of the given ABIs will \n"
+ "be removed from the APK.",
+ &target_abis)
.OptionalFlagList("-c",
"Comma separated list of configurations to include. The default\n"
"is all configurations.",
@@ -388,7 +365,8 @@ int Optimize(const std::vector<StringPiece>& args) {
return 1;
}
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
+ const std::string& apk_path = flags.GetArgs()[0];
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
if (!apk) {
return 1;
}
@@ -418,8 +396,8 @@ int Optimize(const std::vector<StringPiece>& args) {
// Parse the split parameters.
for (const std::string& split_arg : split_args) {
- options.split_paths.push_back({});
- options.split_constraints.push_back({});
+ options.split_paths.emplace_back();
+ options.split_constraints.emplace_back();
if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
&options.split_constraints.back())) {
return 1;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index b0ed792215df..a9d6da0258cb 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -59,6 +59,7 @@ using ::aapt::xml::XmlActionExecutor;
using ::aapt::xml::XmlActionExecutorPolicy;
using ::aapt::xml::XmlNodeAction;
using ::android::base::ReadFileToString;
+using ::android::StringPiece;
const std::unordered_map<std::string, Abi> kStringToAbiMap = {
{"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a},
@@ -117,9 +118,9 @@ const std::string& AbiToString(Abi abi) {
* success, or false if the either the placeholder is not found in the name, or the value is not
* present and the placeholder was.
*/
-static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value,
+static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value,
std::string* name, IDiagnostics* diag) {
- size_t offset = name->find(placeholder);
+ 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.
@@ -139,46 +140,86 @@ static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::
return false;
}
- name->replace(offset, placeholder.length(), value.value());
+ name->replace(offset, placeholder.length(), value.value().data());
// Make sure there was only one instance of the placeholder.
- if (name->find(placeholder) != std::string::npos) {
+ if (name->find(placeholder.data()) != std::string::npos) {
diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder);
return false;
}
return true;
}
-Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const {
- std::string result = format;
+Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, IDiagnostics* diag,
+ const StringPiece& base_name,
+ const StringPiece& ext) const {
+ std::string result = format.to_string();
- if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) {
+ Maybe<StringPiece> maybe_base_name =
+ base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name};
+ if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) {
return {};
}
- if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) {
+ // Extension is optional.
+ if (result.find("${ext}") != std::string::npos) {
+ if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) {
+ return {};
+ }
+ }
+
+ if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) {
+ return {};
+ }
+
+ if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) {
return {};
}
- if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) {
+ if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) {
return {};
}
- if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) {
+ if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
return {};
}
- if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) {
+ if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) {
return {};
}
- if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) {
+ if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) {
return {};
}
return result;
}
+Maybe<std::string> Artifact::Name(const StringPiece& base_name, const StringPiece& ext,
+ IDiagnostics* diag) const {
+ if (!name) {
+ return {};
+ }
+
+ std::string result = name.value();
+
+ // Base name is optional.
+ if (result.find("${basename}") != std::string::npos) {
+ if (!ReplacePlaceholder("${basename}", {base_name}, &result, diag)) {
+ return {};
+ }
+ }
+
+ // Extension is optional.
+ if (result.find("${ext}") != std::string::npos) {
+ if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) {
+ return {};
+ }
+ }
+
+ return result;
+}
+
} // namespace configuration
/** Returns a ConfigurationParser for the file located at the provided path. */
@@ -346,7 +387,10 @@ ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_han
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
ConfigDescription config_descriptor;
const android::StringPiece& text = TrimWhitespace(t->text);
- if (ConfigDescription::Parse(text, &config_descriptor)) {
+ 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 {
@@ -379,17 +423,25 @@ ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
<< 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);
+ 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;
}
}
- group.push_back(entry);
}
}
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 28c355e39643..6259ce8e28ea 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -36,7 +36,7 @@ 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;
+ Maybe<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. */
@@ -51,7 +51,13 @@ struct Artifact {
Maybe<std::string> gl_texture_group;
/** Convert an artifact name template into a name string based on configuration contents. */
- Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const;
+ Maybe<std::string> ToArtifactName(const android::StringPiece& format, IDiagnostics* diag,
+ const android::StringPiece& base_name = "",
+ const android::StringPiece& ext = "apk") const;
+
+ /** Convert an artifact name template into a name string based on configuration contents. */
+ Maybe<std::string> Name(const android::StringPiece& base_name, const android::StringPiece& ext,
+ IDiagnostics* diag) const;
};
/** Enumeration of currently supported ABIs. */
@@ -129,7 +135,7 @@ struct PostProcessingConfiguration {
Group<Abi> abi_groups;
Group<ConfigDescription> screen_density_groups;
- Group<Locale> locale_groups;
+ Group<ConfigDescription> locale_groups;
Group<AndroidSdk> android_sdk_groups;
Group<DeviceFeature> device_feature_groups;
Group<GlTexture> gl_texture_groups;
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index ab3b7ec2003b..5bd083120c4d 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -67,18 +67,15 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
<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>en</locale>
+ <locale>es</locale>
+ <locale>fr</locale>
+ <locale>de</locale>
</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>en</locale>
+ <locale>es-rMX</locale>
+ <locale>fr-rCA</locale>
</locale-group>
<android-sdk-group label="19">
<android-sdk
@@ -156,10 +153,9 @@ TEST_F(ConfigurationParserTest, ValidateFile) {
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(2ul, 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());
@@ -198,7 +194,7 @@ TEST_F(ConfigurationParserTest, ArtifactAction) {
EXPECT_EQ(1ul, config.artifacts.size());
auto& artifact = config.artifacts.front();
- EXPECT_EQ("", artifact.name); // TODO: make this fail.
+ EXPECT_FALSE(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());
@@ -298,10 +294,10 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
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>en</locale>
+ <locale>es</locale>
+ <locale>fr</locale>
+ <locale>de</locale>
</locale-group>)xml";
auto doc = test::BuildXmlDom(xml);
@@ -313,16 +309,12 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) {
ASSERT_EQ(1ul, config.locale_groups.size());
ASSERT_EQ(1u, config.locale_groups.count("europe"));
- auto& out = config.locale_groups["europe"];
+ const 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");
+ ConfigDescription en = test::ParseConfigOrDie("en");
+ ConfigDescription es = test::ParseConfigOrDie("es");
+ ConfigDescription fr = test::ParseConfigOrDie("fr");
+ ConfigDescription de = test::ParseConfigOrDie("de");
ASSERT_THAT(out, ElementsAre(en, es, fr, de));
}
@@ -425,14 +417,14 @@ TEST(ArtifactTest, Simple) {
Artifact x86;
x86.abi_group = {"x86"};
- auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag);
+ auto x86_result = x86.ToArtifactName("something.${abi}.apk", &diag);
ASSERT_TRUE(x86_result);
EXPECT_EQ(x86_result.value(), "something.x86.apk");
Artifact arm;
arm.abi_group = {"armeabi-v7a"};
- auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag);
+ auto arm_result = arm.ToArtifactName("app.${abi}.apk", &diag);
ASSERT_TRUE(arm_result);
EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk");
}
@@ -447,8 +439,8 @@ TEST(ArtifactTest, Complex) {
artifact.locale_group = {"en-AU"};
artifact.android_sdk_group = {"26"};
- auto result =
- artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag);
+ auto result = artifact.ToArtifactName(
+ "app.${density}_${locale}_${feature}_${gl}.sdk${sdk}.${abi}.apk", &diag);
ASSERT_TRUE(result);
EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.sdk26.mips64.apk");
}
@@ -458,7 +450,7 @@ TEST(ArtifactTest, Missing) {
Artifact x86;
x86.abi_group = {"x86"};
- EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag));
+ EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", &diag));
EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag));
}
@@ -466,7 +458,7 @@ TEST(ArtifactTest, Empty) {
StdErrDiagnostics diag;
Artifact artifact;
- EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag));
+ EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", &diag));
EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag));
}
@@ -475,8 +467,8 @@ TEST(ArtifactTest, Repeated) {
Artifact artifact;
artifact.screen_density_group = {"mdpi"};
- EXPECT_TRUE(artifact.ToArtifactName("something.{density}.apk", &diag));
- EXPECT_FALSE(artifact.ToArtifactName("something.{density}.{density}.apk", &diag));
+ ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", &diag));
+ EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", &diag));
}
TEST(ArtifactTest, Nesting) {
@@ -484,36 +476,36 @@ TEST(ArtifactTest, Nesting) {
Artifact x86;
x86.abi_group = {"x86"};
- EXPECT_FALSE(x86.ToArtifactName("something.{abi{density}}.apk", &diag));
+ EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", &diag));
- const Maybe<std::string>& name = x86.ToArtifactName("something.{abi{abi}}.apk", &diag);
- EXPECT_TRUE(name);
- EXPECT_EQ(name.value(), "something.{abix86}.apk");
+ const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", &diag);
+ ASSERT_TRUE(name);
+ EXPECT_EQ(name.value(), "something.${abix86}.apk");
}
TEST(ArtifactTest, Recursive) {
StdErrDiagnostics diag;
Artifact artifact;
- artifact.device_feature_group = {"{gl}"};
+ artifact.device_feature_group = {"${gl}"};
artifact.gl_texture_group = {"glx1"};
- EXPECT_FALSE(artifact.ToArtifactName("app.{feature}.{gl}.apk", &diag));
+ EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag));
artifact.device_feature_group = {"df1"};
- artifact.gl_texture_group = {"{feature}"};
+ artifact.gl_texture_group = {"${feature}"};
{
- const auto& result = artifact.ToArtifactName("app.{feature}.{gl}.apk", &diag);
- EXPECT_TRUE(result);
- EXPECT_EQ(result.value(), "app.df1.{feature}.apk");
+ const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result.value(), "app.df1.${feature}.apk");
}
// This is an invalid case, but should be the only possible case due to the ordering of
// replacement.
- artifact.device_feature_group = {"{gl}"};
+ artifact.device_feature_group = {"${gl}"};
artifact.gl_texture_group = {"glx1"};
{
- const auto& result = artifact.ToArtifactName("app.{feature}.apk", &diag);
- EXPECT_TRUE(result);
+ const auto& result = artifact.ToArtifactName("app.${feature}.apk", &diag);
+ ASSERT_TRUE(result);
EXPECT_EQ(result.value(), "app.glx1.apk");
}
}
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
index 3f1341684912..ebb81519dceb 100644
--- a/tools/aapt2/filter/ConfigFilter.h
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -38,13 +38,9 @@ class IConfigFilter {
};
/**
- * Implements config axis matching. An axis is one component of a configuration,
- * like screen
- * density or locale. If an axis is specified in the filter, and the axis is
- * specified in
- * the configuration to match, they must be compatible. Otherwise the
- * configuration to match is
- * accepted.
+ * Implements config axis matching. An axis is one component of a configuration, like screen density
+ * or locale. If an axis is specified in the filter, and the axis is specified in the configuration
+ * to match, they must be compatible. Otherwise the configuration to match is accepted.
*
* Used when handling "-c" options.
*/
diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp
index fb75a4b4d7c1..db2e69fc90d3 100644
--- a/tools/aapt2/filter/Filter_test.cpp
+++ b/tools/aapt2/filter/Filter_test.cpp
@@ -25,22 +25,16 @@
namespace aapt {
namespace {
-TEST(FilterChainTest, EmptyChain) {
+TEST(FilterTest, FilterChain) {
FilterChain chain;
ASSERT_TRUE(chain.Keep("some/random/path"));
-}
-TEST(FilterChainTest, SingleFilter) {
- FilterChain chain;
chain.AddFilter(util::make_unique<PrefixFilter>("keep/"));
ASSERT_FALSE(chain.Keep("removed/path"));
ASSERT_TRUE(chain.Keep("keep/path/1"));
ASSERT_TRUE(chain.Keep("keep/path/2"));
-}
-TEST(FilterChainTest, MultipleFilters) {
- FilterChain chain;
chain.AddFilter(util::make_unique<PrefixFilter>("keep/"));
chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/"));
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
new file mode 100644
index 000000000000..f413ee960264
--- /dev/null
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 "MultiApkGenerator.h"
+
+#include <algorithm>
+#include <string>
+
+#include "androidfw/StringPiece.h"
+
+#include "LoadedApk.h"
+#include "configuration/ConfigurationParser.h"
+#include "filter/AbiFilter.h"
+#include "filter/Filter.h"
+#include "flatten/Archive.h"
+#include "process/IResourceTableConsumer.h"
+#include "split/TableSplitter.h"
+#include "util/Files.h"
+
+namespace aapt {
+
+using ::aapt::configuration::Artifact;
+using ::aapt::configuration::PostProcessingConfiguration;
+using ::android::StringPiece;
+
+MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context)
+ : apk_(apk), context_(context) {
+}
+
+bool MultiApkGenerator::FromBaseApk(const std::string& out_dir,
+ const PostProcessingConfiguration& config,
+ const TableFlattenerOptions& table_flattener_options) {
+ // TODO(safarmer): Handle APK version codes for the generated APKs.
+ // TODO(safarmer): Handle explicit outputs/generating an output file list for other tools.
+
+ const std::string& apk_path = apk_->GetSource().path;
+ const StringPiece ext = file::GetExtension(apk_path);
+ const std::string base_name = apk_path.substr(0, apk_path.rfind(ext.to_string()));
+
+ // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
+ for (const Artifact& artifact : config.artifacts) {
+ FilterChain filters;
+ TableSplitterOptions splits;
+ AxisConfigFilter axis_filter;
+
+ if (!artifact.name && !config.artifact_format) {
+ context_->GetDiagnostics()->Error(
+ DiagMessage() << "Artifact does not have a name and no global name template defined");
+ return false;
+ }
+
+ Maybe<std::string> artifact_name =
+ (artifact.name)
+ ? artifact.Name(base_name, ext.substr(1), context_->GetDiagnostics())
+ : artifact.ToArtifactName(config.artifact_format.value(), context_->GetDiagnostics(),
+ base_name, ext.substr(1));
+
+ if (!artifact_name) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "Could not determine split APK artifact name");
+ return false;
+ }
+
+ if (artifact.abi_group) {
+ const std::string& group_name = artifact.abi_group.value();
+
+ auto group = config.abi_groups.find(group_name);
+ // TODO: Remove validation when configuration parser ensures referential integrity.
+ if (group == config.abi_groups.end()) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced ABI group '"
+ << group_name << "'");
+ return false;
+ }
+ filters.AddFilter(AbiFilter::FromAbiList(group->second));
+ }
+
+ if (artifact.screen_density_group) {
+ const std::string& group_name = artifact.screen_density_group.value();
+
+ auto group = config.screen_density_groups.find(group_name);
+ // TODO: Remove validation when configuration parser ensures referential integrity.
+ if (group == config.screen_density_groups.end()) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
+ << group_name << "'");
+ return false;
+ }
+
+ const std::vector<ConfigDescription>& densities = group->second;
+ std::for_each(densities.begin(), densities.end(), [&](const ConfigDescription& c) {
+ splits.preferred_densities.push_back(c.density);
+ });
+ }
+
+ if (artifact.locale_group) {
+ const std::string& group_name = artifact.locale_group.value();
+ auto group = config.locale_groups.find(group_name);
+ // TODO: Remove validation when configuration parser ensures referential integrity.
+ if (group == config.locale_groups.end()) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
+ << group_name << "'");
+ return false;
+ }
+
+ const std::vector<ConfigDescription>& locales = group->second;
+ std::for_each(locales.begin(), locales.end(),
+ [&](const ConfigDescription& c) { axis_filter.AddConfig(c); });
+ splits.config_filter = &axis_filter;
+ }
+
+ std::unique_ptr<ResourceTable> table = apk_->GetResourceTable()->Clone();
+
+ TableSplitter splitter{{}, splits};
+ splitter.SplitTable(table.get());
+
+ std::string out = out_dir;
+ file::AppendPath(&out, artifact_name.value());
+
+ std::unique_ptr<IArchiveWriter> writer =
+ CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
+
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(DiagMessage() << "Writing output: " << out);
+ }
+
+ if (!apk_->WriteToArchive(context_, table.get(), table_flattener_options, &filters,
+ writer.get())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h
new file mode 100644
index 000000000000..f325d83053f4
--- /dev/null
+++ b/tools/aapt2/optimize/MultiApkGenerator.h
@@ -0,0 +1,53 @@
+/*
+ * 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_APKSPLITTER_H
+#define AAPT2_APKSPLITTER_H
+
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "configuration/ConfigurationParser.h"
+
+namespace aapt {
+
+/**
+ * Generates a set of APKs that are a subset of the original base APKs. Each of the new APKs contain
+ * only the resources and assets for an artifact in the configuration file.
+ */
+class MultiApkGenerator {
+ public:
+ MultiApkGenerator(LoadedApk* apk, IAaptContext* context);
+
+ /**
+ * Writes a set of APKs to the provided output directory. Each APK is a subset fo the base APK and
+ * represents an artifact in the post processing configuration.
+ */
+ bool FromBaseApk(const std::string& out_dir,
+ const configuration::PostProcessingConfiguration& config,
+ const TableFlattenerOptions& table_flattener_options);
+
+ private:
+ IDiagnostics* GetDiagnostics() {
+ return context_->GetDiagnostics();
+ }
+
+ LoadedApk* apk_;
+ IAaptContext* context_;
+};
+
+} // namespace aapt
+
+#endif // AAPT2_APKSPLITTER_H
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
new file mode 100644
index 000000000000..6c928d94e879
--- /dev/null
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 "optimize/MultiApkGenerator.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "LoadedApk.h"
+#include "ResourceTable.h"
+#include "configuration/ConfigurationParser.h"
+#include "filter/Filter.h"
+#include "flatten/Archive.h"
+#include "flatten/TableFlattener.h"
+#include "process/IResourceTableConsumer.h"
+#include "test/Context.h"
+#include "test/Test.h"
+
+namespace aapt {
+namespace {
+
+using ::aapt::configuration::Abi;
+using ::aapt::configuration::Artifact;
+using ::aapt::configuration::PostProcessingConfiguration;
+
+using ::testing::Eq;
+using ::testing::Return;
+using ::testing::_;
+
+class MockApk : public LoadedApk {
+ public:
+ MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){};
+ MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&,
+ FilterChain*, IArchiveWriter*));
+};
+
+TEST(MultiApkGeneratorTest, FromBaseApk) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:drawable/icon", "res/drawable-mdpi/icon.png",
+ test::ParseConfigOrDie("mdpi"))
+ .AddFileReference("android:drawable/icon", "res/drawable-hdpi/icon.png",
+ test::ParseConfigOrDie("hdpi"))
+ .AddFileReference("android:drawable/icon", "res/drawable-xhdpi/icon.png",
+ test::ParseConfigOrDie("xhdpi"))
+ .AddFileReference("android:drawable/icon", "res/drawable-xxhdpi/icon.png",
+ test::ParseConfigOrDie("xxhdpi"))
+ .AddSimple("android:string/one")
+ .Build();
+
+ MockApk apk{std::move(table)};
+
+ EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0);
+
+ test::Context ctx;
+ PostProcessingConfiguration empty_config;
+ TableFlattenerOptions table_flattener_options;
+
+ MultiApkGenerator generator{&apk, &ctx};
+ EXPECT_TRUE(generator.FromBaseApk("out", empty_config, table_flattener_options));
+
+ Artifact x64 = test::ArtifactBuilder()
+ .SetName("${basename}.x64.apk")
+ .SetAbiGroup("x64")
+ .SetLocaleGroup("en")
+ .SetDensityGroup("xhdpi")
+ .Build();
+
+ Artifact intel = test::ArtifactBuilder()
+ .SetName("${basename}.intel.apk")
+ .SetAbiGroup("intel")
+ .SetLocaleGroup("europe")
+ .SetDensityGroup("large")
+ .Build();
+
+ auto config = test::PostProcessingConfigurationBuilder()
+ .SetLocaleGroup("en", {"en"})
+ .SetLocaleGroup("europe", {"en", "fr", "de", "es"})
+ .SetAbiGroup("x64", {Abi::kX86_64})
+ .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86})
+ .SetDensityGroup("xhdpi", {"xhdpi"})
+ .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"})
+ .AddArtifact(x64)
+ .AddArtifact(intel)
+ .Build();
+
+ // Called once for each artifact.
+ EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true));
+ EXPECT_TRUE(generator.FromBaseApk("out", config, table_flattener_options));
+}
+
+} // namespace
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 27e13d81ff49..9d49ca6c0aa9 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -32,8 +32,7 @@
namespace aapt {
using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
-using ConfigDensityGroups =
- std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) {
ConfigDescription without_density = config;
@@ -51,8 +50,7 @@ class SplitValueSelector {
if (config.density == 0) {
density_independent_configs_.insert(config);
} else {
- density_dependent_config_to_density_map_[CopyWithoutDensity(config)] =
- config.density;
+ density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = config.density;
}
}
}
@@ -94,9 +92,7 @@ class SplitValueSelector {
ResourceConfigValue* best_value = nullptr;
for (ResourceConfigValue* this_value : related_values) {
- if (!best_value ||
- this_value->config.isBetterThan(best_value->config,
- &target_density)) {
+ if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) {
best_value = this_value;
}
}
@@ -120,9 +116,8 @@ class SplitValueSelector {
};
/**
- * Marking non-preferred densities as claimed will make sure the base doesn't
- * include them,
- * leaving only the preferred density behind.
+ * Marking non-preferred densities as claimed will make sure the base doesn't include them, leaving
+ * only the preferred density behind.
*/
static void MarkNonPreferredDensitiesAsClaimed(
const std::vector<uint16_t>& preferred_densities, const ConfigDensityGroups& density_groups,
@@ -161,8 +156,7 @@ bool TableSplitter::VerifySplitConstraints(IAaptContext* context) {
for (size_t i = 0; i < split_constraints_.size(); i++) {
for (size_t j = i + 1; j < split_constraints_.size(); j++) {
for (const ConfigDescription& config : split_constraints_[i].configs) {
- if (split_constraints_[j].configs.find(config) !=
- split_constraints_[j].configs.end()) {
+ if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) {
context->GetDiagnostics()->Error(DiagMessage()
<< "config '" << config
<< "' appears in multiple splits, "
@@ -193,28 +187,22 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
for (auto& entry : type->entries) {
if (options_.config_filter) {
// First eliminate any resource that we definitely don't want.
- for (std::unique_ptr<ResourceConfigValue>& config_value :
- entry->values) {
+ for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
if (!options_.config_filter->Match(config_value->config)) {
- // null out the entry. We will clean up and remove nulls at the
- // end for performance reasons.
+ // null out the entry. We will clean up and remove nulls at the end for performance
+ // reasons.
config_value.reset();
}
}
}
- // Organize the values into two separate buckets. Those that are
- // density-dependent
- // and those that are density-independent.
- // One density technically matches all density, it's just that some
- // densities
- // match better. So we need to be aware of the full set of densities to
- // make this
- // decision.
+ // Organize the values into two separate buckets. Those that are density-dependent and those
+ // that are density-independent. One density technically matches all density, it's just that
+ // some densities match better. So we need to be aware of the full set of densities to make
+ // this decision.
ConfigDensityGroups density_groups;
ConfigClaimedMap config_claimed_map;
- for (const std::unique_ptr<ResourceConfigValue>& config_value :
- entry->values) {
+ for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
if (config_value) {
config_claimed_map[config_value.get()] = false;
@@ -226,9 +214,8 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
}
}
- // First we check all the splits. If it doesn't match one of the splits,
- // we
- // leave it in the base.
+ // First we check all the splits. If it doesn't match one of the splits, we leave it in the
+ // base.
for (size_t idx = 0; idx < split_count; idx++) {
const SplitConstraints& split_constraint = split_constraints_[idx];
ResourceTable* split_table = splits_[idx].get();
@@ -240,20 +227,16 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
// No need to do any work if we selected nothing.
if (!selected_values.empty()) {
- // Create the same resource structure in the split. We do this
- // lazily because we might not have actual values for each
- // type/entry.
- ResourceTablePackage* split_pkg =
- split_table->FindPackage(pkg->name);
- ResourceTableType* split_type =
- split_pkg->FindOrCreateType(type->type);
+ // Create the same resource structure in the split. We do this lazily because we might
+ // not have actual values for each type/entry.
+ ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name);
+ ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
if (!split_type->id) {
split_type->id = type->id;
split_type->symbol_status = type->symbol_status;
}
- ResourceEntry* split_entry =
- split_type->FindOrCreateEntry(entry->name);
+ ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
if (!split_entry->id) {
split_entry->id = entry->id;
split_entry->symbol_status = entry->symbol_status;
@@ -262,8 +245,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
// Copy the selected values into the new Split Entry.
for (ResourceConfigValue* config_value : selected_values) {
ResourceConfigValue* new_config_value =
- split_entry->FindOrCreateValue(config_value->config,
- config_value->product);
+ split_entry->FindOrCreateValue(config_value->config, config_value->product);
new_config_value->value = std::unique_ptr<Value>(
config_value->value->Clone(&split_table->string_pool));
}
@@ -276,11 +258,9 @@ void TableSplitter::SplitTable(ResourceTable* original_table) {
&config_claimed_map);
}
- // All splits are handled, now check to see what wasn't claimed and
- // remove
- // whatever exists in other splits.
- for (std::unique_ptr<ResourceConfigValue>& config_value :
- entry->values) {
+ // All splits are handled, now check to see what wasn't claimed and remove whatever exists
+ // in other splits.
+ for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
if (config_value && config_claimed_map[config_value.get()]) {
// Claimed, remove from base.
config_value.reset();
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index b5795451e749..80e6adf134e6 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -212,5 +212,63 @@ std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* contex
return doc;
}
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAbiGroup(
+ const std::string& name, const std::vector<configuration::Abi>& abis) {
+ config_.abi_groups[name] = abis;
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetLocaleGroup(
+ const std::string& name, const std::vector<std::string>& locales) {
+ auto& group = config_.locale_groups[name];
+ for (const auto& locale : locales) {
+ group.push_back(ParseConfigOrDie(locale));
+ }
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetDensityGroup(
+ const std::string& name, const std::vector<std::string>& densities) {
+ auto& group = config_.screen_density_groups[name];
+ for (const auto& density : densities) {
+ group.push_back(ParseConfigOrDie(density));
+ }
+ return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+ const configuration::Artifact& artifact) {
+ config_.artifacts.push_back(artifact);
+ return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+ return config_;
+}
+
+ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) {
+ artifact_.name = {name};
+ return *this;
+}
+
+ArtifactBuilder& ArtifactBuilder::SetAbiGroup(const std::string& name) {
+ artifact_.abi_group = {name};
+ return *this;
+}
+
+ArtifactBuilder& ArtifactBuilder::SetDensityGroup(const std::string& name) {
+ artifact_.screen_density_group = {name};
+ return *this;
+}
+
+ArtifactBuilder& ArtifactBuilder::SetLocaleGroup(const std::string& name) {
+ artifact_.locale_group = {name};
+ return *this;
+}
+
+configuration::Artifact ArtifactBuilder::Build() {
+ return artifact_;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index d9f3912fb4c6..e8cefc1853b6 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -24,7 +24,9 @@
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "configuration/ConfigurationParser.h"
#include "process/IResourceTableConsumer.h"
+#include "test/Common.h"
#include "util/Maybe.h"
#include "xml/XmlDom.h"
@@ -149,6 +151,37 @@ std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
const android::StringPiece& str);
+class PostProcessingConfigurationBuilder {
+ public:
+ PostProcessingConfigurationBuilder() = default;
+
+ PostProcessingConfigurationBuilder& SetAbiGroup(const std::string& name,
+ const std::vector<configuration::Abi>& abis);
+ PostProcessingConfigurationBuilder& SetLocaleGroup(const std::string& name,
+ const std::vector<std::string>& locales);
+ PostProcessingConfigurationBuilder& SetDensityGroup(const std::string& name,
+ const std::vector<std::string>& densities);
+ PostProcessingConfigurationBuilder& AddArtifact(const configuration::Artifact& artifact);
+ configuration::PostProcessingConfiguration Build();
+
+ private:
+ configuration::PostProcessingConfiguration config_;
+};
+
+class ArtifactBuilder {
+ public:
+ ArtifactBuilder() = default;
+
+ ArtifactBuilder& SetName(const std::string& name);
+ ArtifactBuilder& SetAbiGroup(const std::string& name);
+ ArtifactBuilder& SetDensityGroup(const std::string& name);
+ ArtifactBuilder& SetLocaleGroup(const std::string& name);
+ configuration::Artifact Build();
+
+ private:
+ configuration::Artifact artifact_;
+};
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index d7b46caf8c94..d53c92f6a7b9 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -41,7 +41,7 @@ IDiagnostics* GetDiagnostics();
inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
ResourceNameRef ref;
- CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name";
+ CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str;
return ref.ToResourceName();
}