ci(android): gate name-based reachability on an R8-minified variant

R8 only ever ran on `release`, so every automated gate in this repository
exercised code the shipped APK does not contain. Reflective lookups, JNI
callbacks and native library loading can all break under shrinking while
`flutter test`, the Robolectric suites and `connectedDebugAndroidTest`
stay green — which is exactly how #1703 shipped, with the bundled FFmpeg
audio renderer shrunk out of release builds for TrueHD and DTS-HD.

Add a `minified` build type that inherits release's shrinker
configuration but stays debuggable and debug-signed, so it is an ordinary
test artifact and never a publishable one. Three integration details
took a run each to find: the Flutter plugin copies app build types into
every plugin module, so library-level shrinking deleted the plugin entry
points that only GeneratedPluginRegistrant references; the harness must
not be shrunk or the runner disappears; and androidx.test has to survive
in the app under test, or the runner cannot link its own supertype and
the run reports zero tests instead of failing.

Instrumentation still defaults to `debug`, because only one build type
can host androidTest and the existing playback suites drive media3
builder APIs the app never calls, which R8 shrinks legitimately. The new
reachability test opts into the minified variant instead and touches no
builder API, so the only keeps it depends on are the ones under test.
Emptying proguard-rules.pro was verified to fail it.
This commit is contained in:
edde746
2026-07-28 20:13:40 +02:00
parent a183c17c3b
commit 8aa836d106
7 changed files with 181 additions and 1 deletions
+29
View File
@@ -15,6 +15,11 @@ ANDROID_15_INSTRUMENTATION_CLASSES = (
"com.edde746.plezy.exoplayer.PlezyAudioModePlaybackTest"
)
ANDROID_15_INSTRUMENTATION_TARGET = "android-15-instrumentation"
# Kept separate from the suites above: only one build type can host androidTest, and
# those suites drive media3 builder APIs the app itself never calls, which R8 shrinks
# legitimately. This class asserts only name-based reachability (#1703).
ANDROID_R8_REACHABILITY_CLASSES = "androidx.media3.decoder.ffmpeg.FfmpegDecoderReachabilityTest"
ANDROID_R8_REACHABILITY_TARGET = "android-r8-reachability"
GROUPS: dict[str, tuple[tuple[str, ...], ...]] = {
@@ -194,6 +199,26 @@ def run_android_15_instrumentation() -> None:
)
def run_android_r8_reachability() -> None:
print("==> Android R8 reachability", flush=True)
# The `minified` build type runs R8 over the app under test, so a keep rule that stops
# covering a reflective lookup, a JNI callback or a native library load fails here
# instead of shipping. No other gate in this repository runs R8 at all.
#
# compileFlutterBuildMinified is deliberately not excluded: CI only prebuilds the
# debug APK, so this variant has no Flutter outputs to reuse.
run_maestro._run_checked(
(
"android/gradlew",
"-p",
"android",
":app:connectedMinifiedAndroidTest",
"-Pplezy.testBuildType=minified",
f"-Pandroid.testInstrumentationRunnerArguments.class={ANDROID_R8_REACHABILITY_CLASSES}",
)
)
def run_recipes(recipes: tuple[tuple[str, ...], ...]) -> int:
failed = False
for arguments in recipes:
@@ -223,6 +248,9 @@ def run_target(name: str, *, disposable_emulator: bool = False) -> int:
if name == ANDROID_15_INSTRUMENTATION_TARGET:
run_android_15_instrumentation()
return 0
if name == ANDROID_R8_REACHABILITY_TARGET:
run_android_r8_reachability()
return 0
return run_group(name)
@@ -233,6 +261,7 @@ def main(argv: Sequence[str] | None = None) -> int:
choices=(
*GROUPS,
ANDROID_15_INSTRUMENTATION_TARGET,
ANDROID_R8_REACHABILITY_TARGET,
*DESTRUCTIVE_MANUAL_TARGETS,
),
)