build: target amd64 — Google ships no arm64 Linux build-tools
All checks were successful
build-android-builder-image / build-image (push) Successful in 2m49s

The image pinned build-tools 36.0.0, whose aapt2/d8 are x86_64-only (Google
publishes no arm64 Linux build-tools). Built for and run on arm64, packaging
died with `aapt2: Syntax error: newline unexpected` (an x86_64 binary the
kernel can't exec).

Build the image for linux/amd64 on an amd64 runner, and point the template +
README at ubuntu-24.04. The APK output is architecture-neutral, so this costs
nothing downstream. Also execute aapt2 in the image sanity check so an arch
mismatch fails the image build, not every app build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 22:53:07 -04:00
parent 7556e9b547
commit 605df9bdbf
4 changed files with 31 additions and 18 deletions

View File

@@ -15,7 +15,9 @@ env:
jobs: jobs:
build-image: build-image:
runs-on: ubuntu-24.04-arm64 # amd64: Google ships the Linux build-tools (aapt2, d8) for x86_64 only,
# so the toolchain must be built for and run on amd64.
runs-on: ubuntu-24.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -43,12 +45,12 @@ jobs:
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build and push (linux/arm64) - name: Build and push (linux/amd64)
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/arm64 platforms: linux/amd64
push: true push: true
tags: ${{ steps.tags.outputs.tags }} tags: ${{ steps.tags.outputs.tags }}
# Bump these here if you want to override the Dockerfile defaults # Bump these here if you want to override the Dockerfile defaults

View File

@@ -1,7 +1,9 @@
# Native linux/arm64 Android build toolchain for CI. # Native linux/amd64 Android build toolchain for CI.
# Builds release artifacts only — no emulator, no test/system images. # Builds release artifacts only — no emulator, no test/system images.
# eclipse-temurin publishes a native linux/arm64 manifest, so this runs # Must be amd64: Google publishes the Linux build-tools (aapt2, d8, …) for
# without emulation on an OCI Ampere (aarch64) runner. # x86_64 only — there is no arm64 Linux build-tools package — so packaging
# cannot run natively on an aarch64 runner. The APK output is architecture-
# neutral regardless of the build host.
FROM eclipse-temurin:21-jdk-noble FROM eclipse-temurin:21-jdk-noble
# --- Version pins (bump deliberately; this is what your app repos trust) --- # --- Version pins (bump deliberately; this is what your app repos trust) ---
@@ -40,9 +42,8 @@ RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \
ENV PATH=${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools ENV PATH=${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools
# Accept licenses and bake the SDK packages into the image so prod builds # Accept licenses and bake the SDK packages into the image so prod builds
# don't depend on Google's endpoint at job time. # don't depend on Google's endpoint at job time. The build-tools binaries
# aapt2 and the build-tools binaries ship native arm64 for 35.x, so APK/AAB # (aapt2, d8) are x86_64 ELF — this is why the image must be amd64.
# packaging runs natively on aarch64.
RUN yes | sdkmanager --licenses >/dev/null && \ RUN yes | sdkmanager --licenses >/dev/null && \
sdkmanager --install \ sdkmanager --install \
"platform-tools" \ "platform-tools" \
@@ -51,7 +52,10 @@ RUN yes | sdkmanager --licenses >/dev/null && \
rm -rf ${ANDROID_SDK_ROOT}/.android rm -rf ${ANDROID_SDK_ROOT}/.android
# Sanity: fail the image build if the toolchain isn't actually usable. # Sanity: fail the image build if the toolchain isn't actually usable.
# Execute aapt2 (not just --version a tool) so an arch mismatch in the
# build-tools binaries fails the image build here, not in every app build.
RUN java -version && sdkmanager --version && node --version && \ RUN java -version && sdkmanager --version && node --version && \
"${ANDROID_SDK_ROOT}/build-tools/${BUILD_TOOLS_VERSION}/aapt2" version && \
test -d "${ANDROID_SDK_ROOT}/platforms/${PLATFORM_VERSION}" test -d "${ANDROID_SDK_ROOT}/platforms/${PLATFORM_VERSION}"
WORKDIR /workspace WORKDIR /workspace

View File

@@ -1,18 +1,23 @@
# android-builder # android-builder
Native `linux/arm64` Android build toolchain for CI, published to the Gitea Native `linux/amd64` Android build toolchain for CI, published to the Gitea
container registry at `git.helu.ca/r/android`. container registry at `git.helu.ca/r/android`.
App repos consume this image to build signed release artifacts. Instrumented App repos consume this image to build signed release artifacts. Instrumented
tests are **not** part of this toolchain — by design, CI builds are promotions 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 of code already tested in Dev (on Apple silicon, where the emulator runs
natively). The OCI Ampere (aarch64) runner has no `/dev/kvm` (the guest VM natively).
boots at EL1, so KVM can't access HYP/EL2), so there's no accelerated emulator
here — and we don't need one. **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 ## What's in the image
- Eclipse Temurin JDK 21 (native arm64) - Eclipse Temurin JDK 21 (native amd64)
- Android cmdline-tools, platform, and build-tools — **baked in**, so prod - Android cmdline-tools, platform, and build-tools — **baked in**, so prod
builds don't depend on Google's download endpoint at job time builds don't depend on Google's download endpoint at job time
- `git`, `curl`, `unzip` - `git`, `curl`, `unzip`
@@ -27,8 +32,8 @@ Pinned versions live as `ARG`s at the top of the `Dockerfile`:
| `BUILD_TOOLS_VERSION` | `36.0.0` | SDK Manager / release notes | | `BUILD_TOOLS_VERSION` | `36.0.0` | SDK Manager / release notes |
| `PLATFORM_VERSION` | `android-36`| your app's `compileSdk` | | `PLATFORM_VERSION` | `android-36`| your app's `compileSdk` |
`aapt2` and the build-tools binaries ship native arm64 for 36.x, so packaging The build-tools binaries (`aapt2`, `d8`) are x86_64 ELF — this is why the
runs without emulation. image is amd64 (see "Why amd64" above).
## Tagging model ## Tagging model
@@ -63,7 +68,7 @@ app and pin the toolchain:
```yaml ```yaml
jobs: jobs:
build: build:
runs-on: ubuntu-24.04-arm64 runs-on: ubuntu-24.04
container: container:
image: git.helu.ca/r/android:2026.06 image: git.helu.ca/r/android:2026.06
credentials: credentials:

View File

@@ -24,7 +24,9 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-24.04-arm64 # amd64: the toolchain image carries x86_64 Android build-tools (Google
# ships no arm64 Linux build-tools), so run it on an amd64 runner.
runs-on: ubuntu-24.04
container: container:
image: git.helu.ca/r/android:2026.06 image: git.helu.ca/r/android:2026.06
credentials: credentials: