diff options
| -rw-r--r-- | tools/aapt2/Android.bp | 1 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link.cpp | 24 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link.h | 6 | ||||
| -rw-r--r-- | tools/aapt2/link/ResourceExcluder.cpp | 89 | ||||
| -rw-r--r-- | tools/aapt2/link/ResourceExcluder.h | 48 | ||||
| -rw-r--r-- | tools/aapt2/link/ResourceExcluder_test.cpp | 221 |
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 |