mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Nightly and MoonStore builds identify themselves to RetroAchievements as stock PCSX2, so the server allows softcore only. Hardcore has worked on locally built IPAs and nowhere else. Nothing was broken. pcsx2/Host.cpp reads the client version from ra_ua_secret.h behind __has_include and falls back to a stock agent when the macro is absent. That header is gitignored, so it has never existed on a runner. CI now writes it from the IOS_RA_UA_VERSION repository secret, the way the Android job already writes its keystores from NIGHTLY_RELEASE_KS_B64. A missing secret warns and continues, because fork pull requests never receive secrets and a nightly that fails to publish is worse than one without hardcore. A malformed version fails the build instead: RetroAchievements refuses a version it cannot order, and a refused agent is indistinguishable from an unknown one on the client. Only the nightly and pushes to master embed it. Pull request artifacts keep the stock agent, so a test build is not one more public copy of an identity that needs a release to revoke. A second step reads the finished binary and fails unless the exact version is in it, trailing space included, since a stale 1.2.3 is a prefix of a current 1.2.345. Both iOS jobs are continue-on-error, so that failure does not stop the workflow, but it does skip the upload that follows it, and the publish step treats a missing IPA as absent rather than fatal. A softcore build is not shipped. None of this makes the version private. The compiler bakes the finished agent into the binary as a plain literal, so anyone holding a build can read it out. What the gitignored header prevents is a fork inheriting a live identity straight from source.
271 lines
11 KiB
YAML
271 lines
11 KiB
YAML
# Unified "build everything on every commit" pipeline for the single-core monorepo.
|
|
#
|
|
# One push to the default branch fans out to every supported target and uploads
|
|
# an individual binary artifact per platform:
|
|
# * PC arm64 Qt GUI -> macOS, Linux, Windows (reuses the reusable workflows)
|
|
# * Android APK -> platforms/android (Gradle + NDK, thin CMake on root core)
|
|
# * iOS .app -> platforms/ios (Xcode/CMake on root core)
|
|
#
|
|
# Windows-on-arm64 builds natively on a Windows 11 arm64 runner (see
|
|
# windows_build_qt.yml): a single arm64 deps stage, then the arm64 GUI.
|
|
#
|
|
# NOTE: The Android and iOS jobs build against the freshly-collapsed single core
|
|
# and have not yet had a green run; they are the mechanism that will surface the
|
|
# remaining reconciliation work. Keep them non-blocking until they first pass.
|
|
#
|
|
# INVARIANT (do not weaken): the PC arm64 jobs (pc-macos-arm64 / pc-linux-arm64 /
|
|
# pc-windows-arm64) are BLOCKING and must stay so. They are the guardrail that a
|
|
# mobile-motivated change has not silently altered the shared canonical core.
|
|
# Anything mobile-specific belongs behind a platform guard (#if __ANDROID__ /
|
|
# ARMSX2_IOS) or in a forked, CMake-selected translation unit (see the aR5900*
|
|
# EE fork in pcsx2/CMakeLists.txt) -- never grafted unguarded into shared files.
|
|
name: Build All Platforms
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
# Doc-only pushes shouldn't burn ~2h of arm64 CI.
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE*'
|
|
- '.gitignore'
|
|
pull_request:
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- 'LICENSE*'
|
|
- '.gitignore'
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: build-all-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# ---- PC arm64 Qt GUI (mac / linux) -------------------------------------
|
|
pc-macos-arm64:
|
|
name: PC · macOS arm64 (Qt)
|
|
uses: ./.github/workflows/macos_build.yml
|
|
with:
|
|
jobName: "macOS arm64"
|
|
artifactPrefixName: "armsx2-macos-arm64"
|
|
sign_and_notarize: true
|
|
secrets: inherit
|
|
|
|
pc-linux-arm64:
|
|
name: PC · Linux arm64 (Qt)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
# The GitHub aarch64 runners use 4K pages, so an unqualified build only
|
|
# runs on 4K-page kernels. 16K-page kernels (e.g. Asahi Linux on Apple
|
|
# Silicon) need a separate binary, so build both variants explicitly
|
|
# instead of quietly inheriting the runner's page size.
|
|
- pageVariant: "4k"
|
|
pageSize: "4096"
|
|
cacheLineSize: "64"
|
|
- pageVariant: "16k"
|
|
pageSize: "16384"
|
|
cacheLineSize: "128"
|
|
uses: ./.github/workflows/linux_build_qt.yml
|
|
with:
|
|
jobName: "Linux arm64 (${{ matrix.pageVariant }} pages)"
|
|
artifactPrefixName: "armsx2-linux-arm64-${{ matrix.pageVariant }}-appimage"
|
|
compiler: clang
|
|
cmakeflags: "-DHOST_PAGE_SIZE=${{ matrix.pageSize }} -DHOST_CACHE_LINE_SIZE=${{ matrix.cacheLineSize }}"
|
|
buildAppImage: true
|
|
|
|
# ---- libretro core (linux arm64) ---------------------------------------
|
|
pc-linux-arm64-libretro:
|
|
name: PC · Linux arm64 (libretro core)
|
|
uses: ./.github/workflows/linux_build_libretro.yml
|
|
with:
|
|
jobName: "libretro Core Build (arm64)"
|
|
artifactPrefixName: "armsx2-linux-arm64-libretro"
|
|
fetchTags: true
|
|
secrets: inherit
|
|
|
|
# ---- SDL/kmsdrm handheld frontend (linux arm64) ------------------------
|
|
pc-linux-arm64-sdl:
|
|
name: PC · Linux arm64 (SDL kmsdrm)
|
|
uses: ./.github/workflows/linux_build_sdl.yml
|
|
with:
|
|
jobName: "SDL Handheld Build (arm64)"
|
|
artifactPrefixName: "armsx2-linux-arm64-sdl"
|
|
fetchTags: true
|
|
secrets: inherit
|
|
|
|
# ---- PC arm64 Qt GUI (windows) -----------------------------------------
|
|
pc-windows-arm64:
|
|
name: PC · Windows arm64 (Qt)
|
|
uses: ./.github/workflows/windows_build_qt.yml
|
|
with:
|
|
jobName: "Windows arm64"
|
|
artifactPrefixName: "armsx2-windows-arm64"
|
|
|
|
# ---- Android APK -------------------------------------------------------
|
|
android:
|
|
name: Android APK
|
|
runs-on: ubuntu-24.04
|
|
# Non-blocking until the collapsed-core Android build first goes green.
|
|
continue-on-error: true
|
|
defaults:
|
|
run:
|
|
working-directory: platforms/android
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Set up JDK 17
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: "17"
|
|
|
|
- name: Set up Android SDK
|
|
uses: android-actions/setup-android@v3
|
|
|
|
# NDK matches the version AGP resolves to (build.gradle.kts pins none, so it
|
|
# uses AGP's default 28.2.13676358); cmake 3.31.6 matches the pin in
|
|
# build.gradle.kts. 3.22.1 (AGP's default) is the shaderc deps' exact
|
|
# cmake_minimum_required floor and fails to configure spirv-tools.
|
|
- name: Install NDK + CMake
|
|
run: yes | sdkmanager "ndk;28.2.13676358" "cmake;3.31.6" >/dev/null || true
|
|
|
|
# shaderc's SPIRV-Tools/glslang/etc. are fetched on demand (not vendored,
|
|
# not submodules), so `submodules: recursive` above does not provide them.
|
|
- name: Fetch shaderc third-party deps
|
|
run: python3 app/src/main/cpp/3rdparty/shaderc/utils/git-sync-deps
|
|
|
|
# librashader is built by cargo for the Android target; the runner's
|
|
# rustup only ships the host std, so the cargo step dies with
|
|
# "error[E0463]: can't find crate for `core`" without this.
|
|
- name: Install Rust Android target
|
|
run: rustup target add aarch64-linux-android
|
|
|
|
- name: Build release APK
|
|
run: ./gradlew :app:assembleRelease --stacktrace
|
|
|
|
- name: Upload APK
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: armsx2-android-arm64
|
|
path: platforms/android/app/build/outputs/apk/**/*.apk
|
|
if-no-files-found: warn
|
|
|
|
# ---- iOS .app ----------------------------------------------------------
|
|
ios:
|
|
name: iOS (arm64, unsigned device IPA)
|
|
runs-on: macos-26
|
|
# Non-blocking until the collapsed-core iOS build first goes green.
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Select Xcode
|
|
run: sudo xcode-select -switch /Applications/Xcode.app
|
|
|
|
# RetroAchievements grants hardcore on the client name and version in the HTTP user
|
|
# agent, and pcsx2/Host.cpp reads that version from a gitignored header. Only builds
|
|
# that reach players get it: a pull request artifact would be one more public copy of
|
|
# an identity that cannot be revoked without a release. Those keep the stock PCSX2
|
|
# agent and stay softcore, which is what a test build should be.
|
|
- name: Embed the RetroAchievements client identity
|
|
if: >-
|
|
github.repository == 'ARMSX2/ARMSX2' &&
|
|
github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
env:
|
|
IOS_RA_UA_VERSION: ${{ secrets.IOS_RA_UA_VERSION }}
|
|
run: bash .github/workflows/scripts/common/write-ra-ua-secret.sh
|
|
|
|
- name: Configure (CMake, Xcode generator, real device SDK, no signing)
|
|
working-directory: platforms/ios/app/src/main/cpp
|
|
run: >
|
|
cmake -G Xcode -B build
|
|
-DCMAKE_SYSTEM_NAME=iOS
|
|
-DCMAKE_OSX_ARCHITECTURES=arm64
|
|
-DARMSX2_REAL_DEVICE=ON
|
|
|
|
- name: Build (iphoneos SDK, unsigned)
|
|
working-directory: platforms/ios/app/src/main/cpp
|
|
run: >
|
|
cmake --build build --config Release
|
|
-- -sdk iphoneos CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO
|
|
|
|
- name: Package unsigned .ipa
|
|
working-directory: platforms/ios/app/src/main/cpp
|
|
run: |
|
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
|
|
IPA_NAME="ARMSX2-iOS-${SHORT_SHA}.ipa"
|
|
|
|
APP_PATH=$(find build -name "ARMSX2iOS.app" -type d | head -1)
|
|
if [ -z "$APP_PATH" ]; then
|
|
echo "ERROR: ARMSX2iOS.app not found in build output"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found app at: $APP_PATH"
|
|
echo "Packaging as: $IPA_NAME"
|
|
|
|
STAGING=$(mktemp -d)
|
|
mkdir -p "$STAGING/Payload"
|
|
cp -R "$APP_PATH" "$STAGING/Payload/"
|
|
|
|
cd "$STAGING"
|
|
zip -r "$GITHUB_WORKSPACE/$IPA_NAME" Payload -x "*.DS_Store"
|
|
cd "$GITHUB_WORKSPACE"
|
|
|
|
echo "IPA_NAME=$IPA_NAME" >> $GITHUB_ENV
|
|
ls -lh "$IPA_NAME"
|
|
|
|
# Nothing on the client reports a rejected agent, so a master build that lost the
|
|
# identity looks healthy until a player loses an unlock. Fail here instead. Carries
|
|
# the same guard as the write step, or every pull request would fail this check.
|
|
- name: Verify the client identity reached the binary
|
|
if: >-
|
|
github.repository == 'ARMSX2/ARMSX2' &&
|
|
github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
working-directory: platforms/ios/app/src/main/cpp
|
|
env:
|
|
IOS_RA_UA_VERSION: ${{ secrets.IOS_RA_UA_VERSION }}
|
|
run: |
|
|
APP_PATH=$(find build -name "ARMSX2iOS.app" -type d | head -1)
|
|
bash "$GITHUB_WORKSPACE/.github/workflows/scripts/common/verify-ra-ua-agent.sh" \
|
|
"$APP_PATH/ARMSX2iOS"
|
|
|
|
- name: Upload .ipa
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ env.IPA_NAME }}
|
|
path: ${{ github.workspace }}/${{ env.IPA_NAME }}
|
|
if-no-files-found: error
|
|
|
|
# ---- Alert on a broken master build ------------------------------------
|
|
# Fires only when a BLOCKING guardrail job (the PC arm64 builds) fails on a
|
|
# push to master -- never on PRs, and never on the non-blocking mobile jobs
|
|
# (they are continue-on-error, so they can't trip `failure()`). GitHub also
|
|
# emails the pusher natively; this adds a Discord ping to the shared channel.
|
|
notify-failure:
|
|
name: Notify on master build failure
|
|
needs: [pc-macos-arm64, pc-linux-arm64, pc-windows-arm64]
|
|
if: ${{ failure() && github.event_name == 'push' }}
|
|
runs-on: ubuntu-latest
|
|
# Job-level so the step's `if: env.HOOK != ''` guard can see it.
|
|
env:
|
|
HOOK: ${{ secrets.DISCORD_BUILD_WEBHOOK }}
|
|
steps:
|
|
- name: Discord failure ping
|
|
if: ${{ env.HOOK != '' }} # no-op on forks that lack the secret
|
|
env:
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
run: |
|
|
jq -n \
|
|
--arg url "$RUN_URL" \
|
|
--arg desc "Commit \`${{ github.sha }}\` by ${{ github.actor }} failed a required build." \
|
|
'{username:"ARMSX2 CI", embeds:[{title:"❌ master build broke", url:$url, color:15158332, description:$desc}]}' \
|
|
| curl -fsSL -H 'Content-Type: application/json' -d @- "$HOOK"
|