Skip to content

update build.yml

update build.yml #27

Workflow file for this run

name: Build Signed Android APK/AAB

Check failure on line 1 in .github/workflows/build.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/build.yml

Invalid workflow file

(Line: 24, Col: 25): Unrecognized named-value: 'runner'. Located at position 1 within expression: runner.temp
on:
workflow_dispatch:
inputs:
build_type:
description: "Build type"
required: true
default: "release"
type: choice
options:
- debug
- release
push:
tags:
- "v*.*.*"
jobs:
build-android:
runs-on: ubuntu-latest
env:
KEYSTORE_PATH: ${{ github.workspace }}/release.keystore
# Ensure Gradle user home is explicit and consistent for caching
GRADLE_USER_HOME: ${{ runner.temp }}/gradle-user-home
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
- name: Setup Android SDK
uses: android-actions/setup-android@v3
# Ensure ANDROID_SDK_ROOT is set (use known runner path when available)
- name: Ensure ANDROID_SDK_ROOT
shell: bash
run: |
if [ -z "${ANDROID_SDK_ROOT:-}" ] && [ -d "/usr/local/lib/android/sdk" ]; then
echo "ANDROID_SDK_ROOT=/usr/local/lib/android/sdk" >> $GITHUB_ENV
fi
# print for debugging
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT:-$(cat $GITHUB_ENV 2>/dev/null | sed -n 's/^ANDROID_SDK_ROOT=//p' || true)}"
- name: Install dependencies
run: npm ci --legacy-peer-deps
# Expo prebuild generates the android project (creates gradlew etc.)
- name: Expo Prebuild
run: npx expo prebuild --platform android --clean
# Make gradlew executable immediately after prebuild so subsequent steps can run it
- name: Make gradlew executable
run: chmod +x android/gradlew
# Setup Gradle cache (non-fatal on cache service issues)
- name: Setup Gradle cache
uses: gradle/actions/setup-gradle@v3
continue-on-error: true
with:
gradle-home-cache-cleanup: true
# Ensure Gradle distribution is downloaded via the wrapper (resilient fallback)
- name: Ensure Gradle distribution (force wrapper download)
run: |
cd android
# Try running a cheap Gradle task to trigger a download if the distribution isn't present.
./gradlew --no-daemon -v || (echo "Gradle wrapper failed to run; printing wrapper properties for debug" && cat gradle/wrapper/gradle-wrapper.properties && exit 1)
- name: Decode Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > "$KEYSTORE_PATH"
chmod 600 "$KEYSTORE_PATH"
echo "✅ Keystore written to $KEYSTORE_PATH"
# Build APK (unsigned)
- name: Build Release APK (Unsigned)
run: |
cd android
./gradlew assembleRelease --no-daemon
- name: Sign APK
id: sign_apk
run: |
set -euo pipefail
cd android
APK_PATH=$(find ./app/build/outputs/apk/release \
\( -name "*-release-unsigned.apk" -o -name "*-release.apk" \) \
-print -quit)
if [[ -z "$APK_PATH" ]]; then
echo "❌ No APK found – aborting."
exit 1
fi
echo "📦 Found APK: $APK_PATH"
SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "$SDK_ROOT" ]]; then
echo "❌ ANDROID_HOME and ANDROID_SDK_ROOT are not set – aborting."
exit 1
fi
BUILD_TOOLS_DIR=$(ls -1 "$SDK_ROOT/build-tools" | sort -V | tail -n1)
ZIPALIGN="$SDK_ROOT/build-tools/$BUILD_TOOLS_DIR/zipalign"
APKSIGNER="$SDK_ROOT/build-tools/$BUILD_TOOLS_DIR/apksigner"
"$ZIPALIGN" -v -p 4 "$APK_PATH" release-aligned.apk
echo "✅ Aligned → release-aligned.apk"
"$APKSIGNER" sign \
--ks "$KEYSTORE_PATH" \
--ks-key-alias "${{ secrets.KEY_ALIAS }}" \
--ks-pass "pass:${{ secrets.KEYSTORE_PASSWORD }}" \
--key-pass "pass:${{ secrets.KEY_PASSWORD }}" \
--out release-signed.apk \
release-aligned.apk
echo "✅ Signed APK → release-signed.apk"
echo "signed_apk_path=android/release-signed.apk" >> $GITHUB_OUTPUT
- name: Build Release AAB
run: |
cd android
./gradlew bundleRelease --no-daemon
# Sign AAB: remove any existing META-INF signature files first to avoid multiple chains
- name: Sign AAB (remove existing signatures first)
run: |
set -euo pipefail
cd android
AAB_PATH=$(find ./app/build/outputs/bundle/release -name "*-release.aab" -print -quit)
if [[ -z "$AAB_PATH" ]]; then
echo "❌ No AAB found – aborting."
exit 1
fi
echo "📦 Found AAB: $AAB_PATH"
echo "🔍 Removing pre-existing signature files from META-INF (if any)..."
# remove common signature files from META-INF inside the zip (aab is a zip)
zip -d "$AAB_PATH" "META-INF/*.SF" "META-INF/*.RSA" "META-INF/*.DSA" "META-INF/*.EC" >/dev/null 2>&1 || true
echo "📄 META-INF after cleanup (first 200 lines):"
unzip -l "$AAB_PATH" "META-INF/*" | sed -n '1,200p' || true
echo "🔐 Signing AAB with jarsigner..."
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore "$KEYSTORE_PATH" \
-storepass "${{ secrets.KEYSTORE_PASSWORD }}" \
-keypass "${{ secrets.KEY_PASSWORD }}" \
"$AAB_PATH" "${{ secrets.KEY_ALIAS }}"
mv "$AAB_PATH" release-signed.aab
echo "✅ Signed AAB → android/release-signed.aab"
CERT_COUNT=$(unzip -Z1 release-signed.aab | grep -Eo 'META-INF/[^/]+\.(RSA|DSA|EC)' | sort -u | wc -l || true)
echo "Detected META-INF certificate files (count): $CERT_COUNT"
if [[ "$CERT_COUNT" -gt 1 ]]; then
echo "❌ There are still multiple certificate files in META-INF. Aborting to avoid Play Store rejection."
exit 1
fi
echo "✅ AAB has a single certificate chain."
- name: Verify APK signature
run: |
set -euo pipefail
SDK_ROOT="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "$SDK_ROOT" ]]; then
echo "❌ ANDROID_HOME and ANDROID_SDK_ROOT are not set – aborting."
exit 1
fi
BUILD_TOOLS_DIR=$(ls -1 "$SDK_ROOT/build-tools" | sort -V | tail -n1)
APKSIGNER="$SDK_ROOT/build-tools/$BUILD_TOOLS_DIR/apksigner"
APK_FILE="android/release-signed.apk"
if [[ ! -f "$APK_FILE" ]]; then
echo "❌ $APK_FILE not found – aborting."
exit 1
fi
echo "📦 Verifying: $APK_FILE"
"$APKSIGNER" verify --print-certs "$APK_FILE"
SHA1=$("$APKSIGNER" verify --print-certs "$APK_FILE" |
grep "SHA-1 digest:" | head -1 | awk '{print $3}')
echo "Detected SHA‑1: $SHA1"
- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: app-release-apk-${{ github.run_number }}
path: android/release-signed.apk
retention-days: 30
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with:
name: app-release-aab-${{ github.run_number }}
path: android/release-signed.aab
retention-days: 30
- name: Cleanup sensitive files
if: always()
run: |
rm -f "$KEYSTORE_PATH"
echo "✅ Keystore removed"