# Android APK build (debug + optional release). # # The Vite frontend and offline Reticulum manual are produced by the reusable # Frontend build workflow and downloaded into meshchatx/public/. This avoids # re-running the full pnpm install + build pipeline alongside the much heavier # Android NDK/wheel build steps. # # Pinned first-party actions (bump tag and SHA together when upgrading): # actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 # actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 # actions/setup-java@v4.7.1 c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4 # actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0 name: Android build on: pull_request: branches: - dev push: branches: - dev workflow_dispatch: inputs: build_release: description: Build signed/optimized release APK artifacts required: true type: boolean default: true run_tests: description: Run Android unit tests required: true type: boolean default: true permissions: contents: read actions: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NODE_OPTIONS: --max-old-space-size=8192 NODE_VERSION: "24" PNPM_VERSION: "10.32.1" PYTHON_VERSION: "3.11" JAVA_VERSION: "17" CHAQUOPY_REF: "9f563f45108a873d7feb363e1f754c0173f1114e" jobs: frontend: name: Build frontend artifact uses: ./.github/workflows/frontend-build.yml permissions: contents: read with: artifact_name: meshchatx-frontend-android-${{ github.run_id }}-${{ github.run_attempt }} retention_days: 1 # Android wheels run on Python 3.11; using the same here avoids # spurious version drift in the docs-bundling step. python_version: "3.11" android: name: Android test/build runs-on: ubuntu-latest needs: frontend timeout-minutes: 90 permissions: contents: read actions: write defaults: run: shell: bash env: FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }} steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Set up Java uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ env.PYTHON_VERSION }} - name: Download frontend artifact uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 with: name: ${{ env.FRONTEND_ARTIFACT_NAME }} path: meshchatx/public - name: Verify frontend artifact contents run: | set -euo pipefail test -f meshchatx/public/index.html test -d meshchatx/public/assets test -d meshchatx/public/reticulum-docs-bundled/current - name: Install Android wheel build dependencies run: | sudo apt-get update sudo apt-get install -y build-essential cmake pkg-config patchelf - name: Install Rust toolchain run: | curl -sSfL https://sh.rustup.rs -o rustup-init.sh chmod +x rustup-init.sh ./rustup-init.sh -y --profile minimal --default-toolchain stable echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Install Android NDK for Chaquopy native wheels run: | set -euo pipefail SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}" SDKMANAGER="${SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" if [[ ! -x "${SDKMANAGER}" ]]; then echo "Expected preinstalled sdkmanager at ${SDKMANAGER}" >&2 exit 1 fi NDK_VERSION="27.3.13750724" yes_n() { for _ in $(seq 1 "$1"); do echo y; done; } if [[ ! -d "${SDK_ROOT}/ndk/${NDK_VERSION}" ]]; then yes_n 200 | "${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --licenses >/dev/null yes_n 50 | "${SDKMANAGER}" --sdk_root="${SDK_ROOT}" "ndk;${NDK_VERSION}" >/dev/null fi { echo "ANDROID_HOME=${SDK_ROOT}" echo "ANDROID_SDK_ROOT=${SDK_ROOT}" } >> "${GITHUB_ENV}" - name: Build Android wheels run: bash scripts/build-android-wheels-local.sh --python-minor "${PYTHON_VERSION}" --chaquopy-ref "${CHAQUOPY_REF}" --abis arm64-v8a,x86_64,armeabi-v7a - name: Verify required Android wheels are present run: | set -euo pipefail required=( "miniaudio-1.70-*-cp311-cp311-android_24_arm64_v8a.whl" "miniaudio-1.70-*-cp311-cp311-android_24_x86_64.whl" "miniaudio-1.70-*-cp311-cp311-android_24_armeabi_v7a.whl" "pycodec2-*-cp311-cp311-android_24_arm64_v8a.whl" "pycodec2-*-cp311-cp311-android_24_x86_64.whl" "pycodec2-*-cp311-cp311-android_24_armeabi_v7a.whl" "lxst-*-py3-none-any.whl" ) missing=0 for pattern in "${required[@]}"; do if ! ls android/vendor/${pattern} >/dev/null 2>&1; then echo "::error::Missing wheel matching android/vendor/${pattern}" missing=1 fi done if [[ "${missing}" -ne 0 ]]; then echo "Built wheels:" ls -la android/vendor/ || true exit 1 fi echo "All required Android wheels present:" ls -1 android/vendor/ - name: Run unit tests if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} working-directory: android run: | chmod +x gradlew ./gradlew --no-daemon :app:testSlimDebugUnitTest - name: Build debug APK working-directory: android run: | chmod +x gradlew ./gradlew --no-daemon :app:assembleSlimDebug - name: Build release APK if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_release }} working-directory: android run: | chmod +x gradlew ./gradlew --no-daemon :app:assembleSlimRelease - name: Upload wheels uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: name: android-wheels-${{ github.ref_name }}-${{ github.run_id }} path: android/vendor/*.whl if-no-files-found: error - name: Upload debug APK uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: name: meshchatx-android-debug-${{ github.ref_name }}-${{ github.run_id }} path: android/app/build/outputs/apk/slim/debug/*.apk if-no-files-found: error - name: Upload test reports if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_tests }} uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: name: meshchatx-android-tests-${{ github.ref_name }}-${{ github.run_id }} path: | android/app/build/reports/tests/ android/app/build/test-results/ if-no-files-found: warn - name: Upload release APK if: ${{ github.event_name != 'workflow_dispatch' || inputs.build_release }} uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: name: meshchatx-android-release-${{ github.ref_name }}-${{ github.run_id }} path: android/app/build/outputs/apk/slim/release/*.apk if-no-files-found: error