Add annual location time forecasts #825
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| format: | |
| name: SwiftFormat Lint | |
| runs-on: xcode-27 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: jdx/mise-action@v3 | |
| - run: ./swiftformat --lint | |
| # Catalogs drift out of Xcode's serialization the moment one is written by | |
| # anything but Xcode, and the cost lands on whoever next builds in the IDE | |
| # (see "Formatting" in AGENTS.md). This rides along in the format job | |
| # rather than a job of its own because branch protection pins the two | |
| # check names. | |
| - name: String Catalog lint | |
| run: ./xcstrings --lint | |
| # The only thing that actually stops a stale attribution report from | |
| # shipping: nothing about adding or bumping a dependency forces a re-run, | |
| # and the app can't check itself (its test bundle can't read Package.swift). | |
| # Offline and sub-second, so it rides along here for the same reason the | |
| # catalog lint does. | |
| - name: Attribution report check | |
| run: ./attribution --check | |
| architecture: | |
| name: Bumper Bowling | |
| needs: format | |
| runs-on: xcode-27 | |
| env: | |
| BUMPER_CACHE_DIR: .build/bumper-cache | |
| # Where evaluates in about one second; optimize the cold runner build | |
| # instead of spending minutes compiling SwiftSyntax in release mode. | |
| BUMPER_RUNNER_BUILD_CONFIGURATION: debug | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Restore Bumper build cache | |
| id: bumper-cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: .build | |
| key: ${{ runner.os }}-${{ runner.arch }}-bumper-${{ hashFiles('Package.swift', 'Package.resolved', 'BumperBowling.swift', '.bumper/Sources/**/*.swift', '.bumper/Tests/**/*.swift') }} | |
| - name: Validate configuration | |
| run: swift run bumper config . | |
| - name: Test architecture rules | |
| run: swift run bumper test . | |
| - name: Save Bumper build cache | |
| if: steps.bumper-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: .build | |
| key: ${{ steps.bumper-cache.outputs.cache-primary-key }} | |
| - name: Enforce Where architecture | |
| run: swift run bumper lint . --timings | |
| test: | |
| name: Build & Test (iOS) | |
| needs: format | |
| runs-on: xcode-27 | |
| # A cold/stalled CoreSimulator has held this job for 3.5–5 hours on the | |
| # xcode-27 image (a ~10 minute run), so cap it rather than letting a stuck | |
| # boot sit on a runner until the 6-hour default. | |
| timeout-minutes: 45 | |
| env: | |
| # A known path for the diagnostics step below, rather than digging through | |
| # DerivedData for whatever `.xcresult` looks freshest. | |
| TEST_WORKDIR: ${{ github.workspace }}/test-output | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: jdx/mise-action@v3 | |
| # `./test` owns the whole invocation — it regenerates the project, resolves | |
| # and boots the simulator by UDID via `./simulator` (never by name; see | |
| # "Selecting a simulator" in AGENTS.md), runs the scheme, and streams | |
| # progress. That is the same command a developer runs, so CI and local runs | |
| # can't drift apart, and the progress lines are newline-terminated here | |
| # because stdout isn't a terminal. | |
| # | |
| # `--shared` takes the image's existing iPhone 17 instead of creating a | |
| # device of this checkout's own: a job already owns its VM, so the isolation | |
| # is free here and a fresh device would only add a first-boot data migration | |
| # to every run. | |
| # | |
| # `--all` rather than the default affected-bundle scope: a CI run must not | |
| # depend on what the diff looks like. | |
| - name: Build & Test (iOS) | |
| run: ./test --all --shared --os 27.0 | |
| # Hosted tests can crash the test-host process without leaving any error in | |
| # the summarized output. On failure, capture crash reports plus the logs and | |
| # result bundle `./test` wrote, which hold the per-test crash backtraces. | |
| - name: Collect test diagnostics | |
| if: ${{ failure() }} | |
| run: | | |
| mkdir -p diagnostics/crashes | |
| cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true | |
| cp -R "$TEST_WORKDIR" diagnostics/test-output 2>/dev/null || true | |
| echo '--- collected diagnostics ---' | |
| find diagnostics -maxdepth 3 | head -200 | |
| - name: Upload test diagnostics | |
| if: ${{ failure() }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-diagnostics-ios | |
| path: diagnostics | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| snapshot: | |
| name: Snapshot Tests (iOS) | |
| needs: format | |
| # Same image as the `test` job: references are only reproducible against one | |
| # renderer, so the snapshot job must not lag the toolchain the rest of CI | |
| # (and every local run) uses. | |
| runs-on: xcode-27 | |
| # Same stuck-simulator cap as the `test` job above, sized for this job's | |
| # longer run. | |
| timeout-minutes: 60 | |
| env: | |
| TEST_WORKDIR: ${{ github.workspace }}/test-output | |
| steps: | |
| # Reference images live in Git LFS (see .gitattributes), so fetch them — | |
| # without this the images arrive as pointer files and every comparison fails. | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - uses: jdx/mise-action@v3 | |
| # `--snapshots` is the standalone `StuffSnapshotTests` scheme, which is | |
| # where the SNAPSHOT_EXPECTED_* / TZ pins live and which gathers every | |
| # module's image bundle — so this stays one invocation as more modules opt | |
| # in. `--review` describes any capture that differs from its reference | |
| # (pixel count, max channel delta, changed region), which is the difference | |
| # between a diagnosable failure and "does not match reference". | |
| # | |
| # Deliberately NOT parallel. xcodebuild's `-parallel-testing-enabled` / | |
| # `-parallelize-tests-among-destinations` distribute XCTest *classes*, and | |
| # every test here is Swift Testing, which presents none — so it lands | |
| # everything on one worker and Swift Testing's own in-process parallelism | |
| # then interleaves captures inside a single StuffTestHost, corrupting each | |
| # other's images (verified: 24+ spurious mismatches, and 1.2–3x SLOWER than | |
| # serial). See `SnapshotCaptureLock` and the sharding note in | |
| # Shared/SnapshotKitTesting/AGENTS.md. | |
| - name: Snapshot Tests (iOS) | |
| run: ./test --snapshots --shared --os 27.0 --review | |
| - name: Collect snapshot diagnostics | |
| if: ${{ failure() }} | |
| run: | | |
| mkdir -p diagnostics/crashes | |
| cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true | |
| cp -R "$TEST_WORKDIR" diagnostics/test-output 2>/dev/null || true | |
| echo '--- collected diagnostics ---' | |
| find diagnostics -maxdepth 3 | head -200 | |
| - name: Upload snapshot diagnostics | |
| if: ${{ failure() }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: snapshot-diagnostics-ios | |
| path: diagnostics | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| test-macos: | |
| name: Build & Test (macOS) | |
| needs: format | |
| # Same image as the other jobs so every target builds against one toolchain. | |
| runs-on: xcode-27 | |
| # The macOS-only Ledger scheme is self-contained and fast (no simulator, no | |
| # LFS), so this cap is well clear of a normal run. | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: jdx/mise-action@v3 | |
| # The workspace mixes iOS targets and the native-macOS Ledger targets, and | |
| # no single xcodebuild destination can build both — so the macOS scheme | |
| # runs here, in parallel with the iOS `test` job, rather than adding to it. | |
| - name: Build & Test (macOS) | |
| run: mise exec -- tuist test Ledger-macOS-Tests --no-selective-testing -- -destination 'platform=macOS' | |
| # Tests can crash the host process on CI without leaving any error in | |
| # Tuist's summarized log. On failure, capture crash reports + the .xcresult | |
| # bundle (which holds the per-test crash backtraces) and upload them. | |
| - name: Collect test diagnostics | |
| if: ${{ failure() }} | |
| run: | | |
| mkdir -p diagnostics/crashes diagnostics/xcresult | |
| cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true | |
| find "$HOME/Library/Developer/Xcode/DerivedData" -maxdepth 6 -name '*.xcresult' \ | |
| -exec cp -R {} diagnostics/xcresult/ \; 2>/dev/null || true | |
| cp -R "$HOME/.local/state/tuist/sessions" diagnostics/tuist-sessions 2>/dev/null || true | |
| echo '--- collected diagnostics ---' | |
| find diagnostics -maxdepth 3 | head -200 | |
| - name: Upload test diagnostics | |
| if: ${{ failure() }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-diagnostics-macos | |
| path: diagnostics | |
| if-no-files-found: warn | |
| retention-days: 7 |