build(android): install the debug build alongside release as its own app
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 <service> 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.
This commit is contained in:
@@ -31,6 +31,13 @@ bun run android:dev # build + deploy
|
|||||||
bun run android:logs # logcat
|
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
|
CI runs on **Gitea Actions** (`.gitea/workflows/`), not GitHub. Use the `gh` CLI
|
||||||
only against the mirror if one exists; the canonical remote is
|
only against the mirror if one exists; the canonical remote is
|
||||||
`gitea.tourolle.paris`.
|
`gitea.tourolle.paris`.
|
||||||
|
|||||||
@@ -16,10 +16,15 @@ fi
|
|||||||
# Build type: debug or release (default: debug)
|
# Build type: debug or release (default: debug)
|
||||||
BUILD_TYPE="${1:-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
|
if [ "$BUILD_TYPE" = "release" ]; then
|
||||||
APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk"
|
APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk"
|
||||||
|
APP_PACKAGE="com.dtourolle.jellytau"
|
||||||
else
|
else
|
||||||
APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk"
|
APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk"
|
||||||
|
APP_PACKAGE="com.dtourolle.jellytau.debug"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if APK exists
|
# Check if APK exists
|
||||||
@@ -30,8 +35,20 @@ if [ ! -f "$APK_PATH" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📦 Installing APK: $APK_PATH"
|
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 ""
|
||||||
echo "✅ Deployment complete!"
|
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)"
|
||||||
|
|||||||
+25
-4
@@ -1,13 +1,34 @@
|
|||||||
#!/bin/bash
|
#!/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
|
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 "📱 Showing logcat for $APP_PACKAGE"
|
||||||
echo "Press Ctrl+C to stop"
|
echo "Press Ctrl+C to stop"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Filter logcat for the app's package name
|
# Prefer PID-scoped output when the app is running — it drops the noise that a
|
||||||
adb logcat | grep -i "$APP_PACKAGE\|tauri\|rust"
|
# 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
|
||||||
|
|||||||
@@ -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
|
- 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)
|
- **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 `<service>` 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
|
### Key Files
|
||||||
|
|
||||||
Player-related Kotlin files:
|
Player-related Kotlin files:
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ android {
|
|||||||
namespace = "com.dtourolle.jellytau"
|
namespace = "com.dtourolle.jellytau"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
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"
|
applicationId = "com.dtourolle.jellytau"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
@@ -45,6 +50,21 @@ android {
|
|||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("debug") {
|
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
|
||||||
|
// <service> 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"
|
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isJniDebuggable = true
|
isJniDebuggable = true
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
(An earlier version of this file was a partial <application> fragment on the
|
(An earlier version of this file was a partial <application> fragment on the
|
||||||
assumption that Tauri merged it. It did not: the hardwareAccelerated flag it
|
assumption that Tauri merged it. It did not: the hardwareAccelerated flag it
|
||||||
declared never reached any built APK. It is folded in properly below.)
|
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).
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@@ -26,7 +32,7 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="${appLabel}"
|
||||||
android:theme="@style/Theme.jellytau"
|
android:theme="@style/Theme.jellytau"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
@@ -34,7 +40,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|density"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|density"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:label="@string/main_activity_title"
|
android:label="${activityLabel}"
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
|
|||||||
Reference in New Issue
Block a user