Files
ledgrab/.gitea/workflows/build-android.yml
T
alexei.dolgolyov 151cea3ecb
Build Android APK / build-android (push) Failing after 2m31s
Lint & Test / test (push) Successful in 6m15s
ci: Android multi-ABI APK pipeline + pydantic-core wheel rebuild
Adds .gitea/workflows/build-android.yml — Linux runner installs JDK 17,
Python 3.11, Android SDK/NDK, symlinks server/src/ledgrab into the
Chaquopy python source dir, and runs assembleDebug on master pushes /
assembleRelease on v* tags. APK is uploaded as an artifact and attached
to the Gitea release on tag push. Conditional signing config in
build.gradle.kts reads keystore from env vars (CI secrets) and falls
back to debug signing locally. Gradle wrapper (gradlew/gradlew.bat/
gradle-wrapper.jar) committed so CI can drive the build.

Rebuilds pydantic-core wheels for arm64-v8a and x86_64 — both were
missing libpython3.11.so in NEEDED, which would have crashed at import
on real devices. build-pydantic-core.sh rewritten as a multi-ABI builder:
selects targets via args, sets RUSTFLAGS=-C link-arg=-Wl,--no-as-needed
-C link-arg=-lpython3.11 to force the symbol-resolution dependency,
uses the per-ABI sysconfigdata + libpython staged in
android/.build-cache/, prefers `py -3.11` on Windows (Git Bash's
python3.11 is an MSStore stub), uses the .cmd clang wrapper on Windows
(fixes os error 193), and verifies NEEDED via llvm-readelf after each
build. abiFilters restored to the full triple in build.gradle.kts;
multi-ABI debug APK builds cleanly (~99 MB).
2026-04-14 12:36:13 +03:00

193 lines
7.1 KiB
YAML

name: Build Android APK
on:
push:
branches: [master]
tags: ['v*']
paths:
- 'android/**'
- 'server/src/ledgrab/**'
- '.gitea/workflows/build-android.yml'
workflow_dispatch:
inputs:
version:
description: 'Version label (e.g. dev, 0.3.0-test)'
required: false
default: 'dev'
jobs:
build-android:
runs-on: ubuntu-latest
env:
JAVA_VERSION: '17'
PYTHON_VERSION: '3.11'
ANDROID_CMDLINE_TOOLS_VERSION: '11076708'
ANDROID_SDK_PLATFORM: 'android-34'
ANDROID_BUILD_TOOLS: '34.0.0'
ANDROID_NDK_VERSION: '26.1.10909125'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve build label
id: label
run: |
REF="${{ gitea.ref_name }}"
if echo "$REF" | grep -qE '^v[0-9]'; then
LABEL="${REF#v}"
IS_RELEASE="true"
elif [ -n "${{ inputs.version }}" ]; then
LABEL="${{ inputs.version }}"
IS_RELEASE="false"
else
LABEL="dev-${{ gitea.sha }}"
IS_RELEASE="false"
fi
LABEL="${LABEL:0:40}"
echo "label=$LABEL" >> "$GITHUB_OUTPUT"
echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT"
echo "Build label: $LABEL (release=$IS_RELEASE)"
- name: Setup JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Android SDK + NDK
run: |
set -euo pipefail
SDK_ROOT="$HOME/android-sdk"
mkdir -p "$SDK_ROOT/cmdline-tools"
cd "$SDK_ROOT/cmdline-tools"
curl -sSL --retry 3 \
-o cmdline-tools.zip \
"https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip"
unzip -q cmdline-tools.zip
rm cmdline-tools.zip
mv cmdline-tools latest
export ANDROID_SDK_ROOT="$SDK_ROOT"
export ANDROID_HOME="$SDK_ROOT"
SDKMANAGER="$SDK_ROOT/cmdline-tools/latest/bin/sdkmanager"
yes | "$SDKMANAGER" --licenses > /dev/null 2>&1 || true
"$SDKMANAGER" --install \
"platform-tools" \
"platforms;${ANDROID_SDK_PLATFORM}" \
"build-tools;${ANDROID_BUILD_TOOLS}" \
"ndk;${ANDROID_NDK_VERSION}" > /dev/null
echo "ANDROID_SDK_ROOT=$SDK_ROOT" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$SDK_ROOT" >> "$GITHUB_ENV"
echo "ANDROID_NDK_HOME=$SDK_ROOT/ndk/${ANDROID_NDK_VERSION}" >> "$GITHUB_ENV"
echo "$SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
echo "$SDK_ROOT/cmdline-tools/latest/bin" >> "$GITHUB_PATH"
- name: Create local.properties
run: |
echo "sdk.dir=$ANDROID_SDK_ROOT" > android/local.properties
- name: Link Python source (junction equivalent)
run: |
# Chaquopy reads Python modules from android/app/src/main/python/
# On Windows dev machines this is a directory junction; on Linux CI use a symlink.
ln -sfn "$(pwd)/server/src/ledgrab" android/app/src/main/python/ledgrab
ls -la android/app/src/main/python/
- name: Decode signing keystore
id: keystore
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
mkdir -p android/keystore
echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/keystore/release.jks
echo "path=$(pwd)/android/keystore/release.jks" >> "$GITHUB_OUTPUT"
echo "present=true" >> "$GITHUB_OUTPUT"
- name: Build APK
working-directory: android
env:
ANDROID_KEYSTORE_PATH: ${{ steps.keystore.outputs.path }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
chmod +x gradlew
if [ "${{ steps.keystore.outputs.present }}" = "true" ] && [ "${{ steps.label.outputs.is_release }}" = "true" ]; then
echo "Building signed release APK"
./gradlew --no-daemon assembleRelease
else
echo "Building debug APK (no signing keystore available or not a release tag)"
./gradlew --no-daemon assembleDebug
fi
- name: Locate and rename APK
id: apk
run: |
set -euo pipefail
SRC=$(ls android/app/build/outputs/apk/release/*.apk 2>/dev/null | head -1 || true)
if [ -z "$SRC" ]; then
SRC=$(ls android/app/build/outputs/apk/debug/*.apk | head -1)
VARIANT="debug"
else
VARIANT="release"
fi
DEST="build/LedGrab-${{ steps.label.outputs.label }}-android-${VARIANT}.apk"
mkdir -p build
cp "$SRC" "$DEST"
echo "path=$DEST" >> "$GITHUB_OUTPUT"
echo "name=$(basename "$DEST")" >> "$GITHUB_OUTPUT"
ls -lh "$DEST"
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: LedGrab-${{ steps.label.outputs.label }}-android
path: ${{ steps.apk.outputs.path }}
retention-days: 90
- name: Attach APK to Gitea release
if: ${{ steps.label.outputs.is_release == 'true' }}
env:
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
set -euo pipefail
TAG="${{ gitea.ref_name }}"
BASE_URL="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
APK_PATH="${{ steps.apk.outputs.path }}"
APK_NAME="${{ steps.apk.outputs.name }}"
# Fetch release by tag (created by release.yml `create-release` job)
RELEASE_ID=$(curl -s "$BASE_URL/releases/tags/$TAG" \
-H "Authorization: token $GITEA_TOKEN" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))")
if [ -z "$RELEASE_ID" ]; then
echo "::warning::No release found for tag $TAG — skipping asset upload"
exit 0
fi
# Replace existing asset if present (re-run safety)
EXISTING_ID=$(curl -s "$BASE_URL/releases/$RELEASE_ID/assets" \
-H "Authorization: token $GITEA_TOKEN" \
| python3 -c "import sys,json; assets=json.load(sys.stdin); print(next((str(a['id']) for a in assets if a['name']=='$APK_NAME'),''))" 2>/dev/null)
if [ -n "$EXISTING_ID" ]; then
curl -s -X DELETE "$BASE_URL/releases/$RELEASE_ID/assets/$EXISTING_ID" \
-H "Authorization: token $GITEA_TOKEN"
fi
curl -s -X POST \
"$BASE_URL/releases/$RELEASE_ID/assets?name=$APK_NAME" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$APK_PATH"
echo "Uploaded: $APK_NAME"