diff options
Diffstat (limited to 'tools/aapt2/cmd/Link_test.cpp')
-rw-r--r-- | tools/aapt2/cmd/Link_test.cpp | 468 |
1 files changed, 467 insertions, 1 deletions
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 062dd8eac975..430c184ef87d 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -14,14 +14,19 @@ * limitations under the License. */ -#include "AppInfo.h" #include "Link.h" +#include <android-base/file.h> + +#include "AppInfo.h" #include "LoadedApk.h" #include "test/Test.h" using testing::Eq; +using testing::HasSubstr; +using testing::IsNull; using testing::Ne; +using testing::NotNull; namespace aapt { @@ -44,6 +49,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); AssertLoadXml(apk.get(), data.get(), &tree); @@ -70,6 +77,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); AssertLoadXml(apk.get(), data.get(), &tree); @@ -205,6 +214,8 @@ TEST_F(LinkTest, OverlayStyles) { ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + const Style* actual_style = test::GetValue<Style>( apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); ASSERT_NE(actual_style, nullptr); @@ -247,6 +258,8 @@ TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) { ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + const Style* actual_style = test::GetValue<Style>( apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); ASSERT_NE(actual_style, nullptr); @@ -317,4 +330,457 @@ TEST_F(LinkTest, AppInfoWithUsesSplit) { ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag)); } +TEST_F(LinkTest, SharedLibraryAttributeRJava) { + StdErrDiagnostics diag; + const std::string lib_values = + R"(<resources> + <attr name="foo"/> + <public type="attr" name="foo" id="0x00010001"/> + <declare-styleable name="LibraryStyleable"> + <attr name="foo" /> + </declare-styleable> + </resources>)"; + + const std::string client_values = + R"(<resources> + <attr name="bar" /> + <declare-styleable name="ClientStyleable"> + <attr name="com.example.lib:foo" /> + <attr name="bar" /> + </declare-styleable> + </resources>)"; + + // Build a library with a public attribute + const std::string lib_res = GetTestPath("library-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), lib_values, lib_res, &diag)); + + const std::string lib_apk = GetTestPath("library.apk"); + const std::string lib_java = GetTestPath("library_java"); + // clang-format off + auto lib_manifest = ManifestBuilder(this) + .SetPackageName("com.example.lib") + .Build(); + + auto lib_link_args = LinkCommandBuilder(this) + .SetManifestFile(lib_manifest) + .AddFlag("--shared-lib") + .AddParameter("--java", lib_java) + .AddCompiledResDir(lib_res, &diag) + .Build(lib_apk); + // clang-format on + ASSERT_TRUE(Link(lib_link_args, &diag)); + + const std::string lib_r_java = lib_java + "/com/example/lib/R.java"; + std::string lib_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(lib_r_java, &lib_r_contents)); + EXPECT_THAT(lib_r_contents, HasSubstr(" public static int foo=0x00010001;")); + EXPECT_THAT(lib_r_contents, HasSubstr(" com.example.lib.R.attr.foo")); + + // Build a client that uses the library attribute in a declare-styleable + const std::string client_res = GetTestPath("client-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), client_values, client_res, &diag)); + + const std::string client_apk = GetTestPath("client.apk"); + const std::string client_java = GetTestPath("client_java"); + // clang-format off + auto client_manifest = ManifestBuilder(this) + .SetPackageName("com.example.client") + .Build(); + + auto client_link_args = LinkCommandBuilder(this) + .SetManifestFile(client_manifest) + .AddParameter("--java", client_java) + .AddParameter("-I", lib_apk) + .AddCompiledResDir(client_res, &diag) + .Build(client_apk); + // clang-format on + ASSERT_TRUE(Link(client_link_args, &diag)); + + const std::string client_r_java = client_java + "/com/example/client/R.java"; + std::string client_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000")); +} + +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"/> + + <!-- S staged attributes (support staged resources in the same type id) --> + <staging-public-group type="attr" first-id="0x01010050"> + <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" /> + </staging-public-group> + + <!-- 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_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_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>)"; + + 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"); + BuildNonFinalizedSDK(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; 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; }")); + + const std::string app_apk = GetTestPath("app.apk"); + 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; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" 0x01010001, android.R.attr.staged_s_res, 0x7f010000")); + + // Test that the resource ids of staged and non-staged resource can be retrieved + android::AssetManager2 am; + auto android_asset = android::ApkAssets::Load(android_apk); + ASSERT_THAT(android_asset, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get()})); + + auto result = am.GetResourceId("android:attr/finalized_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010001)); + + result = am.GetResourceId("android:attr/staged_s_res"); + 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)); + + result = am.GetResourceId("android:attr/staged_t_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fe0063)); +} + +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(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) { + StdErrDiagnostics diag; + const std::string values = + R"(<resources xmlns:an="http://schemas.android.com/apk/res/android"> + <macro name="is_enabled">true</macro> + <macro name="deep_is_enabled">@macro/is_enabled</macro> + <macro name="attr_ref">?is_enabled_attr</macro> + <macro name="raw_string">Hello World!</macro> + <macro name="android_ref">@an:color/primary_text_dark</macro> + + <attr name="is_enabled_attr" /> + <public type="attr" name="is_enabled_attr" id="0x7f010000"/> + + <string name="is_enabled_str">@macro/is_enabled</string> + <bool name="is_enabled_bool">@macro/deep_is_enabled</bool> + + <array name="my_array"> + <item>@macro/is_enabled</item> + </array> + + <style name="MyStyle"> + <item name="android:background">@macro/attr_ref</item> + <item name="android:fontFamily">@macro/raw_string</item> + </style> + </resources>)"; + + const std::string xml_values = + R"(<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:background="@macro/android_ref" + android:fontFamily="@macro/raw_string"> + </SomeLayout>)"; + + // Build a library with a public attribute + const std::string lib_res = GetTestPath("test-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), values, lib_res, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/layout/layout.xml"), xml_values, lib_res, &diag)); + + const std::string lib_apk = GetTestPath("test.apk"); + // clang-format off + auto lib_link_args = LinkCommandBuilder(this) + .SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build()) + .AddCompiledResDir(lib_res, &diag) + .AddFlag("--no-auto-version") + .Build(lib_apk); + // clang-format on + ASSERT_TRUE(Link(lib_link_args, &diag)); + + auto apk = LoadedApk::LoadApkFromPath(lib_apk, &diag); + ASSERT_THAT(apk, NotNull()); + + // Test that the type flags determines the value type + auto actual_bool = + test::GetValue<BinaryPrimitive>(apk->GetResourceTable(), "com.test:bool/is_enabled_bool"); + ASSERT_THAT(actual_bool, NotNull()); + EXPECT_EQ(android::Res_value::TYPE_INT_BOOLEAN, actual_bool->value.dataType); + EXPECT_EQ(0xffffffffu, actual_bool->value.data); + + auto actual_str = + test::GetValue<String>(apk->GetResourceTable(), "com.test:string/is_enabled_str"); + ASSERT_THAT(actual_str, NotNull()); + EXPECT_EQ(*actual_str->value, "true"); + + // Test nested data structures + auto actual_array = test::GetValue<Array>(apk->GetResourceTable(), "com.test:array/my_array"); + ASSERT_THAT(actual_array, NotNull()); + EXPECT_THAT(actual_array->elements.size(), Eq(1)); + + auto array_el_ref = ValueCast<BinaryPrimitive>(actual_array->elements[0].get()); + ASSERT_THAT(array_el_ref, NotNull()); + EXPECT_THAT(array_el_ref->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN)); + EXPECT_THAT(array_el_ref->value.data, Eq(0xffffffffu)); + + auto actual_style = test::GetValue<Style>(apk->GetResourceTable(), "com.test:style/MyStyle"); + ASSERT_THAT(actual_style, NotNull()); + EXPECT_THAT(actual_style->entries.size(), Eq(2)); + + { + auto style_el = ValueCast<Reference>(actual_style->entries[0].value.get()); + ASSERT_THAT(style_el, NotNull()); + EXPECT_THAT(style_el->reference_type, Eq(Reference::Type::kAttribute)); + EXPECT_THAT(style_el->id, Eq(0x7f010000)); + } + + { + auto style_el = ValueCast<String>(actual_style->entries[1].value.get()); + ASSERT_THAT(style_el, NotNull()); + EXPECT_THAT(*style_el->value, Eq("Hello World!")); + } + + // Test substitution in compiled xml files + auto xml = apk->LoadXml("res/layout/layout.xml", &diag); + ASSERT_THAT(xml, NotNull()); + + auto& xml_attrs = xml->root->attributes; + ASSERT_THAT(xml_attrs.size(), Eq(2)); + + auto attr_value = ValueCast<Reference>(xml_attrs[0].compiled_value.get()); + ASSERT_THAT(attr_value, NotNull()); + EXPECT_THAT(attr_value->reference_type, Eq(Reference::Type::kResource)); + EXPECT_THAT(attr_value->id, Eq(0x01060001)); + + EXPECT_THAT(xml_attrs[1].compiled_value.get(), IsNull()); + EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!")); +} + } // namespace aapt |