mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
CI: dual-core (4k + 16k) + PGO nightly, push trigger, plain changelog
- tools/ci-nightly-dualcore.sh builds the core at both host page sizes and merges both .so into one APK so 16k-page devices load their native core - nightly builds with PGO=optimize (committed pgo/armsx2.profdata) - trigger the nightly on push to master (was schedule + dispatch only) - plain-language changelog in the release body from filtered commit subjects
This commit is contained in:
@@ -14,6 +14,11 @@
|
||||
name: Nightly
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master] # build a nightly on every push to master
|
||||
paths-ignore:
|
||||
- '**/*.md' # skip doc-only pushes
|
||||
- 'docs/**'
|
||||
schedule:
|
||||
- cron: '0 8 * * *' # 08:00 UTC daily
|
||||
workflow_dispatch:
|
||||
@@ -35,6 +40,7 @@ jobs:
|
||||
proceed: ${{ steps.check.outputs.proceed }}
|
||||
date: ${{ steps.check.outputs.date }}
|
||||
sha: ${{ steps.check.outputs.sha }}
|
||||
notes: ${{ steps.notes.outputs.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -57,6 +63,58 @@ jobs:
|
||||
echo "proceed=$PROCEED" >> "$GITHUB_OUTPUT"
|
||||
echo "proceed=$PROCEED date=$DATE sha=$SHA"
|
||||
|
||||
# Plain-language changelog for the release body: one bullet per commit
|
||||
# since the previous nightly tag, with noise filtered and dev "[scope]"
|
||||
# tags stripped. No hashes/emojis/dashes. Built here because gate already
|
||||
# has full history (+ tags) checked out; the publish job (which has no
|
||||
# checkout) consumes it via needs.gate.outputs.notes.
|
||||
- id: notes
|
||||
run: |
|
||||
DATE="${{ steps.check.outputs.date }}"
|
||||
REPO="${{ github.repository }}"
|
||||
# most recent nightly-* tag that isn't today's (empty on the first nightly)
|
||||
PREV="$(git tag -l 'nightly-*' --sort=-creatordate | grep -vx "nightly-${DATE}" | head -1 || true)"
|
||||
if [ -n "$PREV" ] && git rev-parse -q --verify "refs/tags/${PREV}" >/dev/null; then
|
||||
RANGE="${PREV}..HEAD"
|
||||
elif git rev-parse -q --verify "HEAD~20" >/dev/null; then
|
||||
RANGE="HEAD~20..HEAD" # first nightly: fall back to the last 20 commits
|
||||
else
|
||||
RANGE="HEAD"
|
||||
fi
|
||||
# Turn commit subjects into plain user-facing bullets:
|
||||
# - drop internal commits (merge/revert/ci/chore/docs/build/style/test/
|
||||
# refactor, translation syncs, version/cmake bumps)
|
||||
# - strip conventional "feat/fix/perf:" prefixes and leading "[scope]" tags
|
||||
# - convert em/en dashes (—/–) to commas so no AI-style dashes survive
|
||||
# || true keeps head's SIGPIPE from tripping pipefail; empty output is fine.
|
||||
git log "$RANGE" --no-merges --pretty=format:'%s' \
|
||||
| grep -ivE '^(merge |revert )' \
|
||||
| grep -ivE '^(ci|chore|docs|build|style|test|refactor)(\(|:)' \
|
||||
| grep -ivE 'update translations|transifex|bump version|^version |^cmake ' \
|
||||
| sed -E 's/^(feat|fix|perf)(\([^)]*\))?!?: *//' \
|
||||
| sed 's/ *— */, /g; s/ *– */, /g' \
|
||||
| sed 's/^\[[^]]*\] *//' \
|
||||
| sed 's/^/- /' \
|
||||
| head -100 > changes.txt || true
|
||||
{
|
||||
echo "Automated nightly build for community testing (unsigned)."
|
||||
echo
|
||||
echo "Platforms: macOS arm64, Linux arm64 (4K and 16K pages), Windows arm64, Android (sideloadable APK). iOS is not included yet."
|
||||
echo
|
||||
echo "## What's new"
|
||||
if [ -s changes.txt ]; then cat changes.txt; else echo "- No notable changes since the last nightly."; fi
|
||||
if [ -n "$PREV" ]; then
|
||||
echo
|
||||
echo "Full changelog: https://github.com/${REPO}/compare/${PREV}...${{ github.sha }}"
|
||||
fi
|
||||
} > notes.md
|
||||
{
|
||||
echo 'body<<ARMSX2_NOTES_EOF'
|
||||
cat notes.md
|
||||
echo 'ARMSX2_NOTES_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "----- generated release notes -----"; cat notes.md
|
||||
|
||||
# ---- PC arm64 Qt GUI (blocking for the release) ------------------------
|
||||
pc-macos-arm64:
|
||||
name: PC · macOS arm64 (Qt)
|
||||
@@ -131,16 +189,38 @@ jobs:
|
||||
- name: Fetch shaderc third-party deps
|
||||
run: python3 app/src/main/cpp/3rdparty/shaderc/utils/git-sync-deps
|
||||
|
||||
# Release build; when armsx2_keystore.properties is absent (it is, in CI) the
|
||||
# release APK is debug-key-signed -> installable/sideloadable, not Play-grade.
|
||||
- name: Build release APK
|
||||
run: ./gradlew :app:assembleRelease --stacktrace
|
||||
# Dual-core (4k + 16k page size) + PGO nightly. A stock `assembleRelease`
|
||||
# ships ONLY the 4k core and cannot load its native core on 16k-page
|
||||
# devices (Android 15+ handhelds). This compiles the core at both page
|
||||
# sizes and merges both .so's into one APK, with PGO=optimize so the
|
||||
# nightly matches release performance. See tools/ci-nightly-dualcore.sh.
|
||||
- name: Build dual-core (4k + 16k) + PGO APK
|
||||
env:
|
||||
FLAVOR: Github # sideload build (com.armsx2); use Play for the SAF-only build
|
||||
# Optional stable signing: add repo secrets NIGHTLY_KEYSTORE_B64 (base64 of a
|
||||
# .jks) + NIGHTLY_KS_PASS / NIGHTLY_KEY_ALIAS / NIGHTLY_KEY_PASS so every
|
||||
# nightly is signed with one key and installs as an update. Without them a
|
||||
# throwaway debug key is generated (users reinstall between nightlies).
|
||||
NIGHTLY_KS_PASS: ${{ secrets.NIGHTLY_KS_PASS }}
|
||||
NIGHTLY_KEY_ALIAS: ${{ secrets.NIGHTLY_KEY_ALIAS }}
|
||||
NIGHTLY_KEY_PASS: ${{ secrets.NIGHTLY_KEY_PASS }}
|
||||
run: |
|
||||
export VC="${{ github.run_number }}"
|
||||
export VN="$(grep 'versionName' app/build.gradle.kts | sed -n 's/.*?:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)"
|
||||
export VN="${VN:-nightly}"
|
||||
if [ -n "${{ secrets.NIGHTLY_KEYSTORE_B64 }}" ]; then
|
||||
echo "${{ secrets.NIGHTLY_KEYSTORE_B64 }}" | base64 -d > "$RUNNER_TEMP/nightly.jks"
|
||||
export NIGHTLY_KEYSTORE="$RUNNER_TEMP/nightly.jks"
|
||||
fi
|
||||
bash tools/ci-nightly-dualcore.sh
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: armsx2-android-arm64
|
||||
path: platforms/android/app/build/outputs/apk/**/*.apk
|
||||
# ONLY the merged dual-core APK — not the intermediate single-core bases —
|
||||
# so the publish job attaches exactly one APK to the release.
|
||||
path: platforms/android/app/build/outputs/apk/nightly/*.apk
|
||||
if-no-files-found: warn
|
||||
|
||||
# ---- publish: gather artifacts -> dated pre-release --------------------
|
||||
@@ -185,11 +265,9 @@ jobs:
|
||||
prerelease: true
|
||||
target_commitish: ${{ github.sha }}
|
||||
fail_on_unmatched_files: false
|
||||
body: |
|
||||
Automated **nightly** build from `${{ github.sha }}` — unsigned, for community testing.
|
||||
|
||||
**Platforms:** macOS arm64 · Linux arm64 (4K + 16K pages) · Windows arm64 · Android (sideloadable APK).
|
||||
_iOS is not yet included — the device build is still being brought up._
|
||||
# Eden-style release notes (static intro + per-commit changelog + Full
|
||||
# Changelog link) assembled by the gate job, which has git history.
|
||||
body: ${{ needs.gate.outputs.notes }}
|
||||
files: out/*
|
||||
|
||||
- name: Discord announce
|
||||
|
||||
@@ -55,3 +55,6 @@ app/src/main/cpp/3rdparty/shaderc/third_party/googletest/
|
||||
app/src/main/cpp/3rdparty/shaderc/third_party/re2/
|
||||
app/src/main/cpp/3rdparty/shaderc/third_party/spirv-headers/
|
||||
app/src/main/cpp/3rdparty/shaderc/third_party/spirv-tools/
|
||||
|
||||
# CI PGO profile for the dual-core nightly (build-release recipe uses this)
|
||||
!pgo/armsx2.profdata
|
||||
|
||||
Binary file not shown.
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# ci-nightly-dualcore.sh — GitHub Actions dual-core (4k + 16k) + PGO nightly.
|
||||
# ----------------------------------------------------------------------------
|
||||
# A stock `./gradlew :app:assembleRelease` produces ONE core at the default
|
||||
# host page size (emucore_4k @ 0x1000). That APK cannot load its native core on
|
||||
# 16k-page devices (Android 15+ handhelds), so half the fleet is broken.
|
||||
#
|
||||
# PCSX2 fastmem bakes the host page size in at compile time, so a universal APK
|
||||
# needs the core compiled TWICE (0x1000 and 0x4000) and both .so's merged into
|
||||
# one APK — exactly what tools/build-release-apk.sh does for hand-built
|
||||
# releases. This is the CI-portable (Linux, no macOS assumptions) version of
|
||||
# that recipe, plus PGO=optimize so the nightly matches release performance.
|
||||
#
|
||||
# Runs from platforms/android (the gradle root); the nightly workflow sets
|
||||
# working-directory accordingly.
|
||||
#
|
||||
# Env:
|
||||
# VC, VN versionCode / versionName (required)
|
||||
# FLAVOR Github (sideload, com.armsx2) | Play (default Github)
|
||||
# PROF merged .profdata (default pgo/armsx2.profdata; if the
|
||||
# file is absent the build falls back to PGO=none rather
|
||||
# than failing, so a missing profile never breaks CI)
|
||||
# OUT output apk path
|
||||
# NIGHTLY_KEYSTORE / NIGHTLY_KS_PASS / NIGHTLY_KEY_ALIAS / NIGHTLY_KEY_PASS
|
||||
# stable signing key (recommended: add as a repo secret
|
||||
# so nightly-over-nightly updates install; otherwise a
|
||||
# throwaway debug key is generated and users must
|
||||
# reinstall between nightlies).
|
||||
# ============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # platforms/android
|
||||
GRADLE="$ROOT_DIR/gradlew"
|
||||
|
||||
VC="${VC:?set VC=<versionCode>}"
|
||||
VN="${VN:?set VN=<versionName>}"
|
||||
FLAVOR="${FLAVOR:-Github}" # Github | Play
|
||||
flavor_lc="$(printf '%s' "$FLAVOR" | tr '[:upper:]' '[:lower:]')"
|
||||
PROF="${PROF:-$ROOT_DIR/pgo/armsx2.profdata}"
|
||||
OUT="${OUT:-$ROOT_DIR/app/build/outputs/apk/nightly/ARMSX2-nightly-${VN}-vc${VC}.apk}"
|
||||
|
||||
# --- resolve Android SDK build-tools (Linux CI exports ANDROID_HOME/SDK_ROOT) ---
|
||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
||||
[[ -n "$SDK" && -d "$SDK" ]] || { echo "FATAL: ANDROID_HOME/ANDROID_SDK_ROOT not set" >&2; exit 1; }
|
||||
BT="$(find "$SDK/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)"
|
||||
ZIPALIGN="$BT/zipalign"; APKSIGNER="$BT/apksigner"; AAPT="$BT/aapt2"
|
||||
for t in "$ZIPALIGN" "$APKSIGNER"; do
|
||||
[[ -x "$t" ]] || { echo "FATAL: missing build-tool $t" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# --- PGO: optimize when the profile exists, else fall back so CI never fails ---
|
||||
if [[ -f "$PROF" ]]; then
|
||||
PGO_ARGS=(-Parmsx2.pgo=optimize -Parmsx2.pgoProfile="$PROF")
|
||||
echo "PGO: optimize using $PROF ($(( $(wc -c < "$PROF") / 1024 / 1024 )) MB)"
|
||||
else
|
||||
PGO_ARGS=(-Parmsx2.pgo=none)
|
||||
echo "WARNING: PGO profile not found at $PROF — building PGO=none (slower core)." >&2
|
||||
fi
|
||||
|
||||
WORK="$ROOT_DIR/app/build/nightly-dualcore"; rm -rf "$WORK"
|
||||
mkdir -p "$WORK/lib-stage/lib/arm64-v8a" "$(dirname "$OUT")"
|
||||
BUILT="$ROOT_DIR/app/build/outputs/apk/${flavor_lc}/release/app-${flavor_lc}-release.apk"
|
||||
|
||||
fsize() { stat -c%s "$1" 2>/dev/null || stat -f%z "$1"; } # Linux || macOS
|
||||
|
||||
build_core() { # pagesize libname
|
||||
local ps="$1" ln="$2"
|
||||
echo "=== core $ln (page=$ps, flavor=$FLAVOR) ==="
|
||||
rm -f "$BUILT"
|
||||
"$GRADLE" -p "$ROOT_DIR" ":app:assemble${FLAVOR}Release" \
|
||||
-Parmsx2.hostPageSize="$ps" \
|
||||
-Parmsx2.nativeLibName="$ln" \
|
||||
"${PGO_ARGS[@]}" \
|
||||
-Parmsx2.versionCode="$VC" \
|
||||
-Parmsx2.versionName="$VN" \
|
||||
--stacktrace
|
||||
[[ -f "$BUILT" ]] || { echo "FATAL: gradle produced no APK for $ln ($BUILT)" >&2; exit 1; }
|
||||
unzip -l "$BUILT" "lib/arm64-v8a/lib${ln}.so" >/dev/null \
|
||||
|| { echo "FATAL: lib${ln}.so missing from $BUILT" >&2; exit 1; }
|
||||
cp -f "$BUILT" "$WORK/base-${ln}.apk"
|
||||
unzip -p "$BUILT" "lib/arm64-v8a/lib${ln}.so" > "$WORK/lib-stage/lib/arm64-v8a/lib${ln}.so"
|
||||
}
|
||||
|
||||
build_core 0x1000 emucore_4k
|
||||
build_core 0x4000 emucore_16k
|
||||
|
||||
for so in emucore_4k emucore_16k; do
|
||||
sz="$(fsize "$WORK/lib-stage/lib/arm64-v8a/lib${so}.so")"
|
||||
echo " lib${so}.so = $(( sz / 1024 / 1024 )) MB"
|
||||
[[ "$sz" -gt 10000000 ]] || { echo "FATAL: lib${so}.so too small ($sz bytes)" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# --- merge: 4k APK as base, drop old signatures + both cores, re-add STORED ---
|
||||
UNS="$WORK/universal-unsigned.apk"; ALN="$WORK/universal-aligned.apk"
|
||||
cp -f "$WORK/base-emucore_4k.apk" "$UNS"
|
||||
zip -qd "$UNS" "META-INF/*" >/dev/null 2>&1 || true
|
||||
zip -qd "$UNS" "lib/arm64-v8a/libemucore_4k.so" "lib/arm64-v8a/libemucore_16k.so" >/dev/null 2>&1 || true
|
||||
( cd "$WORK/lib-stage" && zip -qr -0 "$UNS" lib )
|
||||
"$ZIPALIGN" -f -P 16 4 "$UNS" "$ALN"
|
||||
|
||||
# --- sign: stable key if provided, else a throwaway debug key (warn) ----------
|
||||
KS="${NIGHTLY_KEYSTORE:-$HOME/.android/debug.keystore}"
|
||||
KS_PASS="${NIGHTLY_KS_PASS:-android}"
|
||||
KS_ALIAS="${NIGHTLY_KEY_ALIAS:-androiddebugkey}"
|
||||
KEY_PASS="${NIGHTLY_KEY_PASS:-android}"
|
||||
if [[ ! -f "$KS" ]]; then
|
||||
echo "WARNING: no keystore at $KS — generating a throwaway debug key." >&2
|
||||
echo " Set NIGHTLY_KEYSTORE (repo secret) for stable nightly-over-nightly updates." >&2
|
||||
mkdir -p "$(dirname "$KS")"
|
||||
keytool -genkeypair -keystore "$KS" -storepass "$KS_PASS" -keypass "$KEY_PASS" \
|
||||
-alias "$KS_ALIAS" -keyalg RSA -keysize 2048 -validity 10000 \
|
||||
-dname "CN=Android Debug,O=Android,C=US" >/dev/null 2>&1
|
||||
fi
|
||||
rm -f "$OUT"
|
||||
"$APKSIGNER" sign --ks "$KS" --ks-key-alias "$KS_ALIAS" \
|
||||
--ks-pass "pass:$KS_PASS" --key-pass "pass:$KEY_PASS" \
|
||||
--out "$OUT" "$ALN"
|
||||
|
||||
echo; echo "================= VERIFY ================="
|
||||
echo "-- both cores present --"
|
||||
unzip -l "$OUT" | grep -E "libemucore_(4k|16k)\.so" || { echo "FATAL: cores missing" >&2; exit 1; }
|
||||
echo "-- 16k alignment --"; "$ZIPALIGN" -c -P 16 4 "$OUT" && echo " align OK"
|
||||
echo "-- signature --"; "$APKSIGNER" verify "$OUT" && echo " sig OK"
|
||||
[[ -x "$AAPT" ]] && "$AAPT" dump badging "$OUT" 2>/dev/null | grep -E "package: name|versionCode|versionName" | head -2
|
||||
echo; echo "OUTPUT: $OUT"
|
||||
echo "NIGHTLY-DUALCORE-DONE"
|
||||
Reference in New Issue
Block a user