From 9b5169eafa9cf839dd8eb0373859bd53ba5dc51c Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:27:14 +0200 Subject: [PATCH 1/5] ci: bump E2E Xcode to 26.6 --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 7b0076bf..4c29d7f4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -146,10 +146,10 @@ jobs: node-version: '24.10.0' cache: 'pnpm' - - name: Setup Xcode 26.4.1 + - name: Setup Xcode 26.6 uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.4.1' + xcode-version: '26.6' - name: Install Watchman run: brew install watchman From 01dd8808e8ede25c8f8288901b46d43f881ada28 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:43:12 +0200 Subject: [PATCH 2/5] ci: measure iOS simulator startup timings --- .../ios-simulator-startup-timings.yml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/ios-simulator-startup-timings.yml diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml new file mode 100644 index 00000000..81f3d8df --- /dev/null +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -0,0 +1,53 @@ +name: iOS Simulator Startup Timings + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + measure-ios-simulator-startup: + name: Measure iOS simulator startup + runs-on: macos-latest + timeout-minutes: 20 + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Measure simulator startup timings + shell: bash + run: | + set -euxo pipefail + + sudo xcode-select \ + --switch /Applications/Xcode_26.6.app/Contents/Developer + + echo "=== First launch ===" + time sudo xcodebuild -runFirstLaunch + + echo "=== Cache preparation ===" + time xcrun simctl runtime dyld_shared_cache update --all + + RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-26-5" + DEVICE_NAME="iPhone 17 Pro" + + UDID="$( + xcrun simctl list devices available -j | + jq -er --arg runtime "$RUNTIME" --arg name "$DEVICE_NAME" ' + .devices[$runtime] + | map(select(.name == $name and .isAvailable == true)) + | .[0].udid + ' + )" + + echo "=== Selected simulator ===" + echo "$DEVICE_NAME ($RUNTIME): $UDID" + + echo "=== Boot request ===" + time xcrun simctl boot "$UDID" || true + + echo "=== Boot completion ===" + time xcrun simctl bootstatus "$UDID" -b From 24e8b2e30b22285121ad42658c88ac220d8d0ea0 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:57:35 +0200 Subject: [PATCH 3/5] ci: benchmark simulator boot runtimes --- .../ios-simulator-startup-timings.yml | 130 ++++++++++++++---- 1 file changed, 102 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index 81f3d8df..2f9f39b4 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -1,53 +1,127 @@ -name: iOS Simulator Startup Timings +name: Simulator boot benchmark on: - pull_request: - branches: - - main + workflow_dispatch: permissions: contents: read jobs: - measure-ios-simulator-startup: - name: Measure iOS simulator startup - runs-on: macos-latest + simulator-boot: + strategy: + fail-fast: false + matrix: + runtime: + - iOS-26-2 + - iOS-26-4 + - iOS-26-5 + + runs-on: macos-26 timeout-minutes: 20 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Measure simulator startup timings + steps: + - name: Select Xcode 26.6 shell: bash run: | - set -euxo pipefail + set -euo pipefail sudo xcode-select \ --switch /Applications/Xcode_26.6.app/Contents/Developer - echo "=== First launch ===" - time sudo xcodebuild -runFirstLaunch + xcodebuild -version + xcrun simctl list runtimes + xcrun simctl list devices available + + - name: Measure existing simulator boot + shell: bash + env: + RUNTIME_SUFFIX: ${{ matrix.runtime }} + DEVICE_NAME: iPhone 17 Pro + run: | + set -euo pipefail - echo "=== Cache preparation ===" - time xcrun simctl runtime dyld_shared_cache update --all + RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.${RUNTIME_SUFFIX}" + BOOT_LOG="$RUNNER_TEMP/bootstatus-${RUNTIME_SUFFIX}.log" - RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-26-5" - DEVICE_NAME="iPhone 17 Pro" + echo "Looking for:" + echo " Runtime: $RUNTIME_ID" + echo " Device: $DEVICE_NAME" + # Verify that the requested runtime is installed. + if ! xcrun simctl list runtimes -j | + jq -e --arg runtime "$RUNTIME_ID" ' + .runtimes[] + | select( + .identifier == $runtime + and .isAvailable == true + ) + ' >/dev/null; then + echo "::error::Runtime $RUNTIME_ID is not available" + exit 1 + fi + + # Select an existing available device only. UDID="$( xcrun simctl list devices available -j | - jq -er --arg runtime "$RUNTIME" --arg name "$DEVICE_NAME" ' - .devices[$runtime] - | map(select(.name == $name and .isAvailable == true)) - | .[0].udid - ' - )" + jq -er \ + --arg runtime "$RUNTIME_ID" \ + --arg name "$DEVICE_NAME" ' + .devices[$runtime] + | map( + select( + .name == $name + and .isAvailable == true + ) + ) + | .[0].udid + ' + )" || { + echo "::error::No existing $DEVICE_NAME found for $RUNTIME_ID" + echo "Available devices:" + xcrun simctl list devices available + exit 1 + } - echo "=== Selected simulator ===" - echo "$DEVICE_NAME ($RUNTIME): $UDID" + echo "Selected existing simulator:" + echo " $DEVICE_NAME" + echo " $RUNTIME_ID" + echo " $UDID" + + # Do not erase, recreate, or delete the selected device. + xcrun simctl shutdown "$UDID" 2>/dev/null || true + + START_SECONDS="$(date +%s)" echo "=== Boot request ===" - time xcrun simctl boot "$UDID" || true + time xcrun simctl boot "$UDID" echo "=== Boot completion ===" - time xcrun simctl bootstatus "$UDID" -b + set +e + xcrun simctl bootstatus "$UDID" -b 2>&1 | + tee "$BOOT_LOG" + BOOT_STATUS="${PIPESTATUS[0]}" + set -e + + END_SECONDS="$(date +%s)" + ELAPSED_SECONDS="$((END_SECONDS - START_SECONDS))" + + MIGRATION_SAMPLES="$( + grep -c "Waiting on Data Migration" "$BOOT_LOG" || true + )" + + { + echo "### Existing simulator boot result" + echo + echo "- Runtime: \`$RUNTIME_ID\`" + echo "- Device: \`$DEVICE_NAME\`" + echo "- UDID: \`$UDID\`" + echo "- Duration: **${ELAPSED_SECONDS}s**" + echo "- Migration samples: **${MIGRATION_SAMPLES}**" + echo "- Runner image: \`${ImageOS:-unknown}\`" + echo "- Image version: \`${ImageVersion:-unknown}\`" + } >> "$GITHUB_STEP_SUMMARY" + + if [[ "$BOOT_STATUS" -ne 0 ]]; then + echo "::error::Simulator failed to finish booting" + exit "$BOOT_STATUS" + fi From a999bc21aa8474128324b1edc79d67e4bafb6a2f Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 16:17:59 +0200 Subject: [PATCH 4/5] ci: cache iOS 26.5 simulator state --- .../ios-simulator-startup-timings.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index 2f9f39b4..f4c73052 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -29,6 +29,22 @@ jobs: --switch /Applications/Xcode_26.6.app/Contents/Developer xcodebuild -version + + echo "SIMULATOR_CACHE_KEY=core-simulator-device-ios-26-5-xcode-26-6-${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" + + - name: Restore iOS 26.5 simulator cache + id: cache-ios-26-5-restore + if: matrix.runtime == 'iOS-26-5' + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E + key: ${{ env.SIMULATOR_CACHE_KEY }} + + - name: Inspect available simulators + shell: bash + run: | + set -euo pipefail + xcrun simctl list runtimes xcrun simctl list devices available @@ -125,3 +141,10 @@ jobs: echo "::error::Simulator failed to finish booting" exit "$BOOT_STATUS" fi + + - name: Save iOS 26.5 simulator cache + if: always() && matrix.runtime == 'iOS-26-5' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E + key: ${{ steps.cache-ios-26-5-restore.outputs.cache-primary-key }} From 368a48bfcd2cdd99780b1d530dfcc09435d61b68 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 16:25:36 +0200 Subject: [PATCH 5/5] fix: shut down simulator before caching --- .../ios-simulator-startup-timings.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index f4c73052..a41f767a 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -142,8 +142,28 @@ jobs: exit "$BOOT_STATUS" fi + - name: Shut down iOS 26.5 simulator before caching + id: shutdown-ios-26-5 + if: always() && matrix.runtime == 'iOS-26-5' + shell: bash + run: | + set -euo pipefail + + UDID="FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E" + xcrun simctl shutdown "$UDID" || true + + for _ in {1..30}; do + if xcrun simctl list devices | grep -F "$UDID" | grep -q '(Shutdown)'; then + exit 0 + fi + sleep 1 + done + + echo "::error::Simulator $UDID did not shut down before caching" + exit 1 + - name: Save iOS 26.5 simulator cache - if: always() && matrix.runtime == 'iOS-26-5' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' + if: always() && matrix.runtime == 'iOS-26-5' && steps.shutdown-ios-26-5.outcome == 'success' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E