Test Android Builds #265
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: Test Android Builds | |
| on: | |
| push: | |
| branches: [main] | |
| # No paths filter on pull_request — required status checks need this workflow to | |
| # always trigger so the aggregator can report. find-projects below narrows the | |
| # matrix to only projects whose subtree changed, so docs-only / other-framework | |
| # PRs produce an empty matrix → build job skipped → aggregator passes via | |
| # `result == 'skipped'`. | |
| pull_request: | |
| schedule: | |
| - cron: "0 9 * * 1" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # Needed by find-projects' `gh api .../pulls/N/files` call. | |
| pull-requests: read | |
| jobs: | |
| find-projects: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.find.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # Default shallow checkout — we only need the working tree for `find` | |
| # below, not git history. The PR's changed files come from gh api. | |
| - id: find | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| WORKFLOW_FILE: .github/workflows/test-android.yml | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| # Discover any sample with an Android Gradle wrapper. Today: react-native | |
| # samples and native android samples. | |
| ALL=$(find samples -name "gradlew" -not -path "*/node_modules/*" -not -path "*/build/*" -exec dirname {} \; 2>/dev/null | sort) | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| CHANGED=$(gh api --paginate "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --jq '.[].filename' 2>/dev/null || echo "") | |
| if printf '%s\n' "$CHANGED" | grep -qFx "$WORKFLOW_FILE"; then | |
| FILTERED="$ALL" | |
| else | |
| # For each gradlew dir, walk up to find a package.json. If found (an | |
| # RN sample — gradlew sits under <rn-root>/android/), watch the RN | |
| # root: JS dep changes upstairs affect Android autolinking. If not | |
| # found (a native Android sample), watch the gradlew dir itself. | |
| # Mirrors the "Install JS dependencies (RN samples)" step's walk-up. | |
| FILTERED=$(while IFS= read -r proj; do | |
| [ -z "$proj" ] && continue | |
| watch_root="$proj" | |
| d="$proj" | |
| while [ "$d" != "." ] && [ "$d" != "/" ]; do | |
| if [ -f "$d/package.json" ]; then | |
| watch_root="$d" | |
| break | |
| fi | |
| d=$(dirname "$d") | |
| done | |
| if printf '%s\n' "$CHANGED" | grep -q "^${watch_root}/"; then | |
| echo "$proj" | |
| fi | |
| done <<< "$ALL") | |
| fi | |
| else | |
| FILTERED="$ALL" | |
| fi | |
| MATRIX=$(printf '%s\n' "$FILTERED" | jq -R -s -c 'split("\n") | map(select(. != ""))') | |
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | |
| build: | |
| needs: find-projects | |
| if: ${{ needs.find-projects.outputs.matrix != '[]' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| android_dir: ${{ fromJson(needs.find-projects.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: "temurin" | |
| java-version: "17" | |
| - name: Set up Android SDK | |
| # The community `android-actions/setup-android` action isn't on the org's | |
| # third-party action allowlist, so we install Android command-line tools | |
| # ourselves. The build number in the cmdline-tools URL is pinned (Google | |
| # publishes new ones periodically; bump intentionally rather than chasing). | |
| env: | |
| ANDROID_HOME: ${{ runner.temp }}/android-sdk | |
| CMDLINE_TOOLS_ZIP: commandlinetools-linux-11076708_latest.zip | |
| # SHA-256 of the build above as published by Google. Bumping the build | |
| # number requires updating this hash; mismatch fails the workflow so we | |
| # never silently run a corrupted/poisoned download. | |
| CMDLINE_TOOLS_SHA256: 2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258 | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$ANDROID_HOME/cmdline-tools" | |
| cd "$ANDROID_HOME/cmdline-tools" | |
| curl -fsSL "https://dl.google.com/android/repository/$CMDLINE_TOOLS_ZIP" -o tools.zip | |
| echo "$CMDLINE_TOOLS_SHA256 tools.zip" | sha256sum -c - | |
| unzip -q tools.zip | |
| mv cmdline-tools latest | |
| rm tools.zip | |
| # Expose for subsequent steps (Gradle reads ANDROID_HOME). | |
| { | |
| echo "ANDROID_HOME=$ANDROID_HOME" | |
| echo "ANDROID_SDK_ROOT=$ANDROID_HOME" | |
| } >> "$GITHUB_ENV" | |
| { | |
| echo "$ANDROID_HOME/cmdline-tools/latest/bin" | |
| echo "$ANDROID_HOME/platform-tools" | |
| } >> "$GITHUB_PATH" | |
| # Accept licenses (sdkmanager errors out otherwise). | |
| printf 'y\n%.0s' $(seq 1 50) | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null | |
| "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \ | |
| "platform-tools" "platforms;android-36" "build-tools;36.0.0" | |
| # React Native samples need their JS deps installed first so autolinking | |
| # can resolve native modules from node_modules. Detect by walking up to | |
| # the directory that owns package.json. | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Install JS dependencies (RN samples) | |
| run: | | |
| # ${{ matrix.android_dir }} is e.g. "samples/react-native/login-pkce/android". | |
| # Walk up until we find a package.json or hit the repo root. | |
| dir="${{ matrix.android_dir }}" | |
| while [ "$dir" != "." ] && [ "$dir" != "/" ]; do | |
| if [ -f "$dir/package.json" ]; then | |
| echo "Installing JS deps in $dir" | |
| (cd "$dir" && yarn install --immutable) | |
| break | |
| fi | |
| dir=$(dirname "$dir") | |
| done | |
| - name: Generate debug keystore | |
| # The committed sample doesn't ship debug.keystore (gitignored). | |
| # Generate a throwaway one so Gradle's `validateSigningDebug` passes. | |
| run: | | |
| keytool -genkeypair -v \ | |
| -keystore "${{ matrix.android_dir }}/app/debug.keystore" \ | |
| -storepass android -alias androiddebugkey -keypass android \ | |
| -keyalg RSA -keysize 2048 -validity 10000 \ | |
| -dname "CN=Android Debug,O=Android,C=US" | |
| - name: Cache Gradle | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles(format('{0}/gradle/wrapper/gradle-wrapper.properties', matrix.android_dir), format('{0}/build.gradle*', matrix.android_dir), format('{0}/app/build.gradle*', matrix.android_dir)) }} | |
| restore-keys: gradle-${{ runner.os }}- | |
| - name: assembleDebug | |
| working-directory: ${{ matrix.android_dir }} | |
| run: ./gradlew assembleDebug --no-daemon | |
| # Stable aggregator for branch protection. | |
| all-tests-passed: | |
| name: Android tests passed | |
| needs: build | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify build matrix succeeded | |
| if: needs.build.result != 'success' && needs.build.result != 'skipped' | |
| run: exit 1 |