Skip to content

Build Release APK

Build Release APK #3

name: Build Release APK
concurrency:
group: android-release-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
permissions:
contents: write
jobs:
build-release-apk:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Validate signing secrets
run: |
for key in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
if [ -z "${!key}" ]; then
echo "Missing required secret: $key"
exit 1
fi
done
- name: Prepare signing keystore
run: |
mkdir -p android/app/keystore
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/app/keystore/release.jks
test -s android/app/keystore/release.jks || {
echo "Failed to generate release keystore"
exit 1
}
- name: Build release APK
working-directory: android
env:
RELEASE_STORE_FILE: keystore/release.jks
RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
chmod +x gradlew
./gradlew --no-daemon --console=plain --stacktrace :app:assembleRelease
- name: Normalize APK artifact
run: |
apk_path=$(find android/app/build/outputs/apk/release -name '*.apk' | head -n 1)
test -n "$apk_path" || {
echo "Release APK not found";
exit 1;
}
cp "$apk_path" app-release.apk
- name: Verify APK signature
run: |
apksigner_bin=$(find "${ANDROID_HOME}/build-tools" -type f -name apksigner | sort | tail -n 1)
test -n "$apksigner_bin" || {
echo "apksigner not found in Android SDK build-tools"
exit 1
}
"$apksigner_bin" verify --verbose app-release.apk
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: android-release-apk
path: app-release.apk
- name: Cleanup signing keystore
if: always()
run: rm -f android/app/keystore/release.jks
- name: Read Android version
id: android-version
run: |
version_name=$(sed -n 's/.*versionName = "\([^"]*\)"/\1/p' android/app/build.gradle.kts | head -n 1)
version_code=$(sed -n 's/.*versionCode = \([0-9][0-9]*\).*/\1/p' android/app/build.gradle.kts | head -n 1)
test -n "$version_name" || {
echo "Unable to determine Android versionName";
exit 1;
}
test -n "$version_code" || {
echo "Unable to determine Android versionCode";
exit 1;
}
echo "version_name=$version_name" >> "$GITHUB_OUTPUT"
echo "version_code=$version_code" >> "$GITHUB_OUTPUT"
- name: Create GitHub release
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v1
with:
tag_name: android-v${{ steps.android-version.outputs.version_name }}
name: Android Release v${{ steps.android-version.outputs.version_name }}
files: app-release.apk
generate_release_notes: true
append_body: true
body: |
Android versionCode: ${{ steps.android-version.outputs.version_code }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}