From 2cc39cd7fd79d478113722cdb3b2205138862e01 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 16 Aug 2026 10:36:48 +0200 Subject: [PATCH] build(android): install the debug build alongside release as its own app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing a debug build meant uninstalling the real one first: same applicationId signed with a different key is INSTALL_FAILED_UPDATE_ INCOMPATIBLE, so every experiment cost the app's settings, credentials and offline cache. The debug build type now carries applicationIdSuffix ".debug" and versionNameSuffix "-debug", so it installs as com.dtourolle.jellytau.debug ("JellyTau Debug", 0.5.5-debug) with its own data directory — two independent apps on one device. Only the *application* id is suffixed. Kotlin classes stay in the `namespace` package com.dtourolle.jellytau, so the JNI loadClass lookups in player/android/mod.rs, the manifest entry and the R8 keep rules are untouched, and the FileProvider authority was already ${applicationId}-relative. Launcher names come from the appLabel / activityLabel manifestPlaceholders rather than resValue, which would collide with Tauri's generated strings.xml; release resolves them back to @string/app_name and merges byte-identical. deploy-android.sh reports the target package and explains an UPDATE_INCOMPATIBLE failure instead of leaving it raw; logcat.sh takes a debug|release argument (it was filtering on com.jellytau.app, a package that has never existed) and attaches by pid when the app is running. Verified: aapt2 badging on the built APK reports com.dtourolle.jellytau.debug / 0.5.5-debug / "JellyTau Debug", and the release manifest merge is unchanged. --- CLAUDE.md | 7 ++++ scripts/deploy-android.sh | 21 ++++++++++-- scripts/logcat.sh | 29 ++++++++++++++--- src-tauri/android/README_ANDROID_BUILD.md | 32 +++++++++++++++++++ src-tauri/android/app/build.gradle.kts | 20 ++++++++++++ .../android/src/main/AndroidManifest.xml | 10 ++++-- 6 files changed, 111 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 482dabd9..cff46826 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,6 +31,13 @@ bun run android:dev # build + deploy bun run android:logs # logcat ``` +The **debug** build type carries `applicationIdSuffix ".debug"`, so +`com.dtourolle.jellytau.debug` ("JellyTau Debug") installs *alongside* a release +build with its own data dir — never uninstall the release app to test a debug +one. Only the applicationId is suffixed; Kotlin classes stay in the `namespace` +package `com.dtourolle.jellytau`, so JNI lookups and R8 keep rules are +unaffected. See [README_ANDROID_BUILD.md](src-tauri/android/README_ANDROID_BUILD.md). + CI runs on **Gitea Actions** (`.gitea/workflows/`), not GitHub. Use the `gh` CLI only against the mirror if one exists; the canonical remote is `gitea.tourolle.paris`. diff --git a/scripts/deploy-android.sh b/scripts/deploy-android.sh index 794a8505..59d7569e 100755 --- a/scripts/deploy-android.sh +++ b/scripts/deploy-android.sh @@ -16,10 +16,15 @@ fi # Build type: debug or release (default: debug) BUILD_TYPE="${1:-debug}" +# The debug build carries applicationIdSuffix ".debug" (see +# src-tauri/android/app/build.gradle.kts), so it is a separate package and +# installs alongside a release build — no uninstall dance needed. if [ "$BUILD_TYPE" = "release" ]; then APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk" + APP_PACKAGE="com.dtourolle.jellytau" else APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk" + APP_PACKAGE="com.dtourolle.jellytau.debug" fi # Check if APK exists @@ -30,8 +35,20 @@ if [ ! -f "$APK_PATH" ]; then fi echo "📦 Installing APK: $APK_PATH" -adb install -r "$APK_PATH" +echo "📛 Package: $APP_PACKAGE" + +if ! adb install -r "$APK_PATH"; then + echo "" + echo "❌ Install failed." + echo " If it says INSTALL_FAILED_UPDATE_INCOMPATIBLE, an older build of" + echo " '$APP_PACKAGE' signed with a different key is still installed." + echo " Uninstall just that one and retry:" + echo " adb uninstall $APP_PACKAGE" + exit 1 +fi echo "" echo "✅ Deployment complete!" -echo "🚀 Launch the app on your device" +echo "🚀 Launching..." +adb shell monkey -p "$APP_PACKAGE" -c android.intent.category.LAUNCHER 1 > /dev/null 2>&1 \ + || echo " (auto-launch failed — start it from the launcher)" diff --git a/scripts/logcat.sh b/scripts/logcat.sh index 92fd9061..d6cbf07e 100755 --- a/scripts/logcat.sh +++ b/scripts/logcat.sh @@ -1,13 +1,34 @@ #!/bin/bash -# View Android logcat output filtered for the app +# View Android logcat output filtered for the app. +# +# Usage: ./scripts/logcat.sh [debug|release] (default: debug) +# +# The debug build has applicationIdSuffix ".debug" so it can be installed +# alongside a release build; pick the package to follow accordingly. set -e -APP_PACKAGE="com.jellytau.app" +BUILD_TYPE="${1:-debug}" + +if [ "$BUILD_TYPE" = "release" ]; then + APP_PACKAGE="com.dtourolle.jellytau" +else + APP_PACKAGE="com.dtourolle.jellytau.debug" +fi echo "📱 Showing logcat for $APP_PACKAGE" echo "Press Ctrl+C to stop" echo "" -# Filter logcat for the app's package name -adb logcat | grep -i "$APP_PACKAGE\|tauri\|rust" +# Prefer PID-scoped output when the app is running — it drops the noise that a +# text grep can't. Fall back to the old keyword filter when it isn't (so you can +# start the script first and then launch the app). +PID="$(adb shell pidof "$APP_PACKAGE" 2>/dev/null | tr -d '\r\n' | awk '{print $1}')" + +if [ -n "$PID" ]; then + echo " (attached to pid $PID)" + adb logcat --pid="$PID" +else + echo " (app not running — falling back to keyword filter)" + adb logcat | grep -i "$APP_PACKAGE\|jellytau\|tauri\|rust" +fi diff --git a/src-tauri/android/README_ANDROID_BUILD.md b/src-tauri/android/README_ANDROID_BUILD.md index 60ff6eea..44536954 100644 --- a/src-tauri/android/README_ANDROID_BUILD.md +++ b/src-tauri/android/README_ANDROID_BUILD.md @@ -41,6 +41,38 @@ When you need to modify Android/Kotlin files: - If you only edit `src-tauri/android/`, your changes won't be in the build - **You must edit both** (or edit source and copy to generated) +### Debug and release install side by side + +The **debug** build type sets `applicationIdSuffix = ".debug"` in +`app/build.gradle.kts`, so a debug build is a genuinely separate Android app: + +| | applicationId | launcher name | versionName | +|---|---|---|---| +| release | `com.dtourolle.jellytau` | jellytau | `0.5.5` | +| debug | `com.dtourolle.jellytau.debug` | JellyTau Debug | `0.5.5-debug` | + +That means: + +- **No uninstall step.** Debug builds are signed with the local auto-generated + `~/.android/debug.keystore`, release builds with the real key. Two different + keys on the *same* package is `INSTALL_FAILED_UPDATE_INCOMPATIBLE`; two + different packages is just two apps. +- Each has **its own data directory** — separate settings, credentials, + downloads and offline cache. A debug experiment cannot corrupt the state of + the build you actually use. +- Only the *application* id changes. Kotlin classes stay in the `namespace` + package `com.dtourolle.jellytau`, so the JNI class lookups in + `src-tauri/src/player/android/mod.rs`, the manifest `` entry and the + R8 keep rules in `proguard-jellytau.pro` are all unaffected. The FileProvider + authority is `${applicationId}.fileprovider`, so it follows the suffix + automatically. +- The launcher labels come from the `appLabel` / `activityLabel` + manifestPlaceholders (`AndroidManifest.xml` uses `${appLabel}`), *not* from + `resValue`, which would collide with Tauri's generated `strings.xml`. + +Follow the right log stream with `./scripts/logcat.sh [debug|release]` +(defaults to debug). + ### Key Files Player-related Kotlin files: diff --git a/src-tauri/android/app/build.gradle.kts b/src-tauri/android/app/build.gradle.kts index 02d01872..6bfa12cd 100644 --- a/src-tauri/android/app/build.gradle.kts +++ b/src-tauri/android/app/build.gradle.kts @@ -27,6 +27,11 @@ android { namespace = "com.dtourolle.jellytau" defaultConfig { manifestPlaceholders["usesCleartextTraffic"] = "false" + // Launcher/app names come from placeholders so the debug build can + // rename itself without touching the generated strings.xml (a + // resValue() override there would collide with Tauri's own entries). + manifestPlaceholders["appLabel"] = "@string/app_name" + manifestPlaceholders["activityLabel"] = "@string/main_activity_title" applicationId = "com.dtourolle.jellytau" minSdk = 24 targetSdk = 36 @@ -45,6 +50,21 @@ android { } buildTypes { getByName("debug") { + // Distinct applicationId so the debug build installs SIDE BY SIDE + // with a release/store install instead of demanding an uninstall + // (different signing keys on the same package = INSTALL_FAILED_ + // UPDATE_INCOMPATIBLE). It gets its own data dir, its own settings + // and its own offline cache — the two are fully independent apps. + // + // This changes only the *application* id. The Kotlin/JNI classes + // stay in the `namespace` package (com.dtourolle.jellytau), so the + // 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" + versionNameSuffix = "-debug" + manifestPlaceholders["appLabel"] = "JellyTau Debug" + manifestPlaceholders["activityLabel"] = "JellyTau Debug" manifestPlaceholders["usesCleartextTraffic"] = "true" isDebuggable = true isJniDebuggable = true diff --git a/src-tauri/android/src/main/AndroidManifest.xml b/src-tauri/android/src/main/AndroidManifest.xml index 0f1539bb..6eb892ac 100644 --- a/src-tauri/android/src/main/AndroidManifest.xml +++ b/src-tauri/android/src/main/AndroidManifest.xml @@ -11,6 +11,12 @@ (An earlier version of this file was a partial fragment on the assumption that Tauri merged it. It did not: the hardwareAccelerated flag it declared never reached any built APK. It is folded in properly below.) + + ${appLabel} / ${activityLabel} are manifestPlaceholders set in + app/build.gradle.kts: they resolve to @string/app_name and + @string/main_activity_title for release, and to "JellyTau Debug" for the + debug build type (which also carries applicationIdSuffix ".debug" so it + installs alongside a release build). --> @@ -26,7 +32,7 @@