diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 6ac40384..58fe5c9a 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -128,6 +128,44 @@ else bun run tauri android build --apk --debug "${TARGET_ARGS[@]}" fi +# The applicationId the APK actually carries — not the one build.gradle.kts asks +# for. `tauri android build` rewrites the debug `buildTypes` block in the +# generated gradle file to inject its keepDebugSymbols entries, and that rewrite +# used to drop `applicationIdSuffix` with it, silently producing a debug APK +# under the release applicationId. Installing that over a real release build +# fails with INSTALL_FAILED_UPDATE_INCOMPATIBLE, whose only obvious remedy is +# uninstalling the release app and losing its data — so this fails the build +# instead. The suffix now lives outside the rewritten block (see +# src-tauri/android/app/build.gradle.kts); this checks that it survived. +assert_application_id() { + local variant="$1" expected="$2" + local metadata="src-tauri/gen/android/app/build/outputs/apk/universal/$variant/output-metadata.json" + + [ -f "$metadata" ] || return 0 + + local actual + actual=$(sed -n 's/.*"applicationId"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata" | head -1) + + if [ -n "$actual" ] && [ "$actual" != "$expected" ]; then + echo "" + echo "❌ APK applicationId is '$actual', expected '$expected'." + echo " A build meant for the side-by-side slot came out under the" + echo " release applicationId; installing it would collide with a real" + echo " install. Check that the applicationIdSuffix at the bottom of" + echo " src-tauri/android/app/build.gradle.kts survived into" + echo " src-tauri/gen/android/app/build.gradle.kts." + exit 1 + fi +} + +if [ "$BUILD_TYPE" = "debug" ]; then + assert_application_id debug "com.dtourolle.jellytau.debug" +elif [ "$SIDE_BY_SIDE" = "1" ]; then + assert_application_id release "com.dtourolle.jellytau.debug" +else + assert_application_id release "com.dtourolle.jellytau" +fi + echo "" echo "✅ APK build complete!" echo "📱 APK location: src-tauri/gen/android/app/build/outputs/apk/" diff --git a/src-tauri/android/app/build.gradle.kts b/src-tauri/android/app/build.gradle.kts index c0c13301..2682dfb5 100644 --- a/src-tauri/android/app/build.gradle.kts +++ b/src-tauri/android/app/build.gradle.kts @@ -73,7 +73,9 @@ android { // fully-qualified class names Rust looks up over JNI, the manifest // entry and the R8 keep rules are all unaffected. The // FileProvider authority is already ${applicationId}-relative. - applicationIdSuffix = ".debug" + // + // The suffix itself is applied AFTER this block -- see the bottom of + // this file. It cannot live here. versionNameSuffix = "-debug" manifestPlaceholders["appLabel"] = "JellyTau Debug" manifestPlaceholders["activityLabel"] = "JellyTau Debug" @@ -127,6 +129,30 @@ android { } } +// The debug applicationId suffix, applied from OUTSIDE the `buildTypes` block. +// +// `tauri android build` rewrites the `getByName("debug")` block in the *generated* +// copy of this file (src-tauri/gen/android/app/build.gradle.kts) to inject its +// `jniLibs.keepDebugSymbols` entries -- you can see the damage in the generated +// file, where `packaging {` ends up with the first injected line welded onto it. +// That rewrite drops `applicationIdSuffix` and nothing else: `versionNameSuffix` +// and the manifest placeholders beside it survive. It happens after +// sync-android-sources.sh has copied this file into place and before Gradle +// configures, so no amount of syncing can beat it. +// +// The result was a debug APK whose applicationId was plain +// `com.dtourolle.jellytau`, colliding with a real release install: +// INSTALL_FAILED_UPDATE_INCOMPATIBLE, with the only obvious way out being to +// uninstall the release app and lose its data. The `sideBySideRelease` suffix in +// the *release* build type is untouched by the same rewrite, which is why that +// path kept working and this one did not. +// +// A top-level statement is not inside the block the rewriter looks for, so it +// survives. scripts/build-android.sh asserts the built applicationId afterwards, +// so a future CLI that reaches further fails the build instead of shipping a +// colliding APK. +android.buildTypes.getByName("debug").applicationIdSuffix = ".debug" + rust { rootDirRel = "../../../" }