build(android): add a side-by-side release build for validating R8

R8 has broken release APKs here before by stripping the JNI-loaded player
and security classes, and the only way to reproduce that was to build with
the real signing key and clobber the install you actually use.

`./scripts/build-and-deploy.sh release --device --debug` now builds a
fully minified release APK — exactly what ships — into the .debug
applicationId slot, signed with the local debug keystore:

  release            com.dtourolle.jellytau        0.5.5
  release --debug    com.dtourolle.jellytau.debug  0.5.5-debug-release
  debug              com.dtourolle.jellytau.debug  0.5.5-debug

It shares the applicationId *and* the signature with the plain debug
build, so the two replace each other cleanly rather than colliding, and
the versionName suffix says which is currently installed. No real key is
needed, so the side-by-side path deliberately skips
write-keystore-properties.sh.

The flag reaches Gradle as JT_SIDE_BY_SIDE=1. CI never sets it, and the
release manifest merges byte-identical without it — verified both ways
through processUniversalReleaseMainManifest.

deploy-android.sh and build-and-deploy.sh learned the flag too, since the
APK path is unchanged but the package to launch is not.
This commit is contained in:
2026-08-16 10:42:59 +02:00
parent 886cbcb29a
commit 521acc75fd
6 changed files with 106 additions and 21 deletions
+21 -1
View File
@@ -22,6 +22,15 @@ val keystoreProperties = Properties().apply {
}
}
// Side-by-side release: set by `scripts/build-android.sh release --debug`, which
// exports JT_SIDE_BY_SIDE=1. It puts a fully R8-minified release build into the
// debug applicationId slot, signed with the local debug keystore — so you can
// test what minification actually produces (R8 stripping JNI-loaded classes has
// broken release APKs here before) without the real signing key and without
// uninstalling your working install. Unset in CI, so distributable release
// builds are untouched.
val sideBySideRelease = System.getenv("JT_SIDE_BY_SIDE").let { it == "1" || it == "true" }
android {
compileSdk = 36
namespace = "com.dtourolle.jellytau"
@@ -76,7 +85,18 @@ android {
}
}
getByName("release") {
if (keystoreProperties.getProperty("storeFile") != null) {
if (sideBySideRelease) {
// Same slot, name and version scheme as the debug build type,
// plus "-release" so you can tell from Settings > Apps which of
// the two is currently sitting there. Signed with the debug
// keystore: it shares a signature with the debug build, so the
// two replace each other cleanly instead of colliding.
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug-release"
manifestPlaceholders["appLabel"] = "JellyTau Debug"
manifestPlaceholders["activityLabel"] = "JellyTau Debug"
signingConfig = signingConfigs.getByName("debug")
} else if (keystoreProperties.getProperty("storeFile") != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true