name: Release Android APK on: release: types: [published] workflow_dispatch: inputs: attach_to_release_tag: description: "Optional release tag to attach the APK to (leave empty to skip upload)" required: false default: "" jobs: build: runs-on: ubuntu-latest timeout-minutes: 45 permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 cache: gradle - uses: android-actions/setup-android@v3 - name: Cache Android NDK / CMake uses: actions/cache@v4 with: path: | ~/.android/build-cache /usr/local/lib/android/sdk/ndk /usr/local/lib/android/sdk/cmake key: android-sdk-${{ runner.os }}-${{ hashFiles('android/build.gradle', 'android/gradle.properties', 'android/app/build.gradle') }} restore-keys: android-sdk-${{ runner.os }}- - name: Compute native cache key input run: | node -e " const fs = require('fs'); const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); if (lock.packages && lock.packages['']) delete lock.packages[''].version; delete lock.version; fs.writeFileSync('.cxx-cache-input.json', JSON.stringify(lock.packages || {})); " - name: Cache native CMake build outputs uses: actions/cache@v4 with: path: | android/app/.cxx android/app/build/intermediates/cxx android/app/build/intermediates/merged_native_libs android/app/build/intermediates/stripped_native_libs key: cxx-${{ runner.os }}-${{ hashFiles('.cxx-cache-input.json', 'android/build.gradle', 'android/app/build.gradle') }} restore-keys: cxx-${{ runner.os }}- - name: Install JS dependencies run: npm ci - name: Decode release keystore id: keystore env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} run: | if [ -n "$KEYSTORE_BASE64" ]; then echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore" echo "path=$RUNNER_TEMP/release.keystore" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" else echo "present=false" >> "$GITHUB_OUTPUT" fi - name: Build release APK working-directory: android env: ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_FILE: ${{ steps.keystore.outputs.path }} ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | chmod +x ./gradlew ./gradlew assembleRelease --no-daemon - name: Rename APK with version id: artifact run: | VERSION=$(cat VERSION | tr -d '[:space:]') SRC=android/app/build/outputs/apk/release/app-release.apk DEST="bulwark-mobile-${VERSION}-${GITHUB_SHA::7}.apk" cp "$SRC" "$DEST" echo "path=$DEST" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@v4 with: name: ${{ steps.artifact.outputs.path }} path: ${{ steps.artifact.outputs.path }} if-no-files-found: error - name: Resolve target release tag id: target_tag env: GH_TOKEN: ${{ github.token }} run: | if [ "${{ github.event_name }}" = "release" ]; then TAG="${{ github.event.release.tag_name }}" elif [ -n "${{ github.event.inputs.attach_to_release_tag }}" ]; then TAG="${{ github.event.inputs.attach_to_release_tag }}" else TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' || true) fi echo "tag=$TAG" >> "$GITHUB_OUTPUT" if [ -n "$TAG" ]; then echo "Will attach APK to release: $TAG" else echo "No release tag resolved - APK is in workflow artifacts only." fi - name: Attach APK to release if: steps.target_tag.outputs.tag != '' uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.target_tag.outputs.tag }} files: ${{ steps.artifact.outputs.path }}