diff --git a/.gitea/workflows/apple.yml b/.gitea/workflows/apple.yml index 058b189..aea7818 100644 --- a/.gitea/workflows/apple.yml +++ b/.gitea/workflows/apple.yml @@ -45,17 +45,22 @@ jobs: # App Store screenshots of the real UI, zipped and attached to the run as a build artifact. # Skipped on PRs (cost); runs on main pushes + manual dispatch. Needs the build/test job green - # first — and being a separate job, a capture hiccup (a missing Simulator runtime, or the mac - # runner lacking Screen Recording permission for the window capture) can never red that signal. + # first, and is a separate job so a capture hiccup can never red the core signal. + # + # Scope = the two REQUIRED iOS sizes (iPhone 6.9" + iPad 13"), captured on the Simulator + # (`simctl io screenshot`, no Screen Recording grant needed). macOS and tvOS are deliberately + # NOT in CI: the self-hosted runner is headless (no window-server session), so the mac window + # capture can't run there; tvOS needs the Tier-3 build-std slice. Generate those two locally on + # a GUI Mac with `clients/apple/tools/screenshots.sh macos tvos`. screenshots: needs: swift if: gitea.event_name != 'pull_request' runs-on: macos-arm64 - timeout-minutes: 90 + timeout-minutes: 75 steps: - uses: actions/checkout@v4 - - name: Rust toolchain + Apple targets (incl. iOS/tvOS Simulator slices) + - name: Rust toolchain + iOS Simulator targets run: | if ! command -v rustup >/dev/null && [ ! -x "$HOME/.cargo/bin/rustup" ]; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ @@ -65,29 +70,18 @@ jobs: dirname "$RUSTUP" >> "$GITHUB_PATH" "$RUSTUP" target add aarch64-apple-darwin x86_64-apple-darwin \ aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios - # tvOS slices are Tier-3 (build-std on nightly + rust-src) — best-effort. - "$RUSTUP" toolchain install nightly --profile minimal --component rust-src || true - - name: Build PunktfunkCore.xcframework (mac + iOS; tvOS best-effort) - run: | - # Prefer an all-platform framework; fall back to mac + iOS so a tvOS toolchain gap - # never blocks the iPhone/iPad screenshots. - if ! BUILD_IOS=1 BUILD_TVOS=1 bash scripts/build-xcframework.sh; then - echo "::warning::tvOS xcframework slice failed — rebuilding mac + iOS only" - BUILD_IOS=1 bash scripts/build-xcframework.sh - fi + - name: Build PunktfunkCore.xcframework (mac + iOS slices) + run: BUILD_IOS=1 bash scripts/build-xcframework.sh - - name: Capture screenshots (best-effort per platform) + - name: Capture screenshots (iPhone 6.9" + iPad 13"; auto-creates the Simulators) working-directory: clients/apple env: SETTLE: "8" # Simulators settle slower than a local run run: | - # Each platform is an independent invocation: a failure (no Simulator runtime, no - # Screen Recording grant for the mac window capture) skips that platform, not the rest. - bash tools/screenshots.sh macos || echo "::warning::macOS screenshots skipped" - bash tools/screenshots.sh ios || echo "::warning::iOS screenshots skipped" - bash tools/screenshots.sh ipad || echo "::warning::iPad screenshots skipped" - bash tools/screenshots.sh tvos || echo "::warning::tvOS screenshots skipped" + # Independent invocations: one platform failing skips it, not the other. + bash tools/screenshots.sh ios || echo "::warning::iOS (iPhone 6.9\") screenshots skipped" + bash tools/screenshots.sh ipad || echo "::warning::iPad 13\" screenshots skipped" echo "Produced:"; ls -la screenshots || true - name: Upload screenshots (zip artifact) diff --git a/clients/apple/README.md b/clients/apple/README.md index cb6170f..12ffccf 100644 --- a/clients/apple/README.md +++ b/clients/apple/README.md @@ -214,12 +214,17 @@ Requirements / gotchas: - The hero defaults to a synthetic synthwave frame — set `PUNKTFUNK_SHOT_HERO` to a real captured frame for a production-quality lead screenshot. -**CI**: the `apple` workflow's **`screenshots`** job runs this on the `macos-arm64` runner on every -main push + manual dispatch (skipped on PRs), and attaches the result as a single zip artifact, -**`punktfunk-appstore-screenshots`** (download it from the run's Artifacts). It's best-effort and -isolated from the build/test job — a missing Simulator runtime or a runner without the Screen -Recording grant only drops that platform, never reds the build. (The macOS window capture in -particular needs that grant on the runner; the Simulator shots don't.) +**CI**: the `apple` workflow's **`screenshots`** job runs on the `macos-arm64` runner on every main +push + manual dispatch (skipped on PRs), and attaches the result as a single zip artifact, +**`punktfunk-appstore-screenshots`** (download it from the run's Artifacts; `upload-artifact@v3` — +Gitea's backend rejects v4). It captures the two **required iOS sizes — iPhone 6.9" + iPad 13"** — +on the Simulator (auto-creating the device if the runner lacks it), and is isolated from the +build/test job so a capture hiccup never reds the build. + +**macOS and tvOS are NOT in CI**, by design: the self-hosted runner is **headless** (no +window-server session), so the macOS window capture can't run there, and tvOS needs the Tier-3 +build-std slice. Generate those on a GUI Mac: `tools/screenshots.sh macos tvos`. (If the runner is +ever switched to a logged-in GUI session, re-adding macOS to the job's capture step is one line.) ## Notes for whoever picks this up next diff --git a/clients/apple/tools/screenshots.sh b/clients/apple/tools/screenshots.sh index 6ec6b4b..8119d56 100755 --- a/clients/apple/tools/screenshots.sh +++ b/clients/apple/tools/screenshots.sh @@ -87,15 +87,30 @@ shoot_macos() { # ------------------------------------------------------------------ iOS / iPadOS / tvOS -# $1 device-name regex $2 scheme $3 sdk $4 file prefix $5 runtime-grep +# $1 device-type regex (matches both existing device names and the device-type catalog) +# $2 scheme $3 sdk $4 file prefix $5 runtime platform (iOS|tvOS — for the create fallback) shoot_sim() { require_xcode - local match="$1" scheme="$2" sdk="$3" prefix="$4" runtime="$5" + local match="$1" scheme="$2" sdk="$3" prefix="$4" platform="$5" + # Reuse an existing device of this type; else create a throwaway one against the newest + # available runtime for the platform. CI runners commonly ship a runtime but not every device + # (the iPhone 16 Pro Max is absent on ours), so create-on-demand is what makes it reproducible. local udid udid="$(xcrun simctl list devices available | grep -E "$match" | grep -oE '[0-9A-F-]{36}' | head -1 || true)" - [ -n "$udid" ] || die "$prefix: no available Simulator matching /$match/. - Create one in Xcode → Settings → Components, or: xcrun simctl create …" + if [ -z "$udid" ]; then + local devtype rt + devtype="$(xcrun simctl list devicetypes | grep -E "$match" \ + | grep -oE 'com\.apple\.CoreSimulator\.SimDeviceType\.[A-Za-z0-9.-]+' | head -1 || true)" + rt="$(xcrun simctl list runtimes available | grep -E "^$platform " \ + | grep -oE 'com\.apple\.CoreSimulator\.SimRuntime\.[A-Za-z0-9.-]+' | tail -1 || true)" + if [ -n "$devtype" ] && [ -n "$rt" ]; then + udid="$(xcrun simctl create "pf-shot-$prefix" "$devtype" "$rt" 2>/dev/null || true)" + [ -n "$udid" ] && log "$prefix — created Simulator $udid ($devtype)" + fi + fi + [ -n "$udid" ] || die "$prefix: no Simulator matching /$match/, and none could be created + (needs a $platform runtime + a matching device type — check 'xcrun simctl list')." log "$prefix — Simulator $udid" xcrun simctl boot "$udid" 2>/dev/null || true xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1 || true