diff --git a/.github/workflows/build-submit-android.yml b/.github/workflows/build-submit-android.yml index 87c7457706..e3e01a48f0 100644 --- a/.github/workflows/build-submit-android.yml +++ b/.github/workflows/build-submit-android.yml @@ -10,12 +10,25 @@ on: options: - testflight-android - production + submit: + type: boolean + description: Submit the build to Google Play (disable to only produce the APK artifact) + default: true workflow_call: inputs: profile: type: string description: Build profile to use required: true + submit: + type: boolean + description: Submit the build to Google Play (disable to only produce the APK artifact) + default: true + runner: + type: string + description: Runner for the build job (defaults to Linux-x64-32core) + required: false + default: '' outputs: package-version: description: Version from package.json @@ -56,48 +69,24 @@ permissions: jobs: build: if: github.repository == 'bluesky-social/social-app' - name: Build and Submit Android - runs-on: Linux-x64-32core + name: Build Android + runs-on: ${{ inputs.runner || 'Linux-x64-32core' }} concurrency: group: android-build cancel-in-progress: false outputs: package-version: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }} version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }} - apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk steps: - - name: Check for EXPO_TOKEN - run: > - if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then - echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" - exit 1 - fi - - name: ⬇️ Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 5 - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - - - name: 🔧 Setup Node - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: 🔧 Setup Expo project + uses: ./.github/actions/setup-expo-project with: - node-version-file: package.json - cache: pnpm - - - name: 🪛 Setup jq - uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1 - - - name: ⚙️ Install dependencies - run: pnpm install --frozen-lockfile - - - name: 🔨 Setup Expo CLI - uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0 - with: - eas-version: '19.0.5' - packager: 'pnpm --allow-build=dtrace-provider' - token: ${{ secrets.EXPO_TOKEN }} + expo-token: ${{ secrets.EXPO_TOKEN }} - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: @@ -105,35 +94,26 @@ jobs: java-version: "17" - name: 🔤 Compile translations - run: pnpm intl:build 2>&1 | tee i18n.log - - - name: Check for i18n compilation errors - run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation - errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi + uses: ./.github/actions/compile-i18n # EXPO_PUBLIC_ENV is handled in eas.json - - name: Env + - name: ✏️ Write environment variables id: env - run: | - export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}' - echo "${{ secrets.ENV_TOKEN }}" > .env - echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env - echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env - echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env - echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env - echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env - echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env - echo "$json" > google-services.json + uses: ./.github/actions/write-env + with: + env-token: ${{ secrets.ENV_TOKEN }} + sentry-dsn: ${{ secrets.SENTRY_DSN }} + bitdrift-api-key: ${{ secrets.BITDRIFT_API_KEY }} + gcp-project-id: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }} + google-services-token: ${{ secrets.GOOGLE_SERVICES_TOKEN }} - name: 🏗️ EAS Build env: PROFILE: ${{ inputs.profile || 'testflight-android' }} run: > SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} - SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} + SENTRY_RELEASE=${{ steps.env.outputs.release-version }} + SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }} pnpm use-build-number-with-bump pnpm eas build -p android --profile $PROFILE @@ -143,6 +123,39 @@ jobs: id: get-build-info run: bash scripts/setGitHubOutput.sh + # Hands the built bundle off to the submit / universalApk jobs. Retention is + # deliberately short (1 day) since it's only an intra-run handoff artifact. + - name: 🚀 Upload AAB artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: android-aab-${{ github.run_id }} + retention-days: 1 + if-no-files-found: error + path: build.aab + + submit: + name: Submit to Google Play + runs-on: ubuntu-latest + needs: [build] + # Submit unless explicitly disabled; on events where inputs is empty this still submits. + if: ${{ inputs.submit != false }} + steps: + # eas submit reads app config from the repo, so we need a checkout. + - name: ⬇️ Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 5 + + - name: 🔧 Setup Expo project + uses: ./.github/actions/setup-expo-project + with: + expo-token: ${{ secrets.EXPO_TOKEN }} + + - name: ⬇️ Download AAB artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: android-aab-${{ github.run_id }} + - name: 🚀 Submit to Google Play env: PROFILE: ${{ inputs.profile || 'testflight-android' }} @@ -156,7 +169,43 @@ jobs: webhook-type: incoming-webhook payload-templated: true payload: | - {"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"} + {"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"} + + # Record the commit only after a successful submit, so a failed submit doesn't + # advance the "most recent testflight" marker. + - name: ⬇️ Restore Cache + id: get-base-commit + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + if: ${{ inputs.profile == 'testflight-android' }} + with: + path: most-recent-testflight-commit.txt + key: most-recent-testflight-commit + + - name: ✏️ Write commit hash to cache + if: ${{ inputs.profile == 'testflight-android' }} + env: + GITHUB_SHA: ${{ github.sha }} + run: echo $GITHUB_SHA > most-recent-testflight-commit.txt + + # Runs in parallel with submit: the QA APK shouldn't be blocked by a Play submission failure. + universalApk: + name: Build universal APK + runs-on: ubuntu-latest + needs: [build] + outputs: + apk-artifact-name: build-${{ steps.timestamp.outputs.time }}.apk + steps: + - name: ⬇️ Download AAB artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: android-aab-${{ github.run_id }} + + # bundletool needs a JRE. ubuntu-latest ships a default JDK, but pin it explicitly + # like the build job so the toolchain is deterministic. + - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 + with: + distribution: "temurin" + java-version: "17" - name: 🔧 Setup bundletool uses: amyu/setup-bundletool@cc2e1857284660bd625e43f2c8a45626f034302f # v1.1 @@ -164,19 +213,24 @@ jobs: version: "1.18.3" - name: 🔑 Decode keystore - run: echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > - keystore.jks + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + run: echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > keystore.jks - name: 📦 Build signed universal APK + env: + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | bundletool build-apks \ --bundle=build.aab \ --output=universal.apks \ --mode=universal \ --ks=keystore.jks \ - --ks-pass=pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }} \ - --ks-key-alias=${{ secrets.ANDROID_KEY_ALIAS }} \ - --key-pass=pass:${{ secrets.ANDROID_KEY_PASSWORD }} + --ks-pass=pass:"$ANDROID_KEYSTORE_PASSWORD" \ + --ks-key-alias="$ANDROID_KEY_ALIAS" \ + --key-pass=pass:"$ANDROID_KEY_PASSWORD" - name: 📋 Rename to .zip for extraction run: mv universal.apks universal.zip @@ -204,21 +258,7 @@ jobs: webhook-type: incoming-webhook payload-templated: true payload: | - {"text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"} - - - name: ⬇️ Restore Cache - id: get-base-commit - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: ${{ inputs.profile == 'testflight-android' }} - with: - path: most-recent-testflight-commit.txt - key: most-recent-testflight-commit - - - name: ✏️ Write commit hash to cache - if: ${{ inputs.profile == 'testflight-android' }} - env: - GITHUB_SHA: ${{ github.sha }} - run: echo $GITHUB_SHA > most-recent-testflight-commit.txt + {"text": "Android ${{ inputs.profile || 'testflight-android' }} APK is ready for testing!\n```Artifact: ${{ steps.upload-artifact.outputs.artifact-url }}\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"} # Releases are cut from tags named after the version (e.g. "1.124.0"), so when a production # build is dispatched against such a tag we attach the APK to the matching release. This runs @@ -226,7 +266,7 @@ jobs: attachToRelease: name: Attach APK to GitHub Release runs-on: ubuntu-latest - needs: [build] + needs: [build, universalApk] if: ${{ inputs.profile == 'production' && github.ref_type == 'tag' && github.repository == 'bluesky-social/social-app' }} permissions: contents: write @@ -254,7 +294,7 @@ jobs: if: ${{ steps.release-check.outputs.exists == 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ needs.build.outputs.apk-artifact-name }} + name: ${{ needs.universalApk.outputs.apk-artifact-name }} - name: 🏷️ Rename APK for release if: ${{ steps.release-check.outputs.exists == 'true' }}