Merge pull request #103 from kirill-markin/codex/split-ai-chat-remote… #209
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: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'apps/android/**' | |
| - '!apps/android/README.md' | |
| - '!apps/android/docs/**' | |
| - 'cloudbuild.android.yaml' | |
| - 'scripts/run-android-ci.sh' | |
| - 'scripts/run-android-firebase-test-lab.sh' | |
| - 'scripts/run-android-release.sh' | |
| - 'scripts/setup-github-android.sh' | |
| - '.github/workflows/android-ci-reusable.yml' | |
| - '.github/workflows/android-release.yml' | |
| workflow_dispatch: | |
| inputs: | |
| target_sha: | |
| description: Git SHA to release | |
| required: true | |
| type: string | |
| upload_to_play_draft: | |
| description: Upload the signed bundle to Google Play as a draft production release after Android CI succeeds | |
| required: true | |
| default: false | |
| type: boolean | |
| 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 }} | |
| compare_base_sha: ${{ steps.resolve.outputs.compare_base_sha }} | |
| android_changed: ${{ steps.resolve.outputs.android_changed }} | |
| block_reason: ${{ steps.resolve.outputs.block_reason }} | |
| android_version_code: ${{ steps.release-metadata.outputs.android_version_code }} | |
| 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@v6 | |
| with: | |
| ref: ${{ github.event_name == 'push' && github.sha || inputs.target_sha }} | |
| fetch-depth: 0 | |
| - name: Resolve release target | |
| id: resolve | |
| env: | |
| INPUT_TARGET_SHA: ${{ github.event_name == 'push' && github.sha || inputs.target_sha }} | |
| PUSH_BEFORE_SHA: ${{ github.event.before }} | |
| run: | | |
| set -euo pipefail | |
| target_sha="${INPUT_TARGET_SHA}" | |
| if [[ -z "${target_sha}" ]]; then | |
| echo "Target SHA is required." >&2 | |
| exit 1 | |
| fi | |
| target_sha="$(git rev-parse "${target_sha}")" | |
| compare_base_sha="" | |
| before_sha="${PUSH_BEFORE_SHA:-}" | |
| if [[ "${GITHUB_EVENT_NAME}" == "push" ]] && [[ -n "${before_sha}" ]] && [[ "${before_sha}" != "0000000000000000000000000000000000000000" ]]; then | |
| compare_base_sha="${before_sha}" | |
| elif git rev-parse "${target_sha}^" >/dev/null 2>&1; then | |
| compare_base_sha="$(git rev-parse "${target_sha}^")" | |
| fi | |
| changed_files_path="${RUNNER_TEMP}/android-release-changed-files.txt" | |
| if [[ -n "${compare_base_sha}" ]]; then | |
| git diff --name-only "${compare_base_sha}" "${target_sha}" > "${changed_files_path}" | |
| else | |
| git ls-tree -r --name-only "${target_sha}" > "${changed_files_path}" | |
| fi | |
| android_changed=false | |
| while IFS= read -r changed_path; do | |
| case "${changed_path}" in | |
| apps/android/README.md) ;; | |
| apps/android/docs/*) ;; | |
| apps/android/*|cloudbuild.android.yaml|scripts/run-android-ci.sh|scripts/run-android-firebase-test-lab.sh|scripts/run-android-release.sh|scripts/setup-github-android.sh|.github/workflows/android-ci-reusable.yml|.github/workflows/android-release.yml) | |
| android_changed=true | |
| break | |
| ;; | |
| esac | |
| done < "${changed_files_path}" | |
| block_reason="" | |
| if [[ "${android_changed}" != "true" ]]; then | |
| block_reason="android_not_changed" | |
| fi | |
| { | |
| echo "target_sha=${target_sha}" | |
| echo "compare_base_sha=${compare_base_sha}" | |
| echo "android_changed=${android_changed}" | |
| echo "block_reason=${block_reason}" | |
| } >> "${GITHUB_OUTPUT}" | |
| - name: Validate Android release target | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| TARGET_SHA: ${{ steps.resolve.outputs.target_sha }} | |
| ANDROID_CHANGED: ${{ steps.resolve.outputs.android_changed }} | |
| BLOCK_REASON: ${{ steps.resolve.outputs.block_reason }} | |
| UPLOAD_TO_PLAY_DRAFT: ${{ inputs.upload_to_play_draft }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${ANDROID_CHANGED}" != "true" ]] && [[ "${EVENT_NAME}" == "push" || "${UPLOAD_TO_PLAY_DRAFT}" == "true" ]]; then | |
| echo "Android release requires Android changes for ${TARGET_SHA}. Block reason: ${BLOCK_REASON}." >&2 | |
| exit 1 | |
| fi | |
| - 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 )) | |
| 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 [[ "${#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_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 "- Compare base SHA: \`${{ steps.resolve.outputs.compare_base_sha || 'n/a' }}\`" | |
| echo "- Android changed: \`${{ steps.resolve.outputs.android_changed }}\`" | |
| echo "- Block reason: \`${{ steps.resolve.outputs.block_reason || 'n/a' }}\`" | |
| echo "- Version code: \`${{ steps.release-metadata.outputs.android_version_code }}\`" | |
| echo "- Release ID: \`${{ steps.release-metadata.outputs.android_release_id }}\`" | |
| echo "- Play release name: \`${{ steps.release-metadata.outputs.android_play_release_name }}\`" | |
| echo "- Event: \`${{ github.event_name }}\`" | |
| echo "- Upload draft to Play: \`${{ github.event_name == 'push' || inputs.upload_to_play_draft }}\`" | |
| 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/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@v6 | |
| 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/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}" | |
| publish_android: | |
| name: Upload Android bundle to Google Play production draft release | |
| needs: | |
| - preflight | |
| - android_ci | |
| if: ${{ needs.android_ci.result == 'success' && (github.event_name == 'push' || inputs.upload_to_play_draft) }} | |
| 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_RELEASE_ID: ${{ needs.preflight.outputs.android_release_id }} | |
| ANDROID_PLAY_RELEASE_NAME: ${{ needs.preflight.outputs.android_play_release_name }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| 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/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 | |
| - 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;36.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/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 }} | |
| run: | | |
| bash scripts/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 | |
| 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 "- Version code: \`${ANDROID_VERSION_CODE}\`" | |
| 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 submission runs in parallel after the fast Android gate." | |
| 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 "- Android changed: \`${{ needs.preflight.outputs.android_changed }}\`" | |
| echo "- Block reason: \`${{ needs.preflight.outputs.block_reason || 'n/a' }}\`" | |
| 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 "- 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}" |