Android Release #527
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Android Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| target_sha: | |
| description: Git SHA to release. Leave empty to release the selected workflow ref. | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| preflight: | |
| name: Resolve Android release target | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| target_sha: ${{ steps.resolve.outputs.target_sha }} | |
| android_version_code: ${{ steps.release-metadata.outputs.android_version_code }} | |
| android_version_name: ${{ steps.release-metadata.outputs.android_version_name }} | |
| android_target_short_sha: ${{ steps.release-metadata.outputs.android_target_short_sha }} | |
| android_release_id: ${{ steps.release-metadata.outputs.android_release_id }} | |
| android_play_release_name: ${{ steps.release-metadata.outputs.android_play_release_name }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ inputs.target_sha || github.sha }} | |
| fetch-depth: 0 | |
| - name: Resolve release target | |
| id: resolve | |
| env: | |
| INPUT_TARGET_SHA: ${{ inputs.target_sha }} | |
| WORKFLOW_TARGET_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| target_sha="${INPUT_TARGET_SHA}" | |
| if [[ -z "${target_sha}" ]]; then | |
| target_sha="${WORKFLOW_TARGET_SHA}" | |
| fi | |
| target_sha="$(git rev-parse "${target_sha}")" | |
| { | |
| echo "target_sha=${target_sha}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Resolve Android release metadata | |
| id: release-metadata | |
| env: | |
| TARGET_SHA: ${{ steps.resolve.outputs.target_sha }} | |
| GITHUB_RUN_ID: ${{ github.run_id }} | |
| GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} | |
| run: | | |
| set -euo pipefail | |
| base_epoch_seconds=1767225600 | |
| current_epoch_seconds="$(date -u +%s)" | |
| version_code=$(( current_epoch_seconds - base_epoch_seconds )) | |
| version_name="$(sed -nE 's/^[[:space:]]*versionName[[:space:]]*=[[:space:]]*"([^"]+)".*$/\1/p' apps/android/app/build.gradle.kts | head -n 1)" | |
| target_short_sha="$(printf '%s' "${TARGET_SHA}" | cut -c1-8)" | |
| release_id="vc${version_code}-r${GITHUB_RUN_ID}a${GITHUB_RUN_ATTEMPT}-s${target_short_sha}" | |
| play_release_name="main-draft-${release_id}" | |
| if [[ "${version_code}" -le 0 ]]; then | |
| echo "Resolved Android version code must be positive. Got: ${version_code}." >&2 | |
| exit 1 | |
| fi | |
| if [[ "${version_code}" -gt 2100000000 ]]; then | |
| echo "Resolved Android version code exceeds Google Play limit. Got: ${version_code}." >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "${version_name}" ]]; then | |
| echo "Could not resolve Android versionName from apps/android/app/build.gradle.kts." >&2 | |
| exit 1 | |
| fi | |
| if [[ "${#target_short_sha}" -ne 8 ]]; then | |
| echo "Resolved target SHA prefix must be 8 characters. Got: ${target_short_sha}." >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "android_version_code=${version_code}" | |
| echo "android_version_name=${version_name}" | |
| echo "android_target_short_sha=${target_short_sha}" | |
| echo "android_release_id=${release_id}" | |
| echo "android_play_release_name=${play_release_name}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Summarize preflight | |
| run: | | |
| { | |
| echo "## Android release preflight" | |
| echo "" | |
| echo "- Target SHA: \`${{ steps.resolve.outputs.target_sha }}\`" | |
| echo "- Version code: \`${{ steps.release-metadata.outputs.android_version_code }}\`" | |
| echo "- Version name: \`${{ steps.release-metadata.outputs.android_version_name }}\`" | |
| echo "- Release ID: \`${{ steps.release-metadata.outputs.android_release_id }}\`" | |
| echo "- Play release name: \`${{ steps.release-metadata.outputs.android_play_release_name }}\`" | |
| echo "- Upload draft to Play: \`true\`" | |
| echo "- Run Firebase Test Lab: \`true\`" | |
| echo "- Play publish mode: \`draft production release uploaded by CI; final publish happens later in Play Console\`" | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| android_ci: | |
| name: Android CI | |
| needs: | |
| - preflight | |
| concurrency: | |
| group: android-release-ci-main | |
| cancel-in-progress: true | |
| uses: ./.github/workflows/android-ci-reusable.yml | |
| with: | |
| target_ref: ${{ needs.preflight.outputs.target_sha }} | |
| android_version_code: ${{ needs.preflight.outputs.android_version_code }} | |
| secrets: inherit | |
| firebase_test_lab_submission: | |
| name: Firebase Test Lab app instrumentation | |
| needs: | |
| - preflight | |
| - android_ci | |
| if: ${{ needs.android_ci.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| outputs: | |
| submission_state: ${{ steps.collect.outputs.submission_state }} | |
| matrix_id: ${{ steps.collect.outputs.matrix_id }} | |
| results_path: ${{ steps.collect.outputs.results_path }} | |
| device_descriptor: ${{ steps.collect.outputs.device_descriptor }} | |
| steps: | |
| - name: Validate Firebase Test Lab configuration | |
| id: validate-config | |
| env: | |
| GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }} | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| GCP_SERVICE_ACCOUNT_EMAIL: ${{ vars.GCP_SERVICE_ACCOUNT_EMAIL }} | |
| ANDROID_FTL_DEVICE_MODEL: ${{ vars.ANDROID_FTL_DEVICE_MODEL }} | |
| ANDROID_FTL_DEVICE_VERSION: ${{ vars.ANDROID_FTL_DEVICE_VERSION }} | |
| ANDROID_FTL_RESULTS_BUCKET: ${{ vars.ANDROID_FTL_RESULTS_BUCKET }} | |
| ANDROID_FTL_RESULTS_DIR: ${{ vars.ANDROID_FTL_RESULTS_DIR }} | |
| INPUT_RELEASE_ID: ${{ needs.preflight.outputs.android_release_id }} | |
| run: | | |
| set -euo pipefail | |
| missing_values=() | |
| for variable_name in \ | |
| GCP_PROJECT_ID \ | |
| GCP_WORKLOAD_IDENTITY_PROVIDER \ | |
| GCP_SERVICE_ACCOUNT_EMAIL \ | |
| ANDROID_FTL_DEVICE_MODEL \ | |
| ANDROID_FTL_DEVICE_VERSION \ | |
| ANDROID_FTL_RESULTS_BUCKET \ | |
| ANDROID_FTL_RESULTS_DIR; do | |
| if [[ -z "${!variable_name}" ]]; then | |
| missing_values+=("${variable_name}") | |
| fi | |
| done | |
| results_dir="" | |
| results_path="" | |
| device_descriptor="" | |
| if [[ -n "${ANDROID_FTL_RESULTS_DIR}" ]]; then | |
| results_dir="${ANDROID_FTL_RESULTS_DIR%/}/${INPUT_RELEASE_ID}" | |
| fi | |
| if [[ -n "${ANDROID_FTL_RESULTS_BUCKET}" ]] && [[ -n "${results_dir}" ]]; then | |
| results_path="${ANDROID_FTL_RESULTS_BUCKET%/}/${results_dir}" | |
| fi | |
| if [[ -n "${ANDROID_FTL_DEVICE_MODEL}" ]] && [[ -n "${ANDROID_FTL_DEVICE_VERSION}" ]]; then | |
| device_descriptor="model=${ANDROID_FTL_DEVICE_MODEL},version=${ANDROID_FTL_DEVICE_VERSION},locale=en,orientation=portrait" | |
| fi | |
| if [[ "${#missing_values[@]}" -gt 0 ]]; then | |
| printf 'Missing required GitHub Actions variables for Android app instrumentation: %s\n' "${missing_values[*]}" >&2 | |
| echo "Configure them with bash scripts/android/setup-github-android.sh or in the GitHub repository settings." >&2 | |
| echo "submission_state=configuration_invalid" >> "${GITHUB_OUTPUT}" | |
| else | |
| echo "submission_state=ready" >> "${GITHUB_OUTPUT}" | |
| fi | |
| { | |
| echo "results_dir=${results_dir}" | |
| echo "results_path=${results_path}" | |
| echo "device_descriptor=${device_descriptor}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - uses: actions/checkout@v7 | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' }} | |
| with: | |
| ref: ${{ needs.preflight.outputs.target_sha }} | |
| - uses: google-github-actions/auth@v3 | |
| id: auth | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' }} | |
| continue-on-error: true | |
| with: | |
| workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ vars.GCP_SERVICE_ACCOUNT_EMAIL }} | |
| project_id: ${{ vars.GCP_PROJECT_ID }} | |
| - uses: google-github-actions/setup-gcloud@v3 | |
| id: setup-gcloud | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' && steps.auth.outcome == 'success' }} | |
| continue-on-error: true | |
| with: | |
| project_id: ${{ vars.GCP_PROJECT_ID }} | |
| - name: Download debug APKs | |
| id: download-debug-apks | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' && steps.auth.outcome == 'success' && steps.setup-gcloud.outcome == 'success' }} | |
| continue-on-error: true | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: android-debug-apks | |
| path: /tmp/android-debug-apks | |
| - name: Resolve debug APK paths | |
| id: resolve-debug-apks | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' && steps.auth.outcome == 'success' && steps.setup-gcloud.outcome == 'success' && steps.download-debug-apks.outcome == 'success' }} | |
| continue-on-error: true | |
| run: | | |
| app_apk_path="$(find /tmp/android-debug-apks -type f -name 'app-debug.apk' | head -n 1)" | |
| test_apk_path="$(find /tmp/android-debug-apks -type f -name 'app-debug-androidTest.apk' | head -n 1)" | |
| if [[ -z "${app_apk_path}" ]]; then | |
| echo "App APK not found in downloaded artifact." >&2 | |
| find /tmp/android-debug-apks -maxdepth 5 -type f >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "${test_apk_path}" ]]; then | |
| echo "Android test APK not found in downloaded artifact." >&2 | |
| find /tmp/android-debug-apks -maxdepth 5 -type f >&2 | |
| exit 1 | |
| fi | |
| printf 'APP_APK_PATH=%s\n' "${app_apk_path}" >> "${GITHUB_ENV}" | |
| printf 'TEST_APK_PATH=%s\n' "${test_apk_path}" >> "${GITHUB_ENV}" | |
| - name: Submit app instrumentation tests to Firebase Test Lab | |
| id: submit | |
| if: ${{ steps.validate-config.outputs.submission_state == 'ready' && steps.auth.outcome == 'success' && steps.setup-gcloud.outcome == 'success' && steps.download-debug-apks.outcome == 'success' && steps.resolve-debug-apks.outcome == 'success' }} | |
| env: | |
| GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }} | |
| ANDROID_FTL_DEVICE_MODEL: ${{ vars.ANDROID_FTL_DEVICE_MODEL }} | |
| ANDROID_FTL_DEVICE_VERSION: ${{ vars.ANDROID_FTL_DEVICE_VERSION }} | |
| ANDROID_FTL_RESULTS_BUCKET: ${{ vars.ANDROID_FTL_RESULTS_BUCKET }} | |
| RESULTS_DIR: ${{ steps.validate-config.outputs.results_dir }} | |
| RELEASE_ID: ${{ needs.preflight.outputs.android_release_id }} | |
| run: | | |
| set -euo pipefail | |
| command_output_file="${RUNNER_TEMP}/firebase-test-lab-submit.log" | |
| set +e | |
| bash scripts/android/run-android-firebase-test-lab.sh \ | |
| --project-id "${GCP_PROJECT_ID}" \ | |
| --device-model "${ANDROID_FTL_DEVICE_MODEL}" \ | |
| --device-version "${ANDROID_FTL_DEVICE_VERSION}" \ | |
| --app-path "${APP_APK_PATH}" \ | |
| --test-path "${TEST_APK_PATH}" \ | |
| --timeout "30m" \ | |
| --test-targets "package com.flashcardsopensourceapp.app" \ | |
| --results-bucket "${ANDROID_FTL_RESULTS_BUCKET}" \ | |
| --results-dir "${RESULTS_DIR}" \ | |
| --async \ | |
| > "${command_output_file}" 2>&1 | |
| command_exit_code=$? | |
| set -e | |
| cat "${command_output_file}" | |
| matrix_id="$(grep -o 'matrix-[[:alnum:]]\+' "${command_output_file}" | head -n 1 || true)" | |
| if [[ "${command_exit_code}" -eq 0 ]] && [[ -n "${matrix_id}" ]]; then | |
| submission_state="submitted" | |
| else | |
| submission_state="submission_failed" | |
| echo "Firebase Test Lab submission failed. Exit code: ${command_exit_code}. Release ID: ${RELEASE_ID}." >&2 | |
| fi | |
| { | |
| echo "submission_state=${submission_state}" | |
| echo "matrix_id=${matrix_id}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Collect Firebase Test Lab outcome | |
| if: always() | |
| id: collect | |
| env: | |
| VALIDATION_STATE: ${{ steps.validate-config.outputs.submission_state }} | |
| AUTH_STEP_OUTCOME: ${{ steps.auth.outcome }} | |
| GCLOUD_STEP_OUTCOME: ${{ steps.setup-gcloud.outcome }} | |
| DOWNLOAD_STEP_OUTCOME: ${{ steps.download-debug-apks.outcome }} | |
| RESOLVE_STEP_OUTCOME: ${{ steps.resolve-debug-apks.outcome }} | |
| SUBMIT_STATE: ${{ steps.submit.outputs.submission_state }} | |
| SUBMIT_MATRIX_ID: ${{ steps.submit.outputs.matrix_id }} | |
| RESULTS_PATH: ${{ steps.validate-config.outputs.results_path }} | |
| DEVICE_DESCRIPTOR: ${{ steps.validate-config.outputs.device_descriptor }} | |
| run: | | |
| set -euo pipefail | |
| submission_state="${VALIDATION_STATE}" | |
| matrix_id="${SUBMIT_MATRIX_ID}" | |
| if [[ "${VALIDATION_STATE}" == "ready" ]]; then | |
| if [[ "${AUTH_STEP_OUTCOME}" != "success" ]]; then | |
| submission_state="auth_failed" | |
| elif [[ "${GCLOUD_STEP_OUTCOME}" != "success" ]]; then | |
| submission_state="setup_failed" | |
| elif [[ "${DOWNLOAD_STEP_OUTCOME}" != "success" ]]; then | |
| submission_state="artifact_download_failed" | |
| elif [[ "${RESOLVE_STEP_OUTCOME}" != "success" ]]; then | |
| submission_state="artifact_resolution_failed" | |
| elif [[ -n "${SUBMIT_STATE}" ]]; then | |
| submission_state="${SUBMIT_STATE}" | |
| else | |
| submission_state="submission_failed" | |
| fi | |
| fi | |
| { | |
| echo "submission_state=${submission_state}" | |
| echo "matrix_id=${matrix_id}" | |
| echo "results_path=${RESULTS_PATH}" | |
| echo "device_descriptor=${DEVICE_DESCRIPTOR}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Summarize Firebase Test Lab submission | |
| if: always() | |
| env: | |
| SUBMISSION_STATE: ${{ steps.collect.outputs.submission_state }} | |
| MATRIX_ID: ${{ steps.collect.outputs.matrix_id }} | |
| RESULTS_PATH: ${{ steps.collect.outputs.results_path }} | |
| DEVICE_DESCRIPTOR: ${{ steps.collect.outputs.device_descriptor }} | |
| RELEASE_ID: ${{ needs.preflight.outputs.android_release_id }} | |
| run: | | |
| { | |
| echo "## Firebase Test Lab submission" | |
| echo "" | |
| echo "- Submission state: \`${SUBMISSION_STATE}\`" | |
| echo "- Release ID: \`${RELEASE_ID}\`" | |
| echo "- Target SHA: \`${{ needs.preflight.outputs.target_sha }}\`" | |
| echo "- Matrix ID: \`${MATRIX_ID:-n/a}\`" | |
| echo "- Results path: \`${RESULTS_PATH:-n/a}\`" | |
| echo "- Device: \`${DEVICE_DESCRIPTOR:-n/a}\`" | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| - name: Require Firebase Test Lab submission | |
| if: always() | |
| env: | |
| SUBMISSION_STATE: ${{ steps.collect.outputs.submission_state }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${SUBMISSION_STATE}" != "submitted" ]]; then | |
| echo "Firebase Test Lab submission is required for Android Release. Submission state: ${SUBMISSION_STATE:-unknown}." >&2 | |
| exit 1 | |
| fi | |
| publish_android: | |
| name: Upload Android bundle to Google Play production draft release | |
| needs: | |
| - preflight | |
| - android_ci | |
| - firebase_test_lab_submission | |
| if: ${{ needs.android_ci.result == 'success' && needs.firebase_test_lab_submission.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| concurrency: | |
| group: android-release-publish-main | |
| cancel-in-progress: false | |
| env: | |
| ANDROID_VERSION_CODE: ${{ needs.preflight.outputs.android_version_code }} | |
| ANDROID_VERSION_NAME: ${{ needs.preflight.outputs.android_version_name }} | |
| ANDROID_RELEASE_ID: ${{ needs.preflight.outputs.android_release_id }} | |
| ANDROID_PLAY_RELEASE_NAME: ${{ needs.preflight.outputs.android_play_release_name }} | |
| ANDROID_SENTRY_DSN: ${{ vars.ANDROID_SENTRY_DSN }} | |
| ANDROID_SENTRY_TRACES_SAMPLE_RATE: ${{ vars.ANDROID_SENTRY_TRACES_SAMPLE_RATE || '0' }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG }} | |
| SENTRY_ANDROID_PROJECT: ${{ vars.SENTRY_ANDROID_PROJECT }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.preflight.outputs.target_sha }} | |
| - name: Validate Google Play configuration | |
| env: | |
| GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }} | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| GCP_PLAY_SERVICE_ACCOUNT_EMAIL: ${{ vars.GCP_PLAY_SERVICE_ACCOUNT_EMAIL }} | |
| ANDROID_PLAY_PACKAGE_NAME: ${{ vars.ANDROID_PLAY_PACKAGE_NAME }} | |
| run: | | |
| missing_values=() | |
| for variable_name in \ | |
| GCP_PROJECT_ID \ | |
| GCP_WORKLOAD_IDENTITY_PROVIDER \ | |
| GCP_PLAY_SERVICE_ACCOUNT_EMAIL \ | |
| ANDROID_PLAY_PACKAGE_NAME; do | |
| if [[ -z "${!variable_name}" ]]; then | |
| missing_values+=("${variable_name}") | |
| fi | |
| done | |
| if [[ "${#missing_values[@]}" -gt 0 ]]; then | |
| printf 'Missing required GitHub Actions variables: %s\n' "${missing_values[*]}" >&2 | |
| echo "Configure them in the GitHub repository settings or with bash scripts/android/setup-github-android.sh." >&2 | |
| exit 1 | |
| fi | |
| - name: Validate Android Sentry configuration | |
| env: | |
| ANDROID_SENTRY_DSN: ${{ vars.ANDROID_SENTRY_DSN }} | |
| SENTRY_ORG: ${{ vars.SENTRY_ORG }} | |
| SENTRY_ANDROID_PROJECT: ${{ vars.SENTRY_ANDROID_PROJECT }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: | | |
| missing_values=() | |
| for variable_name in \ | |
| ANDROID_SENTRY_DSN \ | |
| SENTRY_ORG \ | |
| SENTRY_ANDROID_PROJECT \ | |
| SENTRY_AUTH_TOKEN; do | |
| if [[ -z "${!variable_name}" ]]; then | |
| missing_values+=("${variable_name}") | |
| fi | |
| done | |
| if [[ "${#missing_values[@]}" -gt 0 ]]; then | |
| printf 'Missing required Android Sentry release values: %s\n' "${missing_values[*]}" >&2 | |
| echo "Configure repository variables/secrets or run bash scripts/android/setup-github-android.sh." >&2 | |
| exit 1 | |
| fi | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - uses: gradle/actions/setup-gradle@v6.2.0 | |
| - uses: google-github-actions/auth@v3 | |
| id: auth | |
| with: | |
| create_credentials_file: true | |
| workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ vars.GCP_PLAY_SERVICE_ACCOUNT_EMAIL }} | |
| project_id: ${{ vars.GCP_PROJECT_ID }} | |
| - name: Install Android SDK packages | |
| env: | |
| ANDROID_SDK_ROOT: /usr/local/lib/android/sdk | |
| run: | | |
| yes | "${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" --licenses >/dev/null | |
| "${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" \ | |
| "platform-tools" \ | |
| "platforms;android-36" \ | |
| "build-tools;37.0.0" | |
| - name: Validate Android signing secrets | |
| env: | |
| ANDROID_UPLOAD_KEYSTORE_BASE64: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_BASE64 }} | |
| ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} | |
| ANDROID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_UPLOAD_KEY_ALIAS }} | |
| ANDROID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEY_PASSWORD }} | |
| run: | | |
| missing_secrets=() | |
| for secret_name in \ | |
| ANDROID_UPLOAD_KEYSTORE_BASE64 \ | |
| ANDROID_UPLOAD_KEYSTORE_PASSWORD \ | |
| ANDROID_UPLOAD_KEY_ALIAS \ | |
| ANDROID_UPLOAD_KEY_PASSWORD; do | |
| if [[ -z "${!secret_name}" ]]; then | |
| missing_secrets+=("${secret_name}") | |
| fi | |
| done | |
| if [[ "${#missing_secrets[@]}" -gt 0 ]]; then | |
| printf 'Missing required GitHub Actions secrets: %s\n' "${missing_secrets[*]}" >&2 | |
| echo "Configure them in the GitHub repository settings or with bash scripts/android/setup-github-android.sh." >&2 | |
| exit 1 | |
| fi | |
| - name: Prepare Android upload keystore | |
| env: | |
| ANDROID_UPLOAD_KEYSTORE_BASE64: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_BASE64 }} | |
| run: | | |
| keystore_path="${RUNNER_TEMP}/android-upload-key.jks" | |
| printf '%s' "${ANDROID_UPLOAD_KEYSTORE_BASE64}" | base64 --decode > "${keystore_path}" | |
| if [[ ! -s "${keystore_path}" ]]; then | |
| echo "Decoded Android upload keystore is empty." >&2 | |
| exit 1 | |
| fi | |
| printf 'ANDROID_KEYSTORE_PATH=%s\n' "${keystore_path}" >> "${GITHUB_ENV}" | |
| - name: Build signed Android App Bundle | |
| env: | |
| ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} | |
| ANDROID_UPLOAD_KEY_ALIAS: ${{ secrets.ANDROID_UPLOAD_KEY_ALIAS }} | |
| ANDROID_UPLOAD_KEY_PASSWORD: ${{ secrets.ANDROID_UPLOAD_KEY_PASSWORD }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| run: | | |
| bash scripts/android/run-android-release.sh \ | |
| --version-code "${ANDROID_VERSION_CODE}" \ | |
| --keystore-path "${ANDROID_KEYSTORE_PATH}" \ | |
| --keystore-password "${ANDROID_UPLOAD_KEYSTORE_PASSWORD}" \ | |
| --key-alias "${ANDROID_UPLOAD_KEY_ALIAS}" \ | |
| --key-password "${ANDROID_UPLOAD_KEY_PASSWORD}" | |
| - name: Upload signed Android App Bundle artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: android-release-bundle | |
| if-no-files-found: error | |
| path: apps/android/app/build/outputs/bundle/release/app-release.aab | |
| - name: Upload bundle to Google Play production track as draft release | |
| uses: r0adkll/upload-google-play@v1.1.5 | |
| with: | |
| serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }} | |
| packageName: ${{ vars.ANDROID_PLAY_PACKAGE_NAME }} | |
| releaseFiles: apps/android/app/build/outputs/bundle/release/app-release.aab | |
| tracks: production | |
| status: draft | |
| releaseName: ${{ env.ANDROID_PLAY_RELEASE_NAME }} | |
| - name: Summarize Play draft upload | |
| run: | | |
| { | |
| echo "## Android Play draft upload" | |
| echo "" | |
| echo "- Release ID: \`${ANDROID_RELEASE_ID}\`" | |
| echo "- Production track release name: \`${ANDROID_PLAY_RELEASE_NAME}\`" | |
| echo "- Sentry release: \`com.flashcardsopensourceapp.app@${ANDROID_VERSION_NAME}+${ANDROID_VERSION_CODE}\`" | |
| echo "- Version code: \`${ANDROID_VERSION_CODE}\`" | |
| echo "- Version name: \`${ANDROID_VERSION_NAME}\`" | |
| echo "- Target SHA: \`${{ needs.preflight.outputs.target_sha }}\`" | |
| echo "- GitHub run: \`${{ github.run_id }}\` attempt \`${{ github.run_attempt }}\`" | |
| echo "- GitHub run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| echo "- Upload state: \`draft\`" | |
| echo "- Firebase Test Lab: \`required submission in this Android Release run\`" | |
| echo "- Next step: review Play App strings translations in Play Console, then publish the release manually." | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| release_summary: | |
| name: Android release summary | |
| if: ${{ always() }} | |
| needs: | |
| - preflight | |
| - android_ci | |
| - firebase_test_lab_submission | |
| - publish_android | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Summarize Android release outcome | |
| run: | | |
| { | |
| echo "## Android release summary" | |
| echo "" | |
| echo "- Target SHA: \`${{ needs.preflight.outputs.target_sha }}\`" | |
| echo "- Version code: \`${{ needs.preflight.outputs.android_version_code || 'n/a' }}\`" | |
| echo "- Release ID: \`${{ needs.preflight.outputs.android_release_id || 'n/a' }}\`" | |
| echo "- Play release name: \`${{ needs.preflight.outputs.android_play_release_name || 'n/a' }}\`" | |
| echo "- Android CI result: \`${{ needs.android_ci.result }}\`" | |
| echo "- Firebase Test Lab submission result: \`${{ needs.firebase_test_lab_submission.result }}\`" | |
| echo "- Firebase Test Lab requested: \`true\`" | |
| echo "- Play draft upload requested: \`true\`" | |
| echo "- Android Play draft upload result: \`${{ needs.publish_android.result }}\`" | |
| echo "- Firebase Test Lab submission: \`${{ needs.firebase_test_lab_submission.outputs.submission_state || 'n/a' }}\`" | |
| echo "- Firebase Test Lab matrix ID: \`${{ needs.firebase_test_lab_submission.outputs.matrix_id || 'n/a' }}\`" | |
| echo "- Firebase Test Lab results path: \`${{ needs.firebase_test_lab_submission.outputs.results_path || 'n/a' }}\`" | |
| echo "- Firebase Test Lab device: \`${{ needs.firebase_test_lab_submission.outputs.device_descriptor || 'n/a' }}\`" | |
| echo "- Play publish mode: \`CI uploads a production-track draft only; Play Console publication remains manual\`" | |
| echo "- Run details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| } >> "${GITHUB_STEP_SUMMARY}" |