Skip to content

Build Signed Android APK/AAB #33

Build Signed Android APK/AAB

Build Signed Android APK/AAB #33

Workflow file for this run

name: Build Signed Android APK/AAB
on:
workflow_dispatch:
inputs:
build_type:
description: "Build Type"
required: true
default: "release"
type: choice
options:
- debug
- release
output_format:
description: "Output Format"
required: true
default: "aab"
type: choice
options:
- apk
- aab
- both
should_sign:
description: "Sign the build?"
required: true
default: "true"
type: choice
options:
- "true"
- "false"
push:
tags:
- "v*.*.*"
jobs:
build-android:
runs-on: ubuntu-latest
steps:
# ------------------------------------------------
# 0️⃣ Checkout + basic setup
# ------------------------------------------------
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- uses: android-actions/setup-android@v3
# ------------------------------------------------
# 1️⃣ Install npm deps (Expo pre‑build)
# ------------------------------------------------
- run: npm ci --legacy-peer-deps
- run: npx expo prebuild --platform android --clean
# ------------------------------------------------
# 2️⃣ Cache Gradle (speed‑up)
# ------------------------------------------------
- uses: gradle/actions/setup-gradle@v3
with:
gradle-home-cache-cleanup: true
# ------------------------------------------------
# 3️⃣ Decode the keystore (used by Gradle for signing)
# ------------------------------------------------
- name: Decode keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > release.keystore
chmod 600 release.keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
# ------------------------------------------------
# 4️⃣ Make gradlew executable
# ------------------------------------------------
- run: chmod +x android/gradlew
# ------------------------------------------------
# 5️⃣ Build the artefacts (release or debug)
# ------------------------------------------------
- name: Build Release APK (unsigned)
if: ${{ github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both' }}
run: |
cd android
./gradlew assembleRelease --no-daemon --stacktrace
- name: Build Release AAB (unsigned)
if: ${{ github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both' }}
run: |
cd android
./gradlew bundleRelease --no-daemon --stacktrace
# ------------------------------------------------
# 6️⃣ (Optional) Sign the APK only – AAB stays unsigned
# ------------------------------------------------
- name: Sign APK
if: ${{ github.event.inputs.should_sign == 'true' && (github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both') }}
id: sign_apk
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
run: |
set -euo pipefail
cd android
# locate the unsigned apk
APK_PATH=$(find app/build/outputs/apk -type f -name "*release*.apk" | sort | tail -n1)
echo "🔎 Unsigned APK: $APK_PATH"
# locate the newest build‑tools folder (contains zipalign & apksigner)
BT=$(ls -1 "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -1)
echo "Using build‑tools $BT"
ZIPALIGN="$ANDROID_SDK_ROOT/build-tools/$BT/zipalign"
APKSIGNER="$ANDROID_SDK_ROOT/build-tools/$BT/apksigner"
# Align
"$ZIPALIGN" -v -p 4 "$APK_PATH" release-aligned.apk
# Sign
"$APKSIGNER" sign \
--ks ../release.keystore \
--ks-key-alias "${KEY_ALIAS}" \
--ks-pass "pass:${KEYSTORE_PASSWORD}" \
--key-pass "pass:${KEY_PASSWORD}" \
--out release-signed.apk \
release-aligned.apk
echo "✅ Signed APK ready: release-signed.apk"
echo "signed_apk_path=release-signed.apk" >> $GITHUB_OUTPUT
# ------------------------------------------------
# 7️⃣ Upload artefacts
# ------------------------------------------------
- name: Upload signed APK
if: ${{ github.event.inputs.should_sign == 'true' && (github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both') }}
uses: actions/upload-artifact@v4
with:
name: app-release-apk-${{ github.run_number }}
path: android/release-signed.apk
retention-days: 30
- name: Upload AAB (unsigned – Play will sign it)
if: ${{ github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both' }}
uses: actions/upload-artifact@v4
with:
name: app-release-aab-${{ github.run_number }}
path: android/app/build/outputs/bundle/release/app-release.aab
retention-days: 30
# ------------------------------------------------
# 8️⃣ Cleanup secret files
# ------------------------------------------------
- name: Cleanup
if: always()
run: |
rm -f release.keystore
rm -f android/release-aligned.apk
rm -f android/release-signed.apk
echo "🧹 Cleanup done"