Add <staging-public-group-final> to aapt2
To allow apps that compiled against a pre-release SDK to continue
working for a period of time after API finalization, a new tag,
<staging-public-group-final>, has been added to aapt2.
When finalizing the framework resource API, converting
<staging-public-group> tags to <staging-public-group-final> will
cause aapt2 to generate the resource table so that there is a resource
entry for the old non-finalized (staged) resource ID and another entry
for the finalized resource ID of newly finalized resources. This allows
an application that compiled against the pre-release SDK to continue
resolving resources using pre-release resource IDs.
All references to pre-release resource IDs will be rewritten to their
finalized resource IDs through the information stored in the new staged
alias chunk. This allows applications compiled against
<staging-public-group> resources to use the newly finalized
resource ID without re-compilation.
When an application is re-compiled against the SDK with
<staging-public-group-final> tags, the application will use the
finalized resource IDs.
This change limits the use of the alias chunk to the framework for S.
Bug: 183411356
Test: aapt2_test
Change-Id: Iba1c3033c3c2f32de8e4a19b58d3921c971092c4
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index df31087..3950f33 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -97,15 +97,15 @@
static bool EmitResourceConfigValueDiff(
IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
- const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
+ const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
- const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) {
+ const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
Value* value_a = config_value_a->value.get();
Value* value_b = config_value_b->value.get();
if (!value_a->Equals(value_b)) {
std::stringstream str_stream;
- str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+ str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config << " does not match:\n";
value_a->Print(&str_stream);
str_stream << "\n vs \n";
@@ -118,32 +118,32 @@
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
const ResourceTablePackageView& pkg_a,
- const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
- LoadedApk* apk_b, const ResourceTablePackageView& pkg_b,
+ const ResourceTableTypeView& type_a,
+ const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
+ const ResourceTablePackageView& pkg_b,
const ResourceTableTypeView& type_b,
- const ResourceEntry* entry_b) {
+ const ResourceTableEntryView& entry_b) {
bool diff = false;
- for (const std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
- auto config_value_b = entry_b->FindValue(config_value_a->config);
+ for (const ResourceConfigValue* config_value_a : entry_a.values) {
+ auto config_value_b = entry_b.FindValue(config_value_a->config);
if (!config_value_b) {
std::stringstream str_stream;
- str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+ str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
- diff |=
- EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
- apk_b, pkg_b, type_b, entry_b, config_value_b);
+ diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
+ apk_b, pkg_b, type_b, entry_b, config_value_b);
}
}
// Check for any newly added config values.
- for (const std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
- auto config_value_a = entry_a->FindValue(config_value_b->config);
+ for (const ResourceConfigValue* config_value_b : entry_b.values) {
+ auto config_value_a = entry_a.FindValue(config_value_b->config);
if (!config_value_a) {
std::stringstream str_stream;
- str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name
+ str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
<< " config=" << config_value_b->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
@@ -164,36 +164,35 @@
if (entry_b_iter == type_b.entries.end()) {
// Type A contains a type that type B does not have.
std::stringstream str_stream;
- str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name;
+ str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
} else if (entry_a_iter == type_a.entries.end()) {
// Type B contains a type that type A does not have.
std::stringstream str_stream;
- str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/"
- << (*entry_b_iter)->name;
+ str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
const auto& entry_a = *entry_a_iter;
const auto& entry_b = *entry_b_iter;
- if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
+ if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
std::stringstream str_stream;
- str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+ str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different visibility (";
- if (entry_b->visibility.staged_api) {
+ if (entry_b.visibility.staged_api) {
str_stream << "STAGED ";
}
- if (entry_b->visibility.level == Visibility::Level::kPublic) {
+ if (entry_b.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
- if (entry_a->visibility.staged_api) {
+ if (entry_a.visibility.staged_api) {
str_stream << "STAGED ";
}
- if (entry_a->visibility.level == Visibility::Level::kPublic) {
+ if (entry_a.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@@ -201,19 +200,19 @@
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
- } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
- entry_b->id)) {
+ } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
+ entry_b.id)) {
std::stringstream str_stream;
- str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
+ str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different public ID (";
- if (entry_b->id) {
- str_stream << "0x" << std::hex << entry_b->id.value();
+ if (entry_b.id) {
+ str_stream << "0x" << std::hex << entry_b.id.value();
} else {
str_stream << "none";
}
str_stream << " vs ";
- if (entry_a->id) {
- str_stream << "0x " << std::hex << entry_a->id.value();
+ if (entry_a.id) {
+ str_stream << "0x " << std::hex << entry_a.id.value();
} else {
str_stream << "none";
}
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3118eb8..430c184 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -402,8 +402,39 @@
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
}
-TEST_F(LinkTest, StagedAndroidApi) {
- StdErrDiagnostics diag;
+struct SourceXML {
+ std::string res_file_path;
+ std::string file_contents;
+};
+
+static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+ LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
+ TemporaryDir res_dir;
+ TemporaryDir compiled_res_dir;
+ for (auto& source_file : source_files) {
+ ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
+ source_file.file_contents, compiled_res_dir.path, diag));
+ }
+ ASSERT_TRUE(fixture->Link(
+ link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
+}
+
+static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
+ const std::string& java_root_path, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
+ auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
+
+ auto android_link_args = LinkCommandBuilder(fixture)
+ .SetManifestFile(android_manifest)
+ .AddParameter("--private-symbols", "com.android.internal")
+ .AddParameter("--java", java_root_path);
+
+ BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
+}
+
+static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, IDiagnostics* diag) {
const std::string android_values =
R"(<resources>
<public type="attr" name="finalized_res" id="0x01010001"/>
@@ -413,6 +444,10 @@
<public name="staged_s_res" />
</staging-public-group>
+ <staging-public-group type="string" first-id="0x01fd0080">
+ <public name="staged_s_string" />
+ </staging-public-group>
+
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
<staging-public-group type="attr" first-id="0x01ff0049">
<public name="staged_s2_res" />
@@ -423,46 +458,90 @@
<public name="staged_t_res" />
</staging-public-group>
- <staging-public-group type="string" first-id="0x01fd0072">
- <public name="staged_t_string" />
+ <attr name="finalized_res" />
+ <attr name="staged_s_res" />
+ <attr name="staged_s2_res" />
+ <attr name="staged_t_res" />
+ <string name="staged_s_string">Hello</string>
+ </resources>)";
+
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
+ CommandTestFixture* fixture, IDiagnostics* diag) {
+ const std::string android_values =
+ R"(<resources>
+ <public type="attr" name="finalized_res" id="0x01010001"/>
+ <public type="attr" name="staged_s_res" id="0x01010002"/>
+ <public type="attr" name="staged_s2_res" id="0x01010003"/>
+ <public type="string" name="staged_s_string" id="0x01020000"/>
+
+ <!-- S staged attributes (support staged resources in the same type id) -->
+ <staging-public-group-final type="attr" first-id="0x01010050">
+ <public name="staged_s_res" />
+ </staging-public-group-final>
+
+ <staging-public-group-final type="string" first-id="0x01fd0080">
+ <public name="staged_s_string" />
+ </staging-public-group-final>
+
+ <!-- SV2 staged attributes (support staged resources in a separate type id) -->
+ <staging-public-group-final type="attr" first-id="0x01ff0049">
+ <public name="staged_s2_res" />
+ </staging-public-group-final>
+
+ <!-- T staged attributes (support staged resources in multiple separate type ids) -->
+ <staging-public-group type="attr" first-id="0x01fe0063">
+ <public name="staged_t_res" />
</staging-public-group>
<attr name="finalized_res" />
<attr name="staged_s_res" />
<attr name="staged_s2_res" />
<attr name="staged_t_res" />
- <string name="staged_t_string">Hello</string>
+ <string name="staged_s_string">Hello</string>
</resources>)";
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+ BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
+ const std::string& sdk_path, CommandTestFixture* fixture,
+ IDiagnostics* diag) {
const std::string app_values =
R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
<attr name="bar" />
+ <style name="MyStyle">
+ <item name="android:staged_s_res">@android:string/staged_s_string</item>
+ </style>
<declare-styleable name="ClientStyleable">
<attr name="android:finalized_res" />
<attr name="android:staged_s_res" />
<attr name="bar" />
</declare-styleable>
+ <public name="MyStyle" type="style" id="0x7f020000" />
</resources>)";
- const std::string android_res = GetTestPath("android-res");
- ASSERT_TRUE(
- CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
+ SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
+ auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
+
+ auto app_link_args = LinkCommandBuilder(fixture)
+ .SetManifestFile(app_manifest)
+ .AddParameter("--java", java_path)
+ .AddParameter("-I", sdk_path);
+
+ BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
+}
+
+TEST_F(LinkTest, StagedAndroidApi) {
+ StdErrDiagnostics diag;
const std::string android_apk = GetTestPath("android.apk");
- const std::string android_java = GetTestPath("android_java");
- // clang-format off
- auto android_manifest = ManifestBuilder(this)
- .SetPackageName("android")
- .Build();
-
- auto android_link_args = LinkCommandBuilder(this)
- .SetManifestFile(android_manifest)
- .AddParameter("--private-symbols", "com.android.internal")
- .AddParameter("--java", android_java)
- .AddCompiledResDir(android_res, &diag)
- .Build(android_apk);
- // clang-format on
- ASSERT_TRUE(Link(android_link_args, &diag));
+ const std::string android_java = GetTestPath("android-java");
+ BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
const std::string android_r_java = android_java + "/android/R.java";
std::string android_r_contents;
@@ -473,33 +552,17 @@
HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
EXPECT_THAT(
android_r_contents,
+ HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
+ EXPECT_THAT(
+ android_r_contents,
HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
- EXPECT_THAT(
- android_r_contents,
- HasSubstr("public static final int staged_t_string; static { staged_t_string=0x01fd0072; }"));
-
- // Build an app that uses the framework attribute in a declare-styleable
- const std::string client_res = GetTestPath("app-res");
- ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
const std::string app_apk = GetTestPath("app.apk");
- const std::string app_java = GetTestPath("app_java");
- // clang-format off
- auto app_manifest = ManifestBuilder(this)
- .SetPackageName("com.example.app")
- .Build();
-
- auto app_link_args = LinkCommandBuilder(this)
- .SetManifestFile(app_manifest)
- .AddParameter("--java", app_java)
- .AddParameter("-I", android_apk)
- .AddCompiledResDir(client_res, &diag)
- .Build(app_apk);
- // clang-format on
- ASSERT_TRUE(Link(app_link_args, &diag));
+ const std::string app_java = GetTestPath("app-java");
+ BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
const std::string client_r_java = app_java + "/com/example/app/R.java";
std::string client_r_contents;
@@ -520,6 +583,10 @@
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01010050));
+ result = am.GetResourceId("android:string/staged_s_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01fd0080));
+
result = am.GetResourceId("android:attr/staged_s2_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01ff0049));
@@ -527,10 +594,88 @@
result = am.GetResourceId("android:attr/staged_t_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01fe0063));
+}
- result = am.GetResourceId("android:string/staged_t_string");
+TEST_F(LinkTest, FinalizedAndroidApi) {
+ StdErrDiagnostics diag;
+ const std::string android_apk = GetTestPath("android.apk");
+ const std::string android_java = GetTestPath("android-java");
+ BuildFinalizedSDK(android_apk, android_java, this, &diag);
+
+ const std::string android_r_java = android_java + "/android/R.java";
+ std::string android_r_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
+ EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
+ EXPECT_THAT(
+ android_r_contents,
+ HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
+ ;
+
+ // Build an application against the non-finalized SDK and then load it into an AssetManager with
+ // the finalized SDK.
+ const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
+ const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
+ BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
+
+ const std::string app_apk = GetTestPath("app.apk");
+ const std::string app_java = GetTestPath("app-java");
+ BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
+
+ android::AssetManager2 am;
+ auto android_asset = android::ApkAssets::Load(android_apk);
+ auto app_against_non_final = android::ApkAssets::Load(app_apk);
+ ASSERT_THAT(android_asset, NotNull());
+ ASSERT_THAT(app_against_non_final, NotNull());
+ ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
+
+ auto result = am.GetResourceId("android:attr/finalized_res");
ASSERT_TRUE(result.has_value());
- EXPECT_THAT(*result, Eq(0x01fd0072));
+ EXPECT_THAT(*result, Eq(0x01010001));
+
+ result = am.GetResourceId("android:attr/staged_s_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010002));
+
+ result = am.GetResourceId("android:string/staged_s_string");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01020000));
+
+ result = am.GetResourceId("android:attr/staged_s2_res");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_THAT(*result, Eq(0x01010003));
+
+ {
+ auto style = am.GetBag(0x7f020000);
+ ASSERT_TRUE(style.has_value());
+
+ auto& entry = (*style)->entries[0];
+ EXPECT_THAT(entry.key, Eq(0x01010002));
+ EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+ EXPECT_THAT(entry.value.data, Eq(0x01020000));
+ }
+
+ // Re-compile the application against the finalized SDK and then load it into an AssetManager with
+ // the finalized SDK.
+ const std::string app_apk_respin = GetTestPath("app-respin.apk");
+ const std::string app_java_respin = GetTestPath("app-respin-java");
+ BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
+
+ auto app_against_final = android::ApkAssets::Load(app_apk_respin);
+ ASSERT_THAT(app_against_final, NotNull());
+ ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
+
+ {
+ auto style = am.GetBag(0x7f020000);
+ ASSERT_TRUE(style.has_value());
+
+ auto& entry = (*style)->entries[0];
+ EXPECT_THAT(entry.key, Eq(0x01010002));
+ EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+ EXPECT_THAT(entry.value.data, Eq(0x01020000));
+ }
}
TEST_F(LinkTest, MacroSubstitution) {