Skip to content

fix(android): repair buildozer build for Play Store AAB#1

Open
PhaedrusFlow wants to merge 12 commits into
mainfrom
fix/android-build
Open

fix(android): repair buildozer build for Play Store AAB#1
PhaedrusFlow wants to merge 12 commits into
mainfrom
fix/android-build

Conversation

@PhaedrusFlow
Copy link
Copy Markdown
Member

Problem

Buildozer was failing on pythonforandroid.toolchain create with the cross-compile error:

Py_DEPRECATED(VERSION_UNUSED) __attribute__((__deprecated__))

Three root causes were stacked:

  1. Malformed requirements = in buildozer.spec — mixed comma and newline separators caused python-for-android to parse faster-whisper\nctranslate2\nrequests as a single recipe name.
  2. Incompatible deps: faster-whisper, ctranslate2, ortools, geopy, numpy, pandas, pyproj, shapely, sounddevice have no python-for-android recipe.
  3. NDK 29 (and 26+) breaks p4a's CPython recipe. NDK 25b is the safe pin.

Changes

  • buildozer.spec — single-line p4a-safe requirements, NDK 25b pin (android.ndk = 25b), API 34, android.release_artifact = aab.
  • requirements-android.txt — rewritten to match.
  • core/geocoder.py — replaced geopy.geocoders.Nominatim with a plain requests call to the Nominatim HTTP API (geopy isn't p4a-compatible).
  • mobile/screens/voice.py — guarded from core.voice import VoiceRecognizer with ImportError catch; renders a 'voice unavailable' notice on platforms where faster-whisper/sounddevice aren't installed.
  • build.sh — added clean/debug/release subcommands and exported ANDROIDNDK/ANDROIDAPI/NDKAPI/JAVA_HOME pins.
  • docs/ANDROID_BUILD.md — full Arch Linux pre-flight, SDK/NDK install, build reproduction, signing, and Play Console upload steps.

Verifying locally (Arch)

./build.sh clean
./build.sh release   # produces bin/ontrack-1.0-arm64-v8a-release.aab

See docs/ANDROID_BUILD.md for the full runbook.

PhaedrusFlow and others added 12 commits May 26, 2026 01:15
- buildozer.spec: single-line requirements, NDK 25b pin, API 34, aab artifact
- requirements-android.txt: drop desktop-only deps (faster-whisper,
  ctranslate2, ortools, numpy, pandas, geopy, pyproj, shapely, sounddevice)
- core/geocoder.py: replace geopy with plain requests->Nominatim
- mobile/screens/voice.py: guard ImportError when whisper/sounddevice
  unavailable on Android; show 'voice unavailable' notice
- build.sh: clean/debug/release subcommands, ANDROIDNDK/ANDROIDAPI pins,
  JDK17 export
- docs/ANDROID_BUILD.md: full Arch Linux runbook + Play Console steps

Closes the Py_DEPRECATED(VERSION_UNUSED) cross-compile failure in
pythonforandroid.toolchain create.
- 6a service account + Play Developer API one-time setup
- 6b fastlane supply upload + track promotion
- 6c pure-curl JWT->edit->bundle->track->commit flow
- 6d app-content forms required before production promotion

Uses real package coordinates com.tds.ontrack.ontrack@2.0.0 from
buildozer.spec.
Kivy 2.3.1 ships pre-generated Cython C that calls _PyLong_AsByteArray
with 5 args. Python 3.14 added a 6th `with_exceptions` parameter, so
buildozer dies during the Cython extension compile with:

    error: too few arguments to function call, expected 6, have 5
    note: '_PyLong_AsByteArray' declared here (... 6 params)

The p4a project tracked this in kivy/python-for-android#3274 and shipped
the auto-resolve recipe in PR #3271; on the user side, the documented
fix is to use Kivy master (which is built with Cython >= 3.1 that emits
the new 6-arg call) plus p4a develop + API 36 + NDK r29.

Changes:
- buildozer.spec: kivy==2.3.0 -> kivy==master; android.api 34 -> 36;
  android.ndk 25b -> 29; android.sdk 34 -> 36; comment block rewritten
  to document the new tested combination.
- build.sh: auto-resolve ANDROIDNDK from $ANDROID_SDK_ROOT/ndk/29.*;
  auto-detect buildozer in ~/venv_p4a_develop/bin first (the venv
  Buildozer docs require for the Python 3.14 path); ANDROIDAPI 34 -> 36;
  NDKAPI 21 -> 26; ANDROIDNDKVER 25b -> 29.
- docs/ANDROID_BUILD.md: rewrite \u00a71 (pre-flight) for the python3.14 venv
  install; \u00a72 (SDK) for platform 36 / build-tools 36 / ndk r29; \u00a74
  (build) to source the venv first; expanded troubleshooting table
  with the _PyLong_AsByteArray entry and links to upstream issues.

Refs:
- https://buildozer.readthedocs.io/en/latest/installation/
- kivy/python-for-android#3271
- kivy/python-for-android#3274
A prior local commit on main tried to push a Crates.io token captured
inside log.txt (`./build.sh release 2>&1 | tee log.txt`-style output).
GitHub secret-scanning rejected the push, which is the correct
behaviour, but the failure mode is easy to repeat. Block all build /
runtime logs from being tracked at the repo level so nobody can
accidentally commit a redirect of the buildozer/p4a output (which
echoes $CARGO_REGISTRY_TOKEN, $PIP_INDEX_URL, etc.) again.

Adds to .gitignore:
- *.log
- log.txt
- buildozer_debug.log
- .buildozer/
- bin/
p4a develop dropped the --feature CLI flag from the toolchain build
script:
  python -m pythonforandroid.toolchain: error: unrecognized arguments:
      --feature android.hardware.location
      --feature android.hardware.location.gps

The new file build.py on the develop branch only registers
--uses-library, not --feature. Buildozer was still translating
'android.features = ...' into '--feature ...' for each value, so any
non-empty android.features blew up p4a's argparse.

Fix:
- Comment out android.features in buildozer.spec.
- Add android_manifest_extras.xml with the two <uses-feature> nodes
  (android:required="false" so Wi-Fi-only devices can still install).
- Point buildozer at it with android.extra_manifest_xml. Buildozer
  reads the file contents and passes them through --extra-manifest-xml,
  which p4a still accepts.
- Add troubleshooting row in docs/ANDROID_BUILD.md.

Refs:
- kivy/python-for-android develop build.py (no --feature add_argument)
- kivy/buildozer android.py line ~1149 (still emits --feature)
…ilure

Two issues from the latest build attempt:

1. 'bash: ~/venv_p4a_develop/bin/activate: No such file or directory'
   The Python 3.14 venv that build.sh expects was never created on this
   machine. Docs/ANDROID_BUILD.md §1 had the manual commands, but no
   single runnable script.

   Add scripts/setup-venv.sh: idempotent bootstrap that creates the
   venv at ~/venv_p4a_develop and installs buildozer (master), cython
   0.29.34, legacy-cgi, setuptools, wheel. Supports --recreate to
   nuke and rebuild. build.sh's 'buildozer not found' message now
   points at this script.

2. 'gradlew clean bundleRelease failed' with no visible stack trace
   Buildozer hides subprocess stderr unless run with --verbose, so
   the actual Gradle error was lost. build.sh now invokes buildozer
   with --verbose automatically.

Defensive cleanup:
- Trim android_manifest_extras.xml to just the two <uses-feature> tags.
  The previous version had a multi-line XML comment that p4a passes
  verbatim into the template; some Jinja2 / AAPT2 combinations choke
  on comments-as-manifest-children. Bare tags are universally safe.

Troubleshooting table in docs/ANDROID_BUILD.md gets three new rows:
no-venv, no-buildozer (now points at setup script), silent gradle
failure (run with --verbose).
Adds a second app variant 'FieldSnek' that ships from the same ONTrack
codebase under a separate Play Console listing (com.qompassai.fieldsnek).
The buildozer.spec for ONTrack is untouched; FieldSnek is driven by its
own spec so the two builds don't share bin_dir/build_dir and can iterate
independently.

New files:
- buildozer.fieldsnek.spec     title=FieldSnek, package=com.qompassai.fieldsnek
                               bin_dir=/var/tmp/buildozer/fieldsnek/bin
- Gemfile + .bundle/config     fastlane ~> 2.227, vendor/bundle (no system gems)
- fastlane/Appfile             package_name=com.qompassai.fieldsnek,
                               reuses ~/.config/fastlane/google-play-ontrack.json
- fastlane/Fastfile            lanes: closed (alpha), internal, check_auth, validate
                               glob-resolves the buildozer-generated AAB filename
                               and prints a bootstrap runbook on 'Package not found'
- fastlane/INIT.md             one-time Play Console bootstrap runbook +
                               troubleshooting table + env var reference
- scripts/build-android.sh     wrapper: 'aab|apk|both [ontrack|fieldsnek]'
                               with --help and mode/app validation

Updated:
- .gitignore                   allow buildozer.fieldsnek.spec through the *.spec
                               block, ignore vendor/bundle, fastlane reports
- docs/ANDROID_BUILD.md        new \xc2\xa74b section on building + uploading FieldSnek

Usage on workstation:
  source ~/venv_p4a_develop/bin/activate
  bash scripts/build-android.sh aab fieldsnek          # build for Play
  bundle install --path vendor/bundle                  # first time
  bundle exec fastlane android validate                # dry-run upload
  bundle exec fastlane android internal                # real upload

The first AAB still has to be uploaded through the Play Console UI to
register the package_name with the API (Play Developer API v3 cannot
create apps); fastlane/INIT.md walks through the bootstrap.
The build script was passing 'buildozer --spec buildozer.fieldsnek.spec
android release', but buildozer's argparse only accepts -v/--verbose,
-h/--help, -p/--profile <name>, and --version (per
buildozer/__init__.py::run_command). Anything else gets parsed as a
target name, producing 'Unknown command/target buildozer.fieldsnek.spec'.

Fix: scripts/build-android.sh now symlinks the chosen .spec to
./buildozer.spec for the duration of the build, then restores the
original via an EXIT/INT/TERM trap. The wrapper validates the swap
case where buildozer.spec is already a symlink, a regular file, or
missing entirely, and never copies file contents.

Also updates fastlane/INIT.md and the comment header of
buildozer.fieldsnek.spec to remove the bogus --spec invocation
references and document the symlink behavior + manual-bypass recipe.
Wholesale rebrand:

Text content (50 files):
  ON-Track / ONTrack / OnTrack / Ontrack -> FieldSnek
  ONTRACK_ env var prefix                -> FIELDSNEK_
  ONTRACK constant                       -> FIELDSNEK
  ontrack                                -> fieldsnek
  com.tds.ontrack package_name           -> com.qompassai.fieldsnek
  github.com/qompassai/ONTrack URLs      -> github.com/qompassai/fieldsnek

Preserved verbatim:
  ontrack-rs                             (sibling Rust repo, not this one)

File renames:
  ontrack.spec                           -> fieldsnek.spec
  installer/ontrack_installer.py         -> installer/fieldsnek_installer.py
  tools/scaffold_ontrack.py              -> tools/scaffold_fieldsnek.py
  assets/themes/ontrack.jsonc            -> assets/themes/fieldsnek.jsonc
  pipewire/51-ontrack-echo-cancel.conf   -> pipewire/51-fieldsnek-echo-cancel.conf

Structural changes:
  - buildozer.fieldsnek.spec merged into buildozer.spec; deleted variant.
    There is now one canonical spec, package_name com.qompassai.fieldsnek.
  - scripts/build-android.sh dropped the [ontrack|fieldsnek] app argument
    and the spec-swap symlink dance (no longer needed with a single spec).
  - fastlane/Fastfile dropped the FIELDSNEK_SPEC constant and the
    'build-android.sh aab fieldsnek' invocation.
  - .gitignore: removed the *.fieldsnek.spec exception; added .kivy/logs/.
  - .kivy/logs/*.txt untracked (regenerated on every Kivy run).

Verified:
  - python3 ast.parse on every modified .py file
  - bash -n on every modified .sh file
  - tomllib.loads on Cargo.toml, pyproject.toml, cliff.toml
  - json.loads on .zenodo.json
  - Zero residual 'ontrack' occurrences outside the protected 'ontrack-rs'
    refs (which point to the sibling Rust repo).

Action item the user already started: GitHub repo renamed
qompassai/ONTrack -> qompassai/fieldsnek. Update your local remote:
  git remote set-url origin https://github.com/qompassai/fieldsnek.git
Removes:
- Python comments (preserves docstrings)
- Shell comments (POSIX rule: # only after whitespace/SOL,
  so ${#var}, $#, etc. are preserved)
- Ruby comments in Gemfile/Fastfile/Appfile (preserves "#{...}"
  string interpolations)
- TOML/INI/.spec/YAML/.cff/Nix/.conf hash comments
- Rust/Kotlin/Java/JSONC // line and /* */ block comments
- Markdown HTML comments (<!-- ... -->)
- All emoji characters (Unicode pictograph + symbol ranges,
  regional indicators, ZWJ, VS15/16)

Shebangs preserved. Two files (gui/components/address_table.py and
tests/test_platform_compat.py) had pre-existing syntax errors at
HEAD that are unaffected by this change.
Real bugs:

  - mobile/app.py:63 — fix NameError: 'screen_nameame' typo (was
    introduced in 2dd126d, way before this branch). The function
    parameter is 'screen_name'; the doubled 'ame' suffix would
    raise NameError on the first navigation call.

  - gui/components/address_table.py:310 — fix SyntaxError: missing
    'if lbl:' guard line. The body of the for-loop had a single
    indented statement under `lbl = rw.get('addr')` with no
    conditional, making the indentation unreachable.

  - gui/components/address_table.py:22 — fix NameError: `ctk` was
    used throughout the file (CTkFrame, CTkLabel, CTkFont,
    CTkScrollableFrame) but customtkinter was never imported. The
    unused `import tkinter as tk` is replaced with the correct
    `import customtkinter as ctk`.

  - tests/test_platform_compat.py:28 — fix SyntaxError: the
    `_importable` helper had its `def` line missing, leaving an
    orphan try/except at module scope. Restored the proper
    definition; this is the helper called by every importability
    test in the file.

  - mobile/screens/voice.py:58 — remove duplicate, unguarded
    `from core.voice import VoiceRecognizer` that defeated the
    purpose of the preceding try/except guard.

Code quality:

  - installer/fieldsnek_installer.py:43 — drop f-prefix on a
    string with no placeholders (was `f"~/.local/share/fieldsnek"`).

  - tests/test_voice.py:351 — drop unused `result =` assignment;
    the test asserts on `elapsed`, not the value.

Comment cleanup follow-ups from previous strip pass:

  - requirements.txt, requirements-android.txt, .env.example,
    .gitignore — strip leftover `#` comments (pip requirements
    files and .gitignore weren't covered by the previous pass).

Verified:

  - python3 ast.parse on all 38 .py files — all parse
  - pyflakes — no undefined names anywhere
  - ruff F+E9 — zero real-bug findings (remaining noise is
    "unused import" warnings)
  - bash -n on all 6 .sh files
  - tomllib on all .toml files
  - json.loads on all .json + .jsonc files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant