From 32faa4ced6c5f8e02c098d102ef170b8dbf20b62 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 25 Dec 2024 20:17:57 -0800 Subject: [PATCH] Redesign test APK architecture The test APK and the main APK share the same process and classloader, and in the non-hidden case, the test APK's classes take precedence over the ones in the main APK. This causes issues because the test APK and main APK share some dependencies, but don't always use the same version. This is especially problematic for the Kotlin stdlib and AndroidX dependencies. The solution here is to rely on R8's obfuscation feature and repackage all potentially conflicting classes into a separate package in the test APK. To ensure that the test classes are always using the same classes as the main APK, we have to directly implement all tests inside the main APK, making the test APK purely a "test runner with test dependencies". As a result, the test APK can only be used when built in release mode, because R8 no longer allow class obfuscation to be enabled when building for debug versions. --- app/test/build.gradle.kts | 9 +++++++-- app/test/proguard-rules.pro | 13 +++++++++++++ app/test/src/main/AndroidManifest.xml | 2 -- build.py | 16 +++++++++++++--- buildSrc/src/main/java/Setup.kt | 4 +++- scripts/test_common.sh | 2 +- 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 app/test/proguard-rules.pro diff --git a/app/test/build.gradle.kts b/app/test/build.gradle.kts index 1ed6b421b..92ae38c9f 100644 --- a/app/test/build.gradle.kts +++ b/app/test/build.gradle.kts @@ -10,14 +10,19 @@ android { applicationId = "com.topjohnwu.magisk.test" versionCode = 1 versionName = "1.0" + proguardFile("proguard-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = true + } } } setupAppCommon() dependencies { - compileOnly(project(":app:core")) - implementation(libs.test.runner) implementation(libs.test.rules) implementation(libs.test.junit) diff --git a/app/test/proguard-rules.pro b/app/test/proguard-rules.pro new file mode 100644 index 000000000..e4388a243 --- /dev/null +++ b/app/test/proguard-rules.pro @@ -0,0 +1,13 @@ +# Keep all test dependencies +-keep class org.junit.** { *; } +-keep class androidx.test.** { *; } + +# Make sure the classloader constructor is kept +-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { (); } + +# Repackage dependencies +-repackageclasses 'deps' +-allowaccessmodification + +# Keep attributes for stacktrace +-keepattributes * diff --git a/app/test/src/main/AndroidManifest.xml b/app/test/src/main/AndroidManifest.xml index 46e3bba77..fa7d5c44d 100644 --- a/app/test/src/main/AndroidManifest.xml +++ b/app/test/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - diff --git a/build.py b/build.py index 99de53552..f562de03c 100755 --- a/build.py +++ b/build.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import argparse +import copy import glob import lzma import multiprocessing @@ -455,9 +456,18 @@ def build_stub(): def build_test(): - header("* Building the test app") - apk = build_apk(":app:test") - header(f"Output: {apk}") + global args + args_bak = copy.copy(args) + # Test APK has to be built as release to prevent classname clash + args.release = True + try: + header("* Building the test app") + source = build_apk(":app:test") + target = source.parent / "test.apk" + mv(source, target) + header(f"Output: {target}") + finally: + args = args_bak ################ diff --git a/buildSrc/src/main/java/Setup.kt b/buildSrc/src/main/java/Setup.kt index d9c13a6eb..773439dbe 100644 --- a/buildSrc/src/main/java/Setup.kt +++ b/buildSrc/src/main/java/Setup.kt @@ -17,7 +17,6 @@ import org.gradle.api.Action import org.gradle.api.DefaultTask import org.gradle.api.JavaVersion import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Copy @@ -300,6 +299,9 @@ fun Project.setupAppCommon() { defaultConfig { targetSdk = 35 + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt") + ) } buildTypes { diff --git a/scripts/test_common.sh b/scripts/test_common.sh index 452eb9441..a9eb4e8e0 100644 --- a/scripts/test_common.sh +++ b/scripts/test_common.sh @@ -56,7 +56,7 @@ run_setup() { adb install -r -g out/app-${variant}.apk # Install the test app - adb install -r -g out/test-${variant}.apk + adb install -r -g out/test.apk # Run setup through the test app am_instrument 'Environment#setupMagisk'