mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
99 lines
5.3 KiB
Prolog
99 lines
5.3 KiB
Prolog
# Keeps that exist so the instrumentation suite can run against the *release*
|
|
# build. They are applied to the shipping APK, which is a real trade-off worth
|
|
# stating plainly: the tested artifact is not byte-identical to an artifact
|
|
# built without them.
|
|
#
|
|
# It is the better side of the trade. The alternative is to run instrumentation
|
|
# against `debug` only, which leaves R8 — the step that has already broken this
|
|
# app twice, in ways no debug run can reproduce — entirely uncovered. What is
|
|
# kept here is a few kilobytes of framework plumbing containing no application
|
|
# logic and revealing nothing about the app.
|
|
#
|
|
# Why these keeps are needed at all: the instrumentation APK is minified in a
|
|
# separate R8 run that consumes the app's mapping file, and links against the
|
|
# app's classes at runtime. That arrangement fails in two ways.
|
|
#
|
|
# 1. The mapping file carries renames but not R8's parameter-permutation
|
|
# optimisation, so a permuted method resolves to the wrong descriptor from
|
|
# the test APK.
|
|
# 2. A class that only the test APK reaches is unreachable from the app's call
|
|
# graph, so R8 removes it from the app APK — and the test APK does not
|
|
# bundle it, because it is supposed to come from the app.
|
|
|
|
# The Kotlin runtime is where both failures land, because androidx.test is
|
|
# itself Kotlin and shares the app's copy of the stdlib. Keeping it wholesale
|
|
# rather than class by class is deliberate: each individual keep only moves the
|
|
# crash to the next facade the runner happens to touch, which is a list nobody
|
|
# can enumerate ahead of time. Three were hit before this rule replaced them:
|
|
#
|
|
# NoSuchMethodError: No static method f(Ljava/lang/Object;Ljava/lang/String;)V
|
|
# in class Lo6/k; (o6.k = kotlin.jvm.internal.Intrinsics,
|
|
# whose parameters R8 had permuted —
|
|
# every Kotlin class calls it on entry,
|
|
# so nothing ran at all)
|
|
# NoClassDefFoundError: Lkotlin/LazyKt; (androidx.test.platform.io.TestDirCalculator)
|
|
#
|
|
# Cost is roughly a megabyte of dex against a ~100 MB APK that is almost
|
|
# entirely native libraries.
|
|
-keep class kotlin.** { *; }
|
|
|
|
# `AndroidJUnitRunner.onCreate` traces its own startup. Nothing in the app
|
|
# reaches androidx.tracing, so R8 dropped it and the runner could not start:
|
|
#
|
|
# java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/tracing/Trace;
|
|
# at androidx.test.runner.AndroidJUnitRunner.onCreate(AndroidJUnitRunner.java:307)
|
|
-keep class androidx.tracing.** { *; }
|
|
|
|
# A third shape of the same problem, and the one worth understanding: the app
|
|
# only ever touches WorkManager through its implementation, so R8 vertically
|
|
# merged the abstract `androidx.work.WorkManager` into `WorkManagerImpl` and the
|
|
# abstract type stopped existing. It is absent from mapping.txt entirely rather
|
|
# than renamed. The download tests declare fields of the public types, so JUnit's
|
|
# field scan could not even construct the test class:
|
|
#
|
|
# java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/work/WorkManager;
|
|
# at org.junit.runners.model.TestClass.getSortedDeclaredFields(TestClass.java:77)
|
|
#
|
|
# Kept package-wide because the same merge can happen to any of the public API
|
|
# types the tests name (WorkInfo, Data, Constraints), and finding out one crash
|
|
# at a time costs a four-minute device round trip each.
|
|
-keep class androidx.work.** { *; }
|
|
|
|
# Everything above is framework plumbing. The last group is app code, and it is
|
|
# the one keep in this file that costs real fidelity: R8 no longer optimises
|
|
# these classes in the shipping build, so the suite no longer proves R8 handles
|
|
# them correctly. It is a narrow loss — the surfaces R8 has actually
|
|
# broken here are the FFI ones, and those are kept for functional reasons in
|
|
# proguard-rules.pro and stay under test.
|
|
#
|
|
# The trigger was `DownloadStateStore.clear()`. It is not dead code — AppViewModel
|
|
# calls it — but it has exactly one call site, so R8 inlined it and dropped the
|
|
# method. Only a caller that resolves it by name, which is to say a test, notices:
|
|
#
|
|
# java.lang.NoSuchMethodError: No virtual method clear()V
|
|
# in class Lio/narl/protonstream/download/c;
|
|
#
|
|
# Package-wide rather than class-by-class: Kotlin's top-level functions live in
|
|
# a synthesised `...Kt` facade that no source file names, so keeping the classes
|
|
# a test imports still leaves it missing `DownloadCoordinatorKt`.
|
|
-keep class io.narl.protonstream.download.** { *; }
|
|
|
|
# Same inlining, one layer out: the `wifiOnly` setter behind the download
|
|
# constraint.
|
|
# java.lang.NoSuchMethodError: No virtual method setWifiOnly(Z)V in class Li5/a;
|
|
-keep class io.narl.protonstream.settings.SettingsStore { *; }
|
|
|
|
# The live cases drive the player directly, so the same inlining applies to the
|
|
# playback classes they name.
|
|
-keep class io.narl.protonstream.playback.** { *; }
|
|
|
|
# `runBlocking` — the live cases drive suspending bridge calls from a JUnit
|
|
# thread. The app itself never blocks a thread on a coroutine, so R8 drops the
|
|
# facade that holds it:
|
|
#
|
|
# java.lang.NoClassDefFoundError: Failed resolution of: Lkotlinx/coroutines/BuildersKt;
|
|
#
|
|
# Kept narrowly rather than as `kotlinx.coroutines.**`: that library is large and
|
|
# on the app's hot paths, and pinning all of it would cost real optimisation.
|
|
-keep class kotlinx.coroutines.BuildersKt** { *; }
|