chore: prepare Nova 1.2.0 release #426
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: Build Nova APK | |
| on: | |
| push: | |
| branches: [ master ] | |
| tags: [ 'v*' ] | |
| pull_request: | |
| branches: [ master ] | |
| workflow_dispatch: | |
| inputs: | |
| run_emulator_smoke: | |
| description: 'Run the slow emulator instrumentation smoke test' | |
| type: boolean | |
| default: false | |
| jobs: | |
| verify: | |
| name: Lint and unit test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| cache: gradle | |
| - name: Set up Android SDK | |
| uses: android-actions/setup-android@v4 | |
| - name: Install NDK | |
| run: sdkmanager "ndk;27.0.12077973" | |
| - name: Lint debug build | |
| run: ./gradlew -PnovaAbis=x86_64 -PlintFailOnError=true lintNonRoot_gameDebug | |
| - name: Run JVM tests | |
| run: ./gradlew -PnovaAbis=x86_64 testNonRoot_gameDebugUnitTest | |
| - name: Run helper and onboarding tests | |
| run: python3 -m unittest tools.test_nova_retroid_smoke tools.test_native_submodule_preflight | |
| android-smoke: | |
| name: Emulator smoke test | |
| needs: verify | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.run_emulator_smoke }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| cache: gradle | |
| - name: Set up Android SDK | |
| uses: android-actions/setup-android@v4 | |
| - name: Enable KVM | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| - name: Install NDK | |
| run: sdkmanager "ndk;27.0.12077973" | |
| - name: Run instrumentation smoke test | |
| timeout-minutes: 30 | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| with: | |
| api-level: 29 | |
| arch: x86_64 | |
| profile: Nexus 6 | |
| target: default | |
| disk-size: 1024M | |
| emulator-boot-timeout: 900 | |
| emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-snapshot -no-metrics | |
| script: ./gradlew -PnovaAbis=x86_64 connectedNonRoot_gameDebugAndroidTest --stacktrace | |
| build: | |
| name: Assemble release APK | |
| needs: [verify, android-smoke] | |
| if: ${{ always() && needs.verify.result == 'success' && (needs.android-smoke.result == 'success' || needs.android-smoke.result == 'skipped') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| env: | |
| HAS_KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 != '' }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| submodules: recursive | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| cache: gradle | |
| - name: Set up Android SDK | |
| uses: android-actions/setup-android@v4 | |
| - name: Install NDK | |
| run: sdkmanager "ndk;27.0.12077973" | |
| - name: Build release APK | |
| run: ./gradlew assembleNonRoot_gameRelease | |
| - name: Sign APKs | |
| if: github.event_name != 'pull_request' && env.HAS_KEYSTORE_BASE64 == 'true' | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/keystore.jks | |
| APK_DIR="app/build/outputs/apk/nonRoot_game/release" | |
| unsigned_apks=("${APK_DIR}"/*release-unsigned.apk) | |
| if [ "${#unsigned_apks[@]}" -eq 0 ]; then | |
| echo "No unsigned release APKs found in ${APK_DIR}" >&2 | |
| exit 1 | |
| fi | |
| BUILD_TOOLS="${ANDROID_HOME}/build-tools/$(ls "${ANDROID_HOME}/build-tools/" | sort -V | tail -1)" | |
| for APK in "${unsigned_apks[@]}"; do | |
| SIGNED_APK="${APK/-unsigned/}" | |
| "${BUILD_TOOLS}/zipalign" -v 4 "$APK" "${APK}.aligned" | |
| "${BUILD_TOOLS}/apksigner" sign \ | |
| --ks /tmp/keystore.jks \ | |
| --ks-key-alias ${{ secrets.KEY_ALIAS }} \ | |
| --ks-pass pass:${{ secrets.KEYSTORE_PASSWORD }} \ | |
| --key-pass pass:${{ secrets.KEY_PASSWORD }} \ | |
| --out "$SIGNED_APK" \ | |
| "${APK}.aligned" | |
| done | |
| - name: Select APK artifacts | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| APK_DIR="app/build/outputs/apk/nonRoot_game/release" | |
| APK_ASSET_DIR="${PWD}/release-assets" | |
| mkdir -p "${APK_ASSET_DIR}" | |
| signed_apks=("${APK_DIR}"/*-release.apk) | |
| if [ "${#signed_apks[@]}" -gt 0 ]; then | |
| apk_files=("${signed_apks[@]}") | |
| else | |
| apk_files=("${APK_DIR}"/*release-unsigned.apk) | |
| fi | |
| if [ "${#apk_files[@]}" -eq 0 ]; then | |
| echo "No release APKs found in ${APK_DIR}" >&2 | |
| exit 1 | |
| fi | |
| if [[ "${GITHUB_REF}" == refs/tags/v* && "${#signed_apks[@]}" -eq 0 ]]; then | |
| echo "Refusing to publish unsigned APKs for ${GITHUB_REF_NAME}" >&2 | |
| exit 1 | |
| fi | |
| for apk in "${apk_files[@]}"; do | |
| apk_name="$(basename "$apk")" | |
| case "$apk_name" in | |
| *arm64-v8a*) abi="arm64-v8a" ;; | |
| *armeabi-v7a*) abi="armeabi-v7a" ;; | |
| *x86_64*) abi="x86_64" ;; | |
| *) | |
| echo "Could not determine ABI for ${apk_name}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| if [[ "$apk_name" == *-unsigned.apk ]]; then | |
| asset_name="Nova-Android-${abi}-unsigned.apk" | |
| else | |
| asset_name="Nova-Android-${abi}.apk" | |
| fi | |
| cp "$apk" "${APK_ASSET_DIR}/${asset_name}" | |
| done | |
| echo "APK_ASSET_DIR=${APK_ASSET_DIR}" >> "$GITHUB_ENV" | |
| - name: Generate checksums | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| for apk in "${APK_ASSET_DIR}"/*.apk; do | |
| sha256sum "$apk" > "$apk.sha256" | |
| done | |
| - name: Upload APK artifact | |
| uses: actions/upload-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: nova-release-apk | |
| path: ${{ env.APK_ASSET_DIR }}/* | |
| if-no-files-found: error | |
| retention-days: 3 | |
| - name: Upload GitHub Release assets | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| release_assets=("${APK_ASSET_DIR}"/*.apk "${APK_ASSET_DIR}"/*.apk.sha256) | |
| for file in "${release_assets[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "Missing release asset: $file" >&2 | |
| exit 1 | |
| fi | |
| done | |
| if ! gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then | |
| gh release create "${GITHUB_REF_NAME}" \ | |
| --verify-tag \ | |
| --title "${GITHUB_REF_NAME}" \ | |
| --generate-notes | |
| fi | |
| gh release upload "${GITHUB_REF_NAME}" "${release_assets[@]}" --clobber | |
| - name: Verify GitHub Release assets | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| asset_count=$(gh release view "${GITHUB_REF_NAME}" --json assets --jq '.assets | length') | |
| if [ "${asset_count}" -lt 6 ]; then | |
| echo "Expected at least 6 release assets on ${GITHUB_REF_NAME}, found ${asset_count}" >&2 | |
| exit 1 | |
| fi | |
| gh release view "${GITHUB_REF_NAME}" --json assets --jq '.assets[].name' | grep -Fx 'Nova-Android-arm64-v8a.apk' | |
| gh release view "${GITHUB_REF_NAME}" --json assets --jq '.assets[].name' | grep -Fx 'Nova-Android-armeabi-v7a.apk' | |
| gh release view "${GITHUB_REF_NAME}" --json assets --jq '.assets[].name' | grep -Fx 'Nova-Android-x86_64.apk' |