Release Android #11
Workflow file for this run
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: Release Android | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version tag using CalVer (e.g., v2025.12.1 for Dec 2025, patch 1)' | |
| required: true | |
| type: string | |
| upload_to_play: | |
| description: 'Upload to Google Play Internal track' | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| jobs: | |
| # Get version and git info once for all jobs, and run tests if needed | |
| version-info: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| version_number: ${{ steps.version.outputs.version_number }} | |
| git_tag: ${{ steps.git_version.outputs.git_tag }} | |
| git_commit: ${{ steps.git_version.outputs.git_commit }} | |
| git_branch: ${{ steps.git_version.outputs.git_branch }} | |
| build_version: ${{ steps.git_version.outputs.version }} | |
| build_time: ${{ steps.git_version.outputs.build_time }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Get version from tag | |
| id: version | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT | |
| - name: Get git version info | |
| id: git_version | |
| run: scripts/get_version_info.sh github >> $GITHUB_OUTPUT | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| generate-mocks: 'true' | |
| - name: Run tests | |
| run: flutter test | |
| # Build APK and AAB in parallel using matrix strategy | |
| build-artifacts: | |
| needs: version-info | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| build-type: | |
| - name: apk | |
| flutter-command: apk | |
| output-path: build/app/outputs/flutter-apk/app-release.apk | |
| artifact-name: android-apk | |
| - name: appbundle | |
| flutter-command: appbundle | |
| output-path: build/app/outputs/bundle/release/app-release.aab | |
| artifact-name: android-aab | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Setup Flutter App | |
| uses: ./.github/actions/setup-flutter-app | |
| with: | |
| google-services-json: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| generate-mocks: 'true' | |
| - name: Decode upload keystore | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| missing=0 | |
| [ -z "$ANDROID_KEYSTORE_BASE64" ] && echo "::error::ANDROID_KEYSTORE_BASE64 secret is not set. See docs/tooling/github-secrets.md" && missing=1 | |
| [ -z "$ANDROID_KEYSTORE_PASSWORD" ] && echo "::error::ANDROID_KEYSTORE_PASSWORD secret is not set. See docs/tooling/github-secrets.md" && missing=1 | |
| [ -z "$ANDROID_KEY_PASSWORD" ] && echo "::error::ANDROID_KEY_PASSWORD secret is not set. See docs/tooling/github-secrets.md" && missing=1 | |
| [ -z "$ANDROID_KEY_ALIAS" ] && echo "::error::ANDROID_KEY_ALIAS secret is not set. See docs/tooling/github-secrets.md" && missing=1 | |
| [ "$missing" -eq 1 ] && exit 1 | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/upload-keystore.jks | |
| - name: Create key.properties | |
| env: | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| run: | | |
| printf 'storePassword=%s\nkeyPassword=%s\nkeyAlias=%s\nstoreFile=%s\n' \ | |
| "$ANDROID_KEYSTORE_PASSWORD" \ | |
| "$ANDROID_KEY_PASSWORD" \ | |
| "$ANDROID_KEY_ALIAS" \ | |
| "../upload-keystore.jks" \ | |
| > android/key.properties | |
| - name: Build release ${{ matrix.build-type.name }} | |
| run: | | |
| flutter build ${{ matrix.build-type.flutter-command }} --release \ | |
| --dart-define=GIT_TAG=${{ needs.version-info.outputs.git_tag }} \ | |
| --dart-define=GIT_COMMIT=${{ needs.version-info.outputs.git_commit }} \ | |
| --dart-define=GIT_BRANCH=${{ needs.version-info.outputs.git_branch }} \ | |
| --dart-define=BUILD_VERSION=${{ needs.version-info.outputs.build_version }} \ | |
| --dart-define=BUILD_TIME=${{ needs.version-info.outputs.build_time }} | |
| - name: Upload ${{ matrix.build-type.name }} artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.build-type.artifact-name }} | |
| path: ${{ matrix.build-type.output-path }} | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload mapping file (deobfuscation) | |
| if: matrix.build-type.name == 'appbundle' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-mapping | |
| path: build/app/outputs/mapping/release/mapping.txt | |
| if-no-files-found: error | |
| retention-days: 7 | |
| # Create release with all artifacts | |
| create-release: | |
| needs: [version-info, build-artifacts] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download APK artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-apk | |
| path: artifacts/apk | |
| - name: Download AAB artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-aab | |
| path: artifacts/aab | |
| - name: Rename artifacts with version | |
| run: | | |
| VERSION="${{ needs.version-info.outputs.version_number }}" | |
| mkdir -p release-artifacts | |
| cp artifacts/apk/app-release.apk "release-artifacts/cambridge-beer-festival-${VERSION}.apk" | |
| cp artifacts/aab/app-release.aab "release-artifacts/cambridge-beer-festival-${VERSION}.aab" | |
| - name: Generate checksums | |
| working-directory: release-artifacts | |
| run: | | |
| sha256sum *.apk > checksums.txt | |
| sha256sum *.aab >> checksums.txt | |
| cat checksums.txt | |
| - name: Upload Android artifacts to Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.version-info.outputs.version }} | |
| append_body: true | |
| body: | | |
| --- | |
| ### Android | |
| - **APK** (`cambridge-beer-festival-${{ needs.version-info.outputs.version_number }}.apk`) — direct install (requires "Install from unknown sources") | |
| - **AAB** (`cambridge-beer-festival-${{ needs.version-info.outputs.version_number }}.aab`) — Google Play upload; Play re-signs with the app signing key | |
| Package: `ralcock.cbf` | Min SDK: API 21 | Target SDK: API 34 | |
| files: | | |
| release-artifacts/*.apk | |
| release-artifacts/*.aab | |
| release-artifacts/checksums.txt | |
| publish-google-play: | |
| needs: [version-info, build-artifacts] | |
| # Only upload when explicitly requested — automated releases pass upload_to_play=true | |
| if: inputs.upload_to_play | |
| # Allow the workflow to succeed even if Play upload fails (e.g. before first manual upload) | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download AAB artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-aab | |
| path: artifacts/aab | |
| - name: Download mapping file artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: android-mapping | |
| path: artifacts/mapping | |
| - name: Upload to Google Play (Internal track) | |
| uses: r0adkll/upload-google-play@v1 | |
| with: | |
| serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} | |
| packageName: ralcock.cbf | |
| releaseFiles: artifacts/aab/app-release.aab | |
| mappingFile: artifacts/mapping/mapping.txt | |
| track: internal | |
| status: completed |