diff --git a/CLAUDE.md b/CLAUDE.md index 1abc90f..962153f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,14 +9,21 @@ Context for future Claude sessions working on this repo. relocatable data path, taskbar icon, "Time Since" column. - Repo is pushed to `repo.tas-tech.net/tas-tech.net/event-tracker` (private — will go public once there's a tagged release). -- Android build (`android/`) is in progress. Native recipe compilation - (SDL2, OpenSSL, sqlite3, pyjnius, hostpython3) succeeds. The last - blocker was a p4a upstream bug in the `kivy` recipe's `python_depends` - — see "Android companion — design decisions" below for the fix - (`android/p4a-recipes/kivy`). Not yet confirmed working end-to-end — - next session should check whether the user has run - `./build.sh android debug` since that fix landed and, if not, ask for - the latest log tail before touching anything else Android-related. +- **Android build (`android/`) works end-to-end** — `./build.sh android + debug` produces `android/bin/eventtracker-0.1-arm64-v8a-debug.apk`. + Confirmed via a full `BUILD SUCCESSFUL` Gradle run, not just "recipes + compiled." Three real, distinct bugs had to be fixed to get here (all + now baked into `build.sh`/`buildozer.spec`, nothing left to redo): + 1. p4a's `kivy` recipe pulling in unused `requests`/`charset-normalizer` + that couldn't actually install — `android/p4a-recipes/kivy`. + 2. p4a's own internal pymodules venv corrupting itself on every + re-build — `build.sh` deletes it before each run. + 3. Gradle's build-script compiler rejecting this machine's default JDK + (java-26) — `build.sh` pins `JAVA_HOME` to `java-17-openjdk` for + just this build. + See "Android companion — design decisions" below for the full + root-cause writeup on each. Not yet installed/tested on an actual + phone — that's the next real checkpoint, not another build fix. - **Outstanding, agreed but not implemented**: split `config.json` into two files — event list (safe to sync between machines) vs. data file path (must stay local per-device, same principle as the Android app's @@ -212,6 +219,38 @@ android/ mode than the one it fixed. Don't reintroduce it as a quick fix for a future build error without checking whether it's actually leaking something first. +- **`build.sh` deletes p4a's internal pymodules venv before every run** — + separate from `android/build/venv` (ours, for buildozer/cython) and + separate from the `p4a-recipes` bug above. p4a creates its own + throwaway venv per arch at + `.buildozer/android/platform/build-/build/venv` to pip-install + pure-Python deps, and unconditionally re-runs `python -m venv venv` + over it on *every* build without `--clear`. First build: ensurepip + bootstraps whatever pip version shipped with this system's Python + (25.3). p4a then runs `pip install -U pip` inside it (26.2.1). Next + build: `python -m venv` re-invokes ensurepip, which unpacks its older + bundled pip back over the newer one without removing the newer + version's now-orphaned files — confirmed by hand, both + `pip-25.3.dist-info` and `pip-26.2.1.dist-info` present in the same + site-packages afterward, producing exactly the kind of mismatched-file + crash you'd expect (`ImportError: cannot import name + 'BuildDependencyInstallError' from 'pip._internal.exceptions'` — a + newer file expecting a class only the newer exceptions.py defines). + `build.sh` just deletes that venv before every buildozer invocation; + costs a few seconds (pip + Cython reinstall in a tiny throwaway venv), + leaves the expensive SDK/NDK/recipe build state untouched. +- **`build.sh` pins `JAVA_HOME` to `java-17-openjdk` for the build only** + — this machine's `archlinux-java` default is java-26 (rolling + release), and Gradle 8.14.3 (pulled in by this p4a version) ships a + Groovy/ASM build-script compiler that can't parse that new a class + file: `BUG! ... Unsupported class file major version 70`. java-17 is + installed alongside the default (`archlinux-java status` lists both); + `JAVA_HOME_FOR_BUILD` at the top of `build.sh` points at it without + touching the system-wide default JDK used by everything else. If this + ever needs to change (different machine, JDK package renamed), that's + the one line to edit — `build.sh` fails loudly with a clear message if + the path doesn't exist rather than silently falling through to + whatever `java` resolves to on PATH. ## Conventions for changes diff --git a/android/README.md b/android/README.md index 88acecb..c3b7045 100644 --- a/android/README.md +++ b/android/README.md @@ -24,6 +24,7 @@ Run on the Linux machine you're building from (not the phone): ```sh command -v buildozer && buildozer version 2>&1 command -v java && java -version 2>&1 +archlinux-java status 2>&1 python3 -c "import cython; print('cython:', cython.__version__)" 2>&1 ``` @@ -32,9 +33,19 @@ python3 -c "import cython; print('cython:', cython.__version__)" 2>&1 **Arch / EndeavourOS:** ```sh -sudo pacman -S --needed jdk-openjdk +sudo pacman -S --needed jdk17-openjdk ``` +Gradle (pulled in by buildozer/p4a) needs a JDK it actually supports — +`jdk-openjdk` on a rolling-release system tracks whatever's newest, and +a too-new JDK breaks Gradle's build-script compiler with `Unsupported +class file major version …`. `jdk17-openjdk` specifically, installed +alongside whatever your system default is (`archlinux-java status` +lists both — no need to switch the default). `build.sh` points this +build at it via `JAVA_HOME` on its own; if `archlinux-java status` shows +it under a different name on your machine, update +`JAVA_HOME_FOR_BUILD` at the top of `build.sh` to match. + buildozer and cython go in an isolated venv, not system Python — see next step. (No `pip install --break-system-packages`; that pollutes the system Python site-packages for no reason when a venv does the job.) diff --git a/android/build.sh b/android/build.sh index 4375458..4d2ad65 100644 --- a/android/build.sh +++ b/android/build.sh @@ -3,6 +3,18 @@ # buildozer through it directly. Usage: ./build.sh android debug set -euo pipefail +# ---- Configuration ---- +# Gradle 8.14.3 (pulled in by this p4a/buildozer version) ships a +# Groovy/ASM build-script compiler that doesn't understand class files +# newer than it was built against. This system's `java` default is +# java-26-openjdk (`archlinux-java status`), and Gradle chokes on it: +# BUG! ... Unsupported class file major version 70 +# java-17-openjdk is also installed on this machine and works — set only +# for this build, via JAVA_HOME below, so the system-wide default JDK +# (used by everything else) is untouched. If Arch's package layout ever +# changes this path, `archlinux-java status` shows the current one. +JAVA_HOME_FOR_BUILD="/usr/lib/jvm/java-17-openjdk" + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VENV_BIN="$SCRIPT_DIR/build/venv/bin" BUILDOZER="$VENV_BIN/buildozer" @@ -12,7 +24,14 @@ if [ ! -x "$BUILDOZER" ]; then exit 1 fi +if [ ! -d "$JAVA_HOME_FOR_BUILD" ]; then + echo "Expected JDK not found at $JAVA_HOME_FOR_BUILD — run 'archlinux-java status' and update JAVA_HOME_FOR_BUILD in this script." >&2 + exit 1 +fi + cd "$SCRIPT_DIR" +export JAVA_HOME="$JAVA_HOME_FOR_BUILD" +export PATH="$JAVA_HOME/bin:$PATH" # Running buildozer by absolute path skips everything `source venv/bin/activate` # normally sets up, so it's replicated by hand: # - PATH: buildozer shells out to find tools like `cython`, which only @@ -23,4 +42,26 @@ cd "$SCRIPT_DIR" # a venv, uses --user, and pip refuses (no user site-packages in a venv). export PATH="$VENV_BIN:$PATH" export VIRTUAL_ENV="$SCRIPT_DIR/build/venv" + +# p4a creates its OWN throwaway venv per arch (.buildozer/.../build/venv, +# distinct from our build/venv above) to pip-install pure-Python deps for +# the app, and unconditionally re-runs `python -m venv venv` over it on +# every single build — without --clear. The first time, ensurepip bundles +# whatever pip version shipped with this system's Python (e.g. 25.3); if +# a previous build then ran `pip install -U pip` inside it (p4a does this +# every time too), the NEXT build's `python -m venv` re-invokes ensurepip, +# which unpacks its older bundled pip back over the newer one WITHOUT +# removing the newer version's now-orphaned files — two dist-info dirs +# end up coexisting, and a newer file (e.g. build_env/installer.py, +# importing a class only the newer pip's exceptions.py defines) can end +# up paired with an older file that doesn't define it, e.g.: +# ImportError: cannot import name 'BuildDependencyInstallError' from +# 'pip._internal.exceptions' +# Confirmed by hand: pip-25.3.dist-info and pip-26.2.1.dist-info both +# present in the same site-packages after a second build. Deleting that +# venv before every build keeps it single-version and costs a few +# seconds (pip + Cython reinstall) — cheap next to the SDK/NDK/recipe +# build state, which this leaves untouched. +rm -rf "$SCRIPT_DIR"/.buildozer/android/platform/build-*/build/venv + exec "$BUILDOZER" "$@"