Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,93 @@ jobs:
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO

# ---------------------------------------------------------------------------------------
# Actually RUN it (`v1.30.0`).
#
# Everything above proves the Swift/Rust bridge COMPILES AND LINKS. It has never proved the
# app starts: `docs/mobile-readiness.md` records that no iOS build has ever executed,
# because this environment has no macOS toolchain and this runner is the only place one
# exists. A binary that links and then traps on launch is indistinguishable from a working
# one in a build log.
#
# This does NOT boot a ROM. The app bundles no cartridge and adding one is a licensing
# question, not a CI one. What it proves is narrower, and worth stating exactly: the app
# installs, launches, and is still alive several seconds later.
# ---------------------------------------------------------------------------------------
- name: Pick an available iPhone simulator
id: sim
run: |
set -euo pipefail
xcrun simctl list devices available
udid=$(xcrun simctl list devices available \
| grep -E '^[[:space:]]+iPhone' \
| head -1 \
| grep -oE '[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}')
if [ -z "$udid" ]; then
echo "::error::no available iPhone simulator on this runner"
exit 1
fi
echo "udid=$udid" >> "$GITHUB_OUTPUT"
echo "using simulator $udid"

- name: Build for that simulator and install
working-directory: ios
env:
UDID: ${{ steps.sim.outputs.udid }}
run: |
set -euo pipefail
xcrun simctl boot "$UDID" || true
xcrun simctl bootstatus "$UDID" -b
xcodebuild build \
-project RustySNES.xcodeproj \
-scheme RustySNES \
-destination "platform=iOS Simulator,id=$UDID" \
-derivedDataPath build \
CODE_SIGNING_ALLOWED=NO
app=$(find build/Build/Products -maxdepth 2 -name '*.app' | head -1)
if [ -z "$app" ]; then
echo "::error::no .app bundle was produced"
exit 1
fi
echo "APP=$PWD/$app" >> "$GITHUB_ENV"
xcrun simctl install "$UDID" "$app"

- name: Launch it, and require it to still be alive
env:
UDID: ${{ steps.sim.outputs.udid }}
run: |
set -euo pipefail
bundle_id=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Info.plist")
echo "bundle id: $bundle_id"
xcrun simctl launch "$UDID" "$bundle_id"
sleep 8
# Ask the SIMULATOR whether the process is still there. A crash on launch is exactly the
# case that links cleanly and then dies, and `simctl launch` has already returned 0 by
# then, so its exit code cannot catch it.
#
# The listing is captured to a variable FIRST rather than piped straight into `grep -q`.
# `grep -q` exits at its first match and closes the pipe, `launchctl` then takes SIGPIPE,
# and under `set -o pipefail` that becomes the pipeline's status -- so the check reported
# "it started and died" for an app that was running fine. It did exactly that on its
# first run, with the process visibly alive in the log above the failure.
listing=$(xcrun simctl spawn "$UDID" launchctl list) || {
echo "::error::could not read the simulator's process list"
exit 1
}
if printf '%s\n' "$listing" | grep -q "$bundle_id"; then
echo "ok: still running 8s after launch"
else
echo "::error::the app is not running 8s after launch -- it started and died"
printf '%s\n' "$listing" | head -40
exit 1
fi

- name: Shut the simulator down
if: always()
env:
UDID: ${{ steps.sim.outputs.udid }}
run: xcrun simctl shutdown "${UDID:-booted}" || true

# TestFlight upload is gated on distribution-signing secrets being configured at all --
# skipped (not failed) when they aren't, since this repo has never had them set up. Secret
# names are placeholders for the project owner to actually provision (App Store Connect API
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
addressing before any assertion branches. Generalised in the plan — rows that hand-load `S` must
restore a known-good stack before anything that returns — but it is not the oracle's fix.

- **iOS CI now launches the app, not just links it (`v1.30.0`).** `ios.yml` boots a simulator,
installs the built `.app`, launches it, and requires the process to **still be alive eight seconds
later** — asked of the simulator via `launchctl list`, because `simctl launch` has already
returned 0 by the time a launch-crash happens, so its exit code cannot catch one.

Every iOS verification before this was compile/link-only, and a binary that links and then traps
on launch is indistinguishable from a working one in a build log. What this does **not** prove is
emulation: the app bundles no cartridge, so no ROM has run. `docs/mobile-readiness.md` now states
that distinction instead of listing the whole runtime question as open.

The liveness check needed a fix of its own before it was trustworthy. Piping `launchctl list`
straight into `grep -q` made it report "it started and died" for an app that was running: `grep -q`
exits at its first match and closes the pipe, `launchctl` takes `SIGPIPE`, and `set -o pipefail`
turns that into the pipeline's status. The first run failed that way with the process visibly
alive one line above the error. The listing is now captured before it is searched, and a genuine
failure prints it.

- **AccuracySNES: the Mesen2 oracle diagnosed, and a stale frame budget aligned.** Three `v1.28.0`
items ended at "no oracle can arbitrate", so the headless runner was investigated rather than
accepted as environmental. Established: the hang is genuinely pre-existing (the `v1.25.0`-era image
Expand Down
12 changes: 9 additions & 3 deletions docs/mobile-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,15 @@ here so a future go/no-go review has a concrete checklist, not a vague "more pol
check, a signed release (not debug) build, a dormant-vs-live Play Billing decision for
`rustysnes-monetization` if monetization is ever actually activated
- **iOS:**
- A real on-device or simulator *run* — every verification so far is compile/link-only (this
development environment has no macOS/Xcode toolchain at all); `ios.yml`'s passing
`xcodebuild` build proves the code compiles, not that it behaves correctly at runtime
- A real on-device *run* with a ROM loaded. `v1.30.0` narrowed this: `ios.yml` now boots a
simulator, installs the app, launches it, and **requires it to still be running eight seconds
later** — a binary that links and then traps on launch is indistinguishable from a working one
in a build log, and that gap is now closed. What is still *not* proven is emulation: the app
bundles no cartridge (a licensing question, not a CI one), so nothing has run a ROM. State the
difference precisely rather than upgrading "it launches" to "it works".

This development environment still has no macOS/Xcode toolchain, so the runner remains the only
place any of it executes.
- TestFlight distribution signing secrets provisioned (the `ios.yml` upload step is currently
an explicit no-op pending these)
- ~~The App Store §4.7 self-audit~~ — **done, `v1.30.0`; see §4.7 audit below.** Two of the three
Expand Down
Loading