[aapt2] Add "link" option to override styles instead of overlaying.
For normal app development, the desired linking semantics are:
* styleables - take union of all definitions
* all other resources - take last non-weak definition
This differs from the semantics needed in other scenarios, where
merging/overlaying styles is desired.
Bug: 134525082
Change-Id: Iac0c43ca2ecf1f3fddc9c3367f8914c12c9258e1
Tested: aapt2_tests
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4b97722..04d12f8 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1691,6 +1691,8 @@
TableMergerOptions table_merger_options;
table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_options.override_styles_instead_of_overlaying =
+ options_.override_styles_instead_of_overlaying;
table_merger_options.strict_visibility = options_.strict_visibility;
table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options);
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 5b0653e..37765f6 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -42,6 +42,7 @@
std::vector<std::string> assets_dirs;
bool output_to_directory = false;
bool auto_add_overlay = false;
+ bool override_styles_instead_of_overlaying = false;
OutputFormat output_format = OutputFormat::kApk;
// Java/Proguard options.
@@ -242,6 +243,10 @@
"Allows the addition of new resources in overlays without\n"
"<add-resource> tags.",
&options_.auto_add_overlay);
+ AddOptionalSwitch("--override-styles-instead-of-overlaying",
+ "Causes styles defined in -R resources to replace previous definitions\n"
+ "instead of merging into them\n",
+ &options_.override_styles_instead_of_overlaying);
AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
&options_.manifest_fixer_options.rename_manifest_package);
AddOptionalFlag("--rename-instrumentation-target-package",
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 32ed1dd..bf8f043 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -171,4 +171,86 @@
EXPECT_FALSE(file->WasCompressed());
}
-} // namespace aapt
\ No newline at end of file
+TEST_F(LinkTest, OverlayStyles) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 2);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor
+ EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background
+}
+
+TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ const std::string override_files_dir = GetTestPath("compiled-override");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:textColor">#123</item>
+ </style>
+ </resources>)",
+ compiled_files_dir, &diag));
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"),
+ R"(<resources>
+ <style name="MyStyle">
+ <item name="android:background">#456</item>
+ </style>
+ </resources>)",
+ override_files_dir, &diag));
+
+
+ const std::string out_apk = GetTestPath("out.apk");
+ std::vector<std::string> link_args = {
+ "--manifest", GetDefaultManifest(kDefaultPackageName),
+ "--override-styles-instead-of-overlaying",
+ "-o", out_apk,
+ };
+ const auto override_files = file::FindFiles(override_files_dir, &diag);
+ for (const auto &override_file : override_files.value()) {
+ link_args.push_back("-R");
+ link_args.push_back(file::BuildPath({override_files_dir, override_file}));
+ }
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+ const Style* actual_style = test::GetValue<Style>(
+ apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle");
+ ASSERT_NE(actual_style, nullptr);
+ ASSERT_EQ(actual_style->entries.size(), 1);
+ EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c0802e6..c25e450 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -172,19 +172,22 @@
//
// Styleables and Styles don't simply overlay each other, their definitions merge and accumulate.
// If both values are Styleables/Styles, we just merge them into the existing value.
-static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming,
- StringPool* pool) {
+static ResourceTable::CollisionResult ResolveMergeCollision(
+ bool override_styles_instead_of_overlaying, Value* existing, Value* incoming,
+ StringPool* pool) {
if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) {
if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) {
// Styleables get merged.
existing_styleable->MergeWith(incoming_styleable);
return ResourceTable::CollisionResult::kKeepOriginal;
}
- } else if (Style* existing_style = ValueCast<Style>(existing)) {
- if (Style* incoming_style = ValueCast<Style>(incoming)) {
- // Styles get merged.
- existing_style->MergeWith(incoming_style, pool);
- return ResourceTable::CollisionResult::kKeepOriginal;
+ } else if (!override_styles_instead_of_overlaying) {
+ if (Style* existing_style = ValueCast<Style>(existing)) {
+ if (Style* incoming_style = ValueCast<Style>(incoming)) {
+ // Styles get merged.
+ existing_style->MergeWith(incoming_style, pool);
+ return ResourceTable::CollisionResult::kKeepOriginal;
+ }
}
}
// Delegate to the default handler.
@@ -194,6 +197,7 @@
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
const ResourceNameRef& res_name,
bool overlay,
+ bool override_styles_instead_of_overlaying,
ResourceConfigValue* dst_config_value,
ResourceConfigValue* src_config_value,
StringPool* pool) {
@@ -204,7 +208,8 @@
CollisionResult collision_result;
if (overlay) {
- collision_result = ResolveMergeCollision(dst_value, src_value, pool);
+ collision_result =
+ ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
} else {
collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
}
@@ -272,9 +277,9 @@
ResourceConfigValue* dst_config_value = dst_entry->FindValue(
src_config_value->config, src_config_value->product);
if (dst_config_value) {
- CollisionResult collision_result =
- MergeConfigValue(context_, res_name, overlay, dst_config_value,
- src_config_value.get(), &master_table_->string_pool);
+ CollisionResult collision_result = MergeConfigValue(
+ context_, res_name, overlay, options_.override_styles_instead_of_overlaying,
+ dst_config_value, src_config_value.get(), &master_table_->string_pool);
if (collision_result == CollisionResult::kConflict) {
error = true;
continue;
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 51305cf..a35a134 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -37,6 +37,8 @@
bool auto_add_overlay = false;
// If true, resource overlays with conflicting visibility are not allowed.
bool strict_visibility = false;
+ // If true, styles specified via "aapt2 link -R" completely replace any previously-seen resources.
+ bool override_styles_instead_of_overlaying = false;
};
// TableMerger takes resource tables and merges all packages within the tables that have the same
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 9dd31e6..0be4ccf 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -436,6 +436,53 @@
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
}
+TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) {
+ std::unique_ptr<ResourceTable> table_a =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/foo", ResourceId(0x1234)).Build())
+ .AddValue("com.app.a:style/Theme",
+ test::StyleBuilder()
+ .AddItem("com.app.a:attr/foo", ResourceUtils::MakeBool(false))
+ .Build())
+ .Build();
+ std::unique_ptr<ResourceTable> table_b =
+ test::ResourceTableBuilder()
+ .SetPackageId("com.app.a", 0x7f)
+ .AddValue(
+ "com.app.a:styleable/MyWidget",
+ test::StyleableBuilder().AddItem("com.app.a:attr/bar", ResourceId(0x5678)).Build())
+ .AddValue(
+ "com.app.a:style/Theme",
+ test::StyleBuilder().AddItem("com.app.a:attr/bat", util::make_unique<Id>()).Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ options.override_styles_instead_of_overlaying = true;
+ TableMerger merger(context_.get(), &final_table, options);
+ ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
+
+ // Styleables are always overlaid
+ std::unique_ptr<Styleable> expected_styleable = test::StyleableBuilder()
+ // The merged Styleable has its entries ordered by name.
+ .AddItem("com.app.a:attr/bar", ResourceId(0x5678))
+ .AddItem("com.app.a:attr/foo", ResourceId(0x1234))
+ .Build();
+ const Styleable* actual_styleable =
+ test::GetValue<Styleable>(&final_table, "com.app.a:styleable/MyWidget");
+ ASSERT_NE(actual_styleable, nullptr);
+ EXPECT_TRUE(actual_styleable->Equals(expected_styleable.get()));
+ // Style should be overridden
+ const Style* actual_style = test::GetValue<Style>(&final_table, "com.app.a:style/Theme");
+ ASSERT_NE(actual_style, nullptr);
+ EXPECT_TRUE(actual_style->Equals(test::GetValue<Style>(table_b.get(), "com.app.a:style/Theme")));
+}
+
TEST_F(TableMergerTest, SetOverlayable) {
auto overlayable = std::make_shared<Overlayable>("CustomizableResources",
"overlay://customization");