diff --git a/CLAUDE.md b/CLAUDE.md index acd065b50..987f97150 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,8 @@ keeps it for returns within five seconds, which leaves a stale query on screen. ## Building +On the desktop: + JAVA_HOME=~/jdk21 ANDROID_HOME=~/android-sdk \ ./gradlew :app:app:assembleDefaultRelease @@ -57,8 +59,56 @@ The `release` build type signs with the `local` signing config, which reads `key in the repository root (git-ignored) or the `KEYSTORE_FILE`, `KEYSTORE_PASSWORD`, `KEY_ALIAS` and `KEY_PASSWORD` environment variables. The desktop key is `~/android-keystores/kvaesitso-release.jks`; the phone has its own copy under -`~/android-keystores/` in the Termux home. Termux itself has no Android SDK, so build there only -after installing one (or build on the desktop and copy the APK over). +`~/android-keystores/` in the Termux home. + +### Building on the phone itself (Termux) + +The phone builds this project without a desktop. `scripts/ondevice.sh` wraps the loop: + + scripts/ondevice.sh build # assemble the debug APK + scripts/ondevice.sh install # build, then pm install it (root) + scripts/ondevice.sh start # launch the debug build + scripts/ondevice.sh logs # follow logcat of the debug build (Ctrl-C to stop) + scripts/ondevice.sh crash # newest crash report for the debug build + scripts/ondevice.sh release # assemble the signed release APK + +Or call Gradle directly: `./gradlew :app:app:assembleDefaultDebug`. A full build takes about 13 +minutes (984 tasks), later ones 20-30 seconds; the release build (R8 plus `lintVital`) takes about +11 minutes. Run `termux-wake-lock` first so the screen lock does not throttle it. + +The **debug** build is `de.mm20.launcher2.debug` (`isDebuggable`, its own empty data directory), +so it installs next to the real launcher and can be logged with +`su -c 'logcat --pid=$(pidof de.mm20.launcher2.debug)'`. Installing, reinstalling or uninstalling +that package cannot affect the real launcher: different package, different data directory. + +> **Never uninstall `de.mm20.launcher2.release`.** It is the launcher the phone actually runs, and +> the config (widgets, pages, tags, search settings) lives in its data directory +> `/data/user/0/de.mm20.launcher2.release`, mostly under `files/datastore`. `pm install -r` of a +> build signed with the same key keeps that data, but it does replace the running launcher, so do it +> deliberately, after backing the data directory up. Reinstalling the *debug* build is always the +> safe way to try a change. + +To try a fix in the real launcher with the real data, build the release APK and `pm install -r` it: +same package (`de.mm20.launcher2.release`) and same key, so it replaces the installed build in +place. The release APK is minified by R8, so this is the slow build. `scripts/ondevice.sh release` +only *builds* it; it never installs anything. + +#### The SDK on the phone + +`~/android-sdk` (see `~/CLAUDE.md`) provides everything Gradle needs: + +* `platforms/android-37.0` — the compileSdk 37 platform, installed with `sdkmanager`. +* `build-tools/36.0.0` — the version AGP 9.3.1 defaults to. Google only ships x86_64 build-tools, + so the three native tools are symlinks into `build-tools/34.0.4`, which is AndroidIDE's aarch64 + build of the AOSP tools: `aapt2`, `zipalign` and `aidl`. Everything else in build-tools + (`d8`, `apksigner`, jars) is Java and runs as is. +* `~/.gradle/gradle.properties` points `android.aapt2FromMavenOverride` at Termux's own aarch64 + `aapt2` (2.20, newer than 34.0.4's) and lowers the Gradle/Kotlin daemon heaps to fit the phone. +* `local.properties` in the repo root sets `sdk.dir`; it is git-ignored. The SDK itself is + machine-wide, not project-specific: another project just needs `termux-sdk init `, and + `termux-sdk install "platforms;android-35" "build-tools;35.0.0"` adds other SDK versions (it + patches their x86_64 native tools to aarch64, which plain `sdkmanager` cannot do). See + `~/CLAUDE.md` for what does and does not build on this phone. ## Releasing (the phone updates through Obtanium) @@ -77,6 +127,11 @@ after installing one (or build on the desktop and copy the APK over). ## Installing on the phone by hand +> **This is the destructive path: it costs the launcher config unless the backup is restored +> correctly. Do not do it without an explicit reason and a backup.** The current config lives in +> `/data/user/0/de.mm20.launcher2.release` (uid of the release build, currently `u0_a311`); copy it +> out with root before touching anything. + The phone already runs this build. The official build is signed with the upstream key, so it cannot be updated in place and Obtanium cannot install over it either. The procedure is: back up `/data/user/0/de.mm20.launcher2.release` with root, `pm uninstall` (a `-k` uninstall keeps the diff --git a/scripts/ondevice.sh b/scripts/ondevice.sh new file mode 100755 index 000000000..32092b5cf --- /dev/null +++ b/scripts/ondevice.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# Build and test this fork directly on the phone, from Termux. +# See CLAUDE.md ("Building on the phone itself") for the SDK setup this expects. +# +# The debug build is the package "de.mm20.launcher2.debug": a separate install from the +# real launcher ("de.mm20.launcher2.release") with its own, initially empty, data directory. +set -euo pipefail + +REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +PKG=de.mm20.launcher2.debug +ACTIVITY=de.mm20.launcher2.ui.launcher.LauncherActivity +DEBUG_APK=$REPO/app/app/build/outputs/apk/default/debug/app-default-debug.apk +RELEASE_APK=$REPO/app/app/build/outputs/apk/default/release/app-default-release.apk + +usage() { + cat <<'EOF' +Usage: scripts/ondevice.sh + + build assemble the debug APK + install build, then install the debug APK (needs root) + start launch the installed debug build + logs follow logcat of the running debug build + crash print the newest crash report for the debug build + release assemble the signed release APK: same package and key as the launcher + installed from Obtanium, so it installs over it and keeps all data +EOF +} + +# Termux ships JDK 21 in $PREFIX; on other machines JAVA_HOME has to be set already. +if [ -z "${JAVA_HOME:-}" ] && [ -n "${PREFIX:-}" ] && [ -d "$PREFIX/lib/jvm/java-21-openjdk" ]; then + export JAVA_HOME=$PREFIX/lib/jvm/java-21-openjdk +fi + +gradle() { + ( cd "$REPO" && ./gradlew --console=plain "$@" ) +} + +# Long builds outlive the screen lock; keep the CPU awake until they are done. +wake() { + command -v termux-wake-lock >/dev/null && termux-wake-lock || true +} + +build_debug() { + wake + gradle :app:app:assembleDefaultDebug + ls -l "$DEBUG_APK" +} + +# Every su -c argument below mixes two quoting levels: the single quoted parts are handed +# to the root shell as-is (so $( ) and $pid are expanded there), the "..." parts are +# expanded here. +case "${1:-}" in + build) + build_debug + ;; + install) + build_debug + # Termux cannot use pm itself; root can, and can read the APK from ~/sources. + su -c "pm install -r -d '$DEBUG_APK'" + ;; + start) + su -c "am start -n $PKG/$ACTIVITY" + ;; + logs) + su -c 'pid=$(pidof '"$PKG"') + if [ -z "$pid" ]; then + echo "not running - start it first (scripts/ondevice.sh start)" >&2 + exit 1 + fi + exec logcat --pid=$pid' + ;; + crash) + su -c 'for f in $(ls -t /data/system/dropbox/data_app_crash@*.txt 2>/dev/null | head -30); do + if grep -q '"$PKG"' "$f"; then echo "== $f"; cat "$f"; break; fi + done' + ;; + release) + wake + gradle :app:app:assembleDefaultRelease + ls -l "$RELEASE_APK" + ;; + *) + usage + exit 1 + ;; +esac