summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/aapt2/Android.bp1
-rw-r--r--tools/aapt2/cmd/Link.cpp24
-rw-r--r--tools/aapt2/cmd/Link.h6
-rw-r--r--tools/aapt2/link/ResourceExcluder.cpp89
-rw-r--r--tools/aapt2/link/ResourceExcluder.h48
-rw-r--r--tools/aapt2/link/ResourceExcluder_test.cpp221
6 files changed, 389 insertions, 0 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 8f752871355f..bc3a9a1c88c8 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -108,6 +108,7 @@ cc_library_host_static {
"link/ProductFilter.cpp",
"link/PrivateAttributeMover.cpp",
"link/ReferenceLinker.cpp",
+ "link/ResourceExcluder.cpp",
"link/TableMerger.cpp",
"link/XmlCompatVersioner.cpp",
"link/XmlNamespaceRemover.cpp",
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 729447ee1de5..8463046a80eb 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -59,6 +59,7 @@
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
#include "link/ReferenceLinker.h"
+#include "link/ResourceExcluder.h"
#include "link/TableMerger.h"
#include "link/XmlCompatVersioner.h"
#include "optimize/ResourceDeduper.h"
@@ -1828,6 +1829,29 @@ class Linker {
}
}
+ if (!options_.exclude_configs_.empty()) {
+ std::vector<ConfigDescription> excluded_configs;
+
+ for (auto& config_string : options_.exclude_configs_) {
+ ConfigDescription config_description;
+
+ if (!ConfigDescription::Parse(config_string, &config_description)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to parse --excluded-configs "
+ << config_string);
+ return 1;
+ }
+
+ excluded_configs.push_back(config_description);
+ }
+
+ ResourceExcluder excluder(excluded_configs);
+ if (!excluder.Consume(context_, &final_table_)) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed excluding configurations");
+ return 1;
+ }
+ }
+
if (!options_.no_resource_deduping) {
ResourceDeduper deduper;
if (!deduper.Consume(context_, &final_table_)) {
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index f70470acb3d8..590a6bb19e30 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -82,6 +82,9 @@ struct LinkOptions {
std::vector<SplitConstraints> split_constraints;
std::vector<std::string> split_paths;
+ // Configurations to exclude
+ std::vector<std::string> exclude_configs_;
+
// Stable ID options.
std::unordered_map<ResourceName, ResourceId> stable_id_map;
Maybe<std::string> resource_id_map_path;
@@ -255,6 +258,9 @@ class LinkCommand : public Command {
"Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
"On Windows, use a semicolon ';' separator instead.",
&split_args_);
+ AddOptionalFlagList("--exclude-configs",
+ "Excludes values of resources whose configs contain the specified qualifiers.",
+ &options_.exclude_configs_);
AddOptionalSwitch("--debug-mode",
"Inserts android:debuggable=\"true\" in to the application node of the\n"
"manifest, making the application debuggable even on production devices.",
diff --git a/tools/aapt2/link/ResourceExcluder.cpp b/tools/aapt2/link/ResourceExcluder.cpp
new file mode 100644
index 000000000000..2555995dfc8e
--- /dev/null
+++ b/tools/aapt2/link/ResourceExcluder.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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 "link/ResourceExcluder.h"
+
+#include <algorithm>
+
+#include "DominatorTree.h"
+#include "ResourceTable.h"
+
+using android::ConfigDescription;
+
+namespace aapt {
+
+namespace {
+
+void RemoveIfExcluded(std::set<std::pair<ConfigDescription, int>>& excluded_configs_,
+ IAaptContext* context,
+ ResourceEntry* entry,
+ ResourceConfigValue* value) {
+ const ConfigDescription& config = value->config;
+
+ // If this entry is a default, ignore
+ if (config == ConfigDescription::DefaultConfig()) {
+ return;
+ }
+
+ for (auto& excluded_pair : excluded_configs_) {
+
+ const ConfigDescription& excluded_config = excluded_pair.first;
+ const int& excluded_diff = excluded_pair.second;
+
+ // Check whether config contains all flags in excluded config
+ int node_diff = config.diff(excluded_config);
+ int masked_diff = excluded_diff & node_diff;
+
+ if (masked_diff == 0) {
+ if (context->IsVerbose()) {
+ context->GetDiagnostics()->Note(
+ DiagMessage(value->value->GetSource())
+ << "excluded resource \""
+ << entry->name
+ << "\" with config "
+ << config.toString());
+ }
+ value->value = {};
+ return;
+ }
+ }
+}
+
+} // namespace
+
+bool ResourceExcluder::Consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ for (auto& value : entry->values) {
+ RemoveIfExcluded(excluded_configs_, context, entry.get(), value.get());
+ }
+
+ // Erase the values that were removed.
+ entry->values.erase(
+ std::remove_if(
+ entry->values.begin(), entry->values.end(),
+ [](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
+ return val == nullptr || val->value == nullptr;
+ }),
+ entry->values.end());
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ResourceExcluder.h b/tools/aapt2/link/ResourceExcluder.h
new file mode 100644
index 000000000000..1890b34ae7c5
--- /dev/null
+++ b/tools/aapt2/link/ResourceExcluder.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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 AAPT_LINK_RESOURCEEXCLUDER_H
+#define AAPT_LINK_RESOURCEEXCLUDER_H
+
+#include "android-base/macros.h"
+
+#include "process/IResourceTableConsumer.h"
+
+using android::ConfigDescription;
+
+namespace aapt {
+
+// Removes excluded configs from resources.
+class ResourceExcluder : public IResourceTableConsumer {
+ public:
+ explicit ResourceExcluder(std::vector<ConfigDescription>& excluded_configs) {
+ for (auto& config: excluded_configs) {
+ int diff_from_default = config.diff(ConfigDescription::DefaultConfig());
+ excluded_configs_.insert(std::pair(config, diff_from_default));
+ }
+ }
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceExcluder);
+
+ std::set<std::pair<ConfigDescription, int>> excluded_configs_;
+};
+
+} // namespace aapt
+
+#endif // AAPT_LINK_RESOURCEEXCLUDER_H
diff --git a/tools/aapt2/link/ResourceExcluder_test.cpp b/tools/aapt2/link/ResourceExcluder_test.cpp
new file mode 100644
index 000000000000..c9d01e49aeae
--- /dev/null
+++ b/tools/aapt2/link/ResourceExcluder_test.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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 "link/ResourceExcluder.h"
+
+#include "ResourceTable.h"
+#include "test/Test.h"
+
+using ::aapt::test::HasValue;
+using ::android::ConfigDescription;
+using ::testing::Not;
+
+namespace {
+
+ConfigDescription BuildArg(const std::string arg) {
+ ConfigDescription config_description;
+ ConfigDescription::Parse(arg, &config_description);
+ return config_description;
+}
+
+std::vector<ConfigDescription> BuildArgList(const std::string arg) {
+ ConfigDescription config_description;
+ ConfigDescription::Parse(arg, &config_description);
+ return { config_description };
+}
+
+} // namespace
+
+namespace aapt {
+
+TEST(ResourceExcluderTest, NonMatchConfigNotExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto fr_config = test::ParseConfigOrDie("fr");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .Build();
+
+ auto args = BuildArgList("en");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, HasValue("android:string/test", fr_config));
+}
+
+TEST(ResourceExcluderTest, ExactConfigExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto fr_config = test::ParseConfigOrDie("fr");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .Build();
+
+ auto args = BuildArgList("fr");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_config)));
+}
+
+TEST(ResourceExcluderTest, MoreSpecificConfigExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto fr_land_config = test::ParseConfigOrDie("fr-land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, fr_land_config, "fr-land")
+ .Build();
+
+ auto args = BuildArgList("fr");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_land_config)));
+}
+
+TEST(ResourceExcluderTest, MultipleMoreSpecificConfigExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto night_config = test::ParseConfigOrDie("night");
+ auto fr_config = test::ParseConfigOrDie("fr");
+ auto fr_land_config = test::ParseConfigOrDie("fr-land");
+ auto fr_night_config = test::ParseConfigOrDie("fr-night");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, night_config, "night")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .AddString("android:string/test", ResourceId{}, fr_land_config, "fr-land")
+ .AddString("android:string/test", ResourceId{}, fr_night_config, "fr-night")
+ .Build();
+
+ auto args = BuildArgList("fr");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, HasValue("android:string/test", night_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_config)));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_land_config)));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_night_config)));
+}
+
+TEST(ResourceExcluderTest, MultipleConfigsExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto night_config = test::ParseConfigOrDie("night");
+ auto fr_config = test::ParseConfigOrDie("fr");
+ auto fr_land_config = test::ParseConfigOrDie("fr-land");
+ auto fr_night_config = test::ParseConfigOrDie("fr-night");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, night_config, "night")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .AddString("android:string/test", ResourceId{}, fr_land_config, "fr-land")
+ .AddString("android:string/test", ResourceId{}, fr_night_config, "fr-night")
+ .Build();
+
+ std::vector<ConfigDescription> args;
+ args.push_back(BuildArg("land"));
+ args.push_back(BuildArg("night"));
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", night_config)));
+ EXPECT_THAT(table, HasValue("android:string/test", fr_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_land_config)));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_night_config)));
+}
+
+TEST(ResourceExcluderTest, LessSpecificConfigNotExcluded) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto fr_config = test::ParseConfigOrDie("fr");
+ auto fr_land_config = test::ParseConfigOrDie("fr-land");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .AddString("android:string/test", ResourceId{}, fr_land_config, "fr-land")
+ .Build();
+
+ auto args = BuildArgList("fr-land");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, HasValue("android:string/test", fr_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_land_config)));
+}
+
+TEST(ResourceExcluderTest, LowerPrecedenceStillExcludes) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto fr_config = test::ParseConfigOrDie("fr");
+ auto fr_night_config = test::ParseConfigOrDie("fr-night");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, fr_config, "fr")
+ .AddString("android:string/test", ResourceId{}, fr_night_config, "fr-night")
+ .Build();
+
+ // "night" is lower precedence than "fr"
+ auto args = BuildArgList("night");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, HasValue("android:string/test", fr_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", fr_night_config)));
+}
+
+TEST(ResourceExcluderTest, OnlyExcludesSpecificTier) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ ConfigDescription default_config;
+ auto mdpi_config = test::ParseConfigOrDie("mdpi");
+ auto hdpi_config = test::ParseConfigOrDie("hdpi");
+ auto xhdpi_config = test::ParseConfigOrDie("xhdpi");
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/test", ResourceId{}, default_config, "default")
+ .AddString("android:string/test", ResourceId{}, mdpi_config, "mdpi")
+ .AddString("android:string/test", ResourceId{}, hdpi_config, "hdpi")
+ .AddString("android:string/test", ResourceId{}, xhdpi_config, "xhdpi")
+ .Build();
+
+ auto args = BuildArgList("hdpi");
+
+ ASSERT_TRUE(ResourceExcluder(args).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/test", default_config));
+ EXPECT_THAT(table, HasValue("android:string/test", mdpi_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/test", hdpi_config)));
+ EXPECT_THAT(table, HasValue("android:string/test", xhdpi_config));
+}
+
+} // namespace aapt