ci: configure git identity for Android release tags #17
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 APK | |
| on: | |
| pull_request: | |
| paths: | |
| - '.github/workflows/android-apk.yml' | |
| - 'android/**' | |
| - 'src/**' | |
| - 'include/**' | |
| - 'lib/**' | |
| - 'CMakeLists.txt' | |
| - 'tools/**' | |
| push: | |
| branches: | |
| - main | |
| - android | |
| - 'android/**' | |
| - 'ci/**' | |
| tags: | |
| - 'android-v*' | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| build_mode: | |
| description: 'runtime builds the real APK; probe builds the SDL lifecycle smoke APK and does not require generated game sources' | |
| required: true | |
| type: choice | |
| default: runtime | |
| options: | |
| - runtime | |
| - probe | |
| build_type: | |
| description: 'Gradle build type' | |
| required: true | |
| type: choice | |
| default: Release | |
| options: | |
| - Release | |
| - Debug | |
| upload_to_release: | |
| description: 'Attach APK and SHA256 to the GitHub Release for the current tag' | |
| required: true | |
| type: boolean | |
| default: false | |
| release_version: | |
| description: 'Optional: create android-v<version> release tag with changelog, then the tag build attaches the APK' | |
| required: false | |
| type: string | |
| default: '' | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: android-apk-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| release: | |
| name: Create Android release tag | |
| if: github.event_name == 'workflow_dispatch' && inputs.release_version != '' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: false | |
| - name: Create tag and release notes | |
| id: release | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_VERSION: ${{ inputs.release_version }} | |
| run: | | |
| set -euo pipefail | |
| version="${RELEASE_VERSION#android-v}" | |
| version="${version#v}" | |
| if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then | |
| echo "release_version must look like 1.2.3, v1.2.3, or android-v1.2.3" >&2 | |
| exit 2 | |
| fi | |
| tag="android-v${version}" | |
| if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then | |
| echo "Tag already exists locally: ${tag}" >&2 | |
| exit 2 | |
| fi | |
| if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then | |
| echo "Tag already exists on origin: ${tag}" >&2 | |
| exit 2 | |
| fi | |
| previous_tag="$(git tag --merged HEAD --list 'android-v*' --sort=-creatordate | head -n1 || true)" | |
| if [[ -z "$previous_tag" ]]; then | |
| previous_tag="$(git tag --merged HEAD --list 'v*' --sort=-creatordate | head -n1 || true)" | |
| fi | |
| { | |
| echo "## BanjoRecomp Android ${tag}" | |
| echo | |
| if [[ -n "$previous_tag" ]]; then | |
| echo "Changes since ${previous_tag}:" | |
| echo | |
| git log --no-merges --pretty=format:'- %s (%h)' "${previous_tag}..HEAD" | |
| else | |
| echo "Changes in this release:" | |
| echo | |
| git log --no-merges --pretty=format:'- %s (%h)' HEAD | |
| fi | |
| echo | |
| echo | |
| echo "The APK and SHA256 checksum are attached by the tag-triggered Android APK workflow run." | |
| } > release-notes.md | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$tag" -m "BanjoRecomp Android ${tag}" | |
| git push origin "$tag" | |
| gh release create "$tag" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "BanjoRecomp Android ${tag}" \ | |
| --notes-file release-notes.md | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| build: | |
| name: Build Android APK | |
| if: github.event_name != 'workflow_dispatch' || inputs.release_version == '' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 120 | |
| env: | |
| ANDROID_API_LEVEL: '36' | |
| ANDROID_BUILD_TOOLS_VERSION: '36.0.0' | |
| ANDROID_NDK_VERSION: '28.2.13676358' | |
| ANDROID_CMAKE_VERSION: '3.22.1' | |
| ANDROID_ABI: arm64-v8a | |
| ANDROID_PLATFORM: '24' | |
| BANJO_ANDROID_PRIVATE_INPUTS_REPO: ${{ vars.BANJO_ANDROID_PRIVATE_INPUTS_REPO || 'AurelioB/BanjoRecomp-private-inputs' }} | |
| BANJO_ANDROID_KEYSTORE_BASE64: ${{ secrets.BANJO_ANDROID_KEYSTORE_BASE64 }} | |
| BANJO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.BANJO_ANDROID_KEYSTORE_PASSWORD }} | |
| BANJO_ANDROID_KEY_ALIAS: ${{ secrets.BANJO_ANDROID_KEY_ALIAS }} | |
| BANJO_ANDROID_KEY_PASSWORD: ${{ secrets.BANJO_ANDROID_KEY_PASSWORD }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Select build parameters | |
| id: params | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ref_name='${{ github.ref_name }}' | |
| version_name="0.1.0-android-${{ github.run_number }}" | |
| version_code='${{ github.run_number }}' | |
| if [[ '${{ github.ref_type }}' == 'tag' ]]; then | |
| version_name="${ref_name#android-v}" | |
| version_name="${version_name#v}" | |
| if [[ ! "$version_name" =~ ^[0-9]+[.][0-9]+[.][0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "Release tag must look like android-v1.2.3 or v1.2.3; got ${ref_name}" >&2 | |
| exit 2 | |
| fi | |
| base_version="${version_name%%-*}" | |
| IFS=. read -r major minor patch <<<"$base_version" | |
| if (( 10#$minor > 99 || 10#$patch > 99 )); then | |
| echo "Minor and patch versions must be <= 99 because versionCode is major*10000 + minor*100 + patch." >&2 | |
| exit 2 | |
| fi | |
| version_code=$((10#$major * 10000 + 10#$minor * 100 + 10#$patch)) | |
| fi | |
| echo "BANJO_ANDROID_VERSION_NAME=$version_name" >> "$GITHUB_ENV" | |
| echo "BANJO_ANDROID_VERSION_CODE=$version_code" >> "$GITHUB_ENV" | |
| echo "Android versionName: $version_name" | |
| echo "Android versionCode: $version_code" | |
| if [[ '${{ github.event_name }}' == 'pull_request' || ('${{ github.event_name }}' == 'push' && ('${{ github.ref_name }}' == ci/* || '${{ github.ref_name }}' == 'android')) ]]; then | |
| mode='probe' | |
| build_type='Debug' | |
| else | |
| mode='${{ inputs.build_mode || 'runtime' }}' | |
| build_type='${{ inputs.build_type || 'Release' }}' | |
| fi | |
| task="assemble${build_type}" | |
| probe_arg='' | |
| if [[ "$mode" == 'probe' ]]; then | |
| probe_arg='-PbanjoProbe=true' | |
| fi | |
| apk_dir="android/app/build/outputs/apk/${build_type,,}" | |
| name="BanjoRecomp-android-${version_name}-${mode}-${build_type,,}.apk" | |
| if [[ '${{ github.ref_type }}' == 'tag' && "$mode" == 'runtime' && "$build_type" == 'Release' ]]; then | |
| name="BanjoRecompiled-v${version_name}-Android-ARM64.apk" | |
| fi | |
| echo "mode=$mode" >> "$GITHUB_OUTPUT" | |
| echo "build_type=$build_type" >> "$GITHUB_OUTPUT" | |
| echo "task=$task" >> "$GITHUB_OUTPUT" | |
| echo "probe_arg=$probe_arg" >> "$GITHUB_OUTPUT" | |
| echo "apk_dir=$apk_dir" >> "$GITHUB_OUTPUT" | |
| echo "artifact_name=$name" >> "$GITHUB_OUTPUT" | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Set up Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android SDK packages | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| yes | sdkmanager --licenses >/dev/null || true | |
| sdkmanager \ | |
| "platforms;android-${ANDROID_API_LEVEL}" \ | |
| "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ | |
| "ndk;${ANDROID_NDK_VERSION}" \ | |
| "cmake;${ANDROID_CMAKE_VERSION}" | |
| echo "ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" >> "$GITHUB_ENV" | |
| - name: Install runtime source generation tools | |
| if: steps.params.outputs.mode == 'runtime' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential clang cmake lld make ninja-build | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| with: | |
| gradle-version: '9.5.1' | |
| - name: Cache Android native prefixes | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Android/prefixes | |
| key: android-prefixes-${{ runner.os }}-${{ env.ANDROID_ABI }}-sdl2-2.32.10-freetype-2.13.3-ndk-${{ env.ANDROID_NDK_VERSION }} | |
| - name: Build Android native dependencies | |
| shell: bash | |
| run: tools/ci/setup_android_deps.sh | |
| - name: Checkout private runtime inputs | |
| if: steps.params.outputs.mode == 'runtime' | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.BANJO_ANDROID_PRIVATE_INPUTS_REPO }} | |
| ssh-key: ${{ secrets.BANJO_ANDROID_PRIVATE_INPUTS_SSH_KEY }} | |
| path: extra | |
| persist-credentials: false | |
| - name: Prepare generated runtime sources | |
| shell: bash | |
| run: tools/ci/prepare_android_generated_sources.sh '${{ steps.params.outputs.mode }}' | |
| - name: Decode release signing key | |
| if: env.BANJO_ANDROID_KEYSTORE_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUNNER_TEMP/banjo-signing" | |
| printf '%s' "$BANJO_ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/banjo-signing/release.jks" | |
| echo "BANJO_ANDROID_KEYSTORE_FILE=$RUNNER_TEMP/banjo-signing/release.jks" >> "$GITHUB_ENV" | |
| - name: Require signing for runtime release APKs | |
| if: steps.params.outputs.mode == 'runtime' && steps.params.outputs.build_type == 'Release' && env.BANJO_ANDROID_KEYSTORE_BASE64 == '' | |
| shell: bash | |
| run: | | |
| echo 'Runtime release APK distribution requires signing secrets.' >&2 | |
| echo 'Set BANJO_ANDROID_KEYSTORE_BASE64, BANJO_ANDROID_KEYSTORE_PASSWORD, BANJO_ANDROID_KEY_ALIAS, and optionally BANJO_ANDROID_KEY_PASSWORD.' >&2 | |
| exit 2 | |
| - name: Static guards | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git diff --check | |
| python3 tools/check_android_port_guards.py | |
| - name: Build APK | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| gradle -p android --no-daemon :app:${{ steps.params.outputs.task }} ${{ steps.params.outputs.probe_arg }} --stacktrace | |
| - name: Locate APK | |
| id: apk | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t apks < <(find '${{ steps.params.outputs.apk_dir }}' -maxdepth 1 -type f -name '*.apk' | sort) | |
| if [[ ${#apks[@]} -ne 1 ]]; then | |
| echo "Expected exactly one APK in ${{ steps.params.outputs.apk_dir }}, found ${#apks[@]}" >&2 | |
| printf ' %s\n' "${apks[@]}" >&2 | |
| exit 2 | |
| fi | |
| echo "path=${apks[0]}" >> "$GITHUB_OUTPUT" | |
| echo "APK path: ${apks[0]}" | |
| - name: Verify APK contents | |
| shell: bash | |
| run: tools/ci/verify_android_apk.sh '${{ steps.apk.outputs.path }}' '${{ steps.params.outputs.mode }}' | |
| - name: Stage distributable | |
| id: dist | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| cp '${{ steps.apk.outputs.path }}' "dist/${{ steps.params.outputs.artifact_name }}" | |
| sha256sum "dist/${{ steps.params.outputs.artifact_name }}" | tee "dist/${{ steps.params.outputs.artifact_name }}.sha256" | |
| echo "apk=dist/${{ steps.params.outputs.artifact_name }}" >> "$GITHUB_OUTPUT" | |
| echo "sha=dist/${{ steps.params.outputs.artifact_name }}.sha256" >> "$GITHUB_OUTPUT" | |
| - name: Upload workflow artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ steps.params.outputs.artifact_name }} | |
| path: | | |
| ${{ steps.dist.outputs.apk }} | |
| ${{ steps.dist.outputs.sha }} | |
| if-no-files-found: error | |
| - name: Attach APK to GitHub Release | |
| if: github.ref_type == 'tag' && (github.event_name == 'push' || inputs.upload_to_release) | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| ${{ steps.dist.outputs.apk }} | |
| ${{ steps.dist.outputs.sha }} | |
| fail_on_unmatched_files: true |