Playbook for taking any Heluca app from repo to Google Play, learned from shipping Dade to internal testing: one-time publisher account + upload keystore setup, per-app checklist (signing via -Pandroid.injected.signing.*, AAB, versionCode encoding, native symbols), internal-testing flow, gotchas, and production promotion. Bakes in Heluca conventions and documents where Dade deliberately diverges (custom signingConfig, UPLOAD_* secret names, :latest pin) so the next app can follow the template instead. Link it from the README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
4.4 KiB
Markdown
106 lines
4.4 KiB
Markdown
# android-builder
|
|
|
|
Native `linux/amd64` Android build toolchain for CI, published to the Gitea
|
|
container registry at `git.helu.ca/r/android`.
|
|
|
|
App repos consume this image to build signed release artifacts. Instrumented
|
|
tests are **not** part of this toolchain — by design, CI builds are promotions
|
|
of code already tested in Dev (on Apple silicon, where the emulator runs
|
|
natively).
|
|
|
|
**Publishing to Google Play?** See [`docs/google-play-publishing.md`](docs/google-play-publishing.md)
|
|
— the reusable Heluca playbook (publisher setup, upload keystore, per-app
|
|
checklist, internal testing, promotion to production), learned from shipping Dade.
|
|
|
|
**Why amd64:** Google publishes the Linux Android build-tools (`aapt2`, `d8`,
|
|
…) for **x86_64 only** — there is no arm64 Linux build-tools package. Running
|
|
the toolchain on an aarch64 runner fails at resource packaging
|
|
(`aapt2: Syntax error: newline unexpected`, an x86_64 binary the kernel can't
|
|
exec). The produced APK/AAB is architecture-neutral regardless of build host,
|
|
so building on amd64 costs nothing on the output side.
|
|
|
|
## What's in the image
|
|
|
|
- Eclipse Temurin JDK 21 (native amd64)
|
|
- Android cmdline-tools, platform, and build-tools — **baked in**, so prod
|
|
builds don't depend on Google's download endpoint at job time
|
|
- `git`, `curl`, `unzip`
|
|
- Node.js 24 — so Gitea Actions JS actions (`actions/checkout`,
|
|
`upload-artifact`, …) run when this image is used as a job `container`
|
|
|
|
Pinned versions live as `ARG`s at the top of the `Dockerfile`:
|
|
|
|
| ARG | Default | Where to check |
|
|
| ----------------------- | ----------- | -------------- |
|
|
| `CMDLINE_TOOLS_VERSION` | `13114758` | https://developer.android.com/studio#command-line-tools-only |
|
|
| `BUILD_TOOLS_VERSION` | `36.0.0` | SDK Manager / release notes |
|
|
| `PLATFORM_VERSION` | `android-36`| your app's `compileSdk` |
|
|
|
|
The build-tools binaries (`aapt2`, `d8`) are x86_64 ELF — this is why the
|
|
image is amd64 (see "Why amd64" above).
|
|
|
|
## Tagging model
|
|
|
|
The whole point of owning this image is that **app repos pin a tag and a new
|
|
toolchain never lands in a prod build by accident.**
|
|
|
|
- **Immutable** — a git tag like `2026.06` produces `git.helu.ca/r/android:2026.06`.
|
|
This is what app repos pin to.
|
|
- **Moving** — pushes to `main` (and any tag build) refresh
|
|
`git.helu.ca/r/android:latest` for convenience. Don't pin prod builds to this.
|
|
|
|
## Cutting a new toolchain
|
|
|
|
1. Update the `ARG` defaults in the `Dockerfile` if you're moving SDK/tool
|
|
versions. Verify `CMDLINE_TOOLS_VERSION` against the link above — a stale
|
|
number 404s the image build.
|
|
2. Commit to `main`. The workflow builds and pushes `:latest`; confirm it's green.
|
|
3. Tag the release with the year-month you want app repos to reference:
|
|
```
|
|
git tag 2026.06
|
|
git push origin 2026.06
|
|
```
|
|
This publishes the immutable `git.helu.ca/r/android:2026.06`.
|
|
4. Roll app repos forward deliberately by bumping their pinned tag (see below)
|
|
— one at a time if you want to validate, all at once if you trust the bump.
|
|
|
|
## Using it in an app repo
|
|
|
|
Drop `.gitea/workflows/build.yml` (the template alongside this repo) into the
|
|
app and pin the toolchain:
|
|
|
|
```yaml
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-24.04
|
|
container:
|
|
image: git.helu.ca/r/android:2026.06
|
|
credentials:
|
|
username: ${{ gitea.actor }}
|
|
password: ${{ secrets.PACKAGE_TOKEN }}
|
|
```
|
|
|
|
The build task is selectable: `assembleRelease` (APK, the default) or
|
|
`bundleRelease` (AAB — only needed for Google Play distribution).
|
|
|
|
### Required secrets in each app repo (or org-level)
|
|
|
|
Signing happens at job time; nothing sensitive lives in the repo or the image.
|
|
|
|
| Secret | What it is |
|
|
| ------------------- | ---------- |
|
|
| `KEYSTORE_BASE64` | release keystore, base64-encoded: `base64 -w0 release.keystore` |
|
|
| `KEYSTORE_PASSWORD` | keystore password |
|
|
| `KEY_ALIAS` | signing key alias |
|
|
| `KEY_PASSWORD` | key password |
|
|
|
|
`PACKAGE_TOKEN` (a PAT, the same secret the other repos here use) needs
|
|
`write:package` in this repo (to push the image) and `read:package` in app
|
|
repos (to pull it). Set it as a repo or org-level Actions secret. The
|
|
built-in `gitea.token` is not used because it isn't scoped for the registry.
|
|
|
|
## First-run sequencing
|
|
|
|
The image must exist in the registry before any app workflow can pull it.
|
|
So: cut and push `2026.06` from this repo first, confirm it's in the registry,
|
|
*then* the app workflows that pin it will resolve. |