Skip to content
Open
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
97 changes: 82 additions & 15 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,38 @@ commands:
python3 Tools/Tests/test_snapshot_shards.py
./snapshot-shards check

validate_runner_environment:
validate_build_environment:
steps:
- run:
name: Validate runner environment
name: Validate build environment
command: |
echo "CPU: $(sysctl -n machdep.cpu.brand_string)"
echo "Logical CPUs: $(sysctl -n hw.logicalcpu)"
echo "Memory bytes: $(sysctl -n hw.memsize)"
echo "Architecture: $(uname -m)"
sw_vers
xcodebuild -version
echo "Available simulator runtimes:"
xcrun simctl list runtimes available

xcode_build="$(xcodebuild -version | awk '/Build version/{print $3}')"
if [[ "$xcode_build" != "27A5228h" ]]; then
echo "Expected Xcode build 27A5228h, found $xcode_build" >&2
exit 1
fi

echo "Checkout: $(git rev-parse HEAD)"
echo "Origin: $(git remote get-url origin)"
if git config --local --get-regexp 'credential|extraheader' >/tmp/git-credential-config.txt 2>/dev/null; then
echo "Unexpected persisted Git credential configuration:" >&2
sed -E 's/(authorization:).*/\1 [REDACTED]/I' /tmp/git-credential-config.txt >&2
exit 1
fi

validate_simulator_environment:
steps:
- run:
name: Validate simulator environment
command: |
echo "Available simulator runtimes:"
xcrun simctl list runtimes available
if ! xcrun simctl list runtimes available | grep -Eq '^iOS 27(\.0)? '; then
echo "An available iOS 27 simulator runtime is required" >&2
exit 1
Expand All @@ -93,18 +106,21 @@ commands:
echo "Selected simulator: $selected_simulator"
xcrun simctl list devices available | grep "$selected_simulator"

echo "Checkout: $(git rev-parse HEAD)"
echo "Origin: $(git remote get-url origin)"
if git config --local --get-regexp 'credential|extraheader' >/tmp/git-credential-config.txt 2>/dev/null; then
echo "Unexpected persisted Git credential configuration:" >&2
sed -E 's/(authorization:).*/\1 [REDACTED]/I' /tmp/git-credential-config.txt >&2
exit 1
fi
validate_runner_environment:
steps:
- validate_build_environment
- validate_simulator_environment

prepare_builder:
parameters:
test_workdir:
type: string
run_ci_helper_tests:
type: boolean
default: true
require_simulator:
type: boolean
default: true
steps:
- checkout_without_lfs
- restore_cache:
Expand All @@ -130,7 +146,10 @@ commands:
- ~/.local/share/mise/installs
- configure_test_workdir:
test_workdir: << parameters.test_workdir >>
- test_ci_helpers
- when:
condition: << parameters.run_ci_helper_tests >>
steps:
- test_ci_helpers
- run:
name: Generate project and resolve Swift packages
command: |
Expand All @@ -153,7 +172,11 @@ commands:
key: swiftpm-artifacts-v2-{{ arch }}-{{ checksum "Package.resolved" }}
paths:
- ~/Library/Caches/org.swift.swiftpm/artifacts
- validate_runner_environment
- validate_build_environment
- when:
condition: << parameters.require_simulator >>
steps:
- validate_simulator_environment

prepare_worker:
parameters:
Expand All @@ -169,13 +192,16 @@ commands:

run_with_timeout:
parameters:
step_name:
type: string
default: Run tests
seconds:
type: integer
command:
type: string
steps:
- run:
name: Run tests
name: << parameters.step_name >>
no_output_timeout: 45m
command: |
ruby -e '
Expand Down Expand Up @@ -307,6 +333,41 @@ jobs:
test_workdir: test-output-ios
artifact_name: test-ios

build-where-audiences:
executor: m4-pro-medium
parallelism: 2
steps:
- prepare_builder:
test_workdir: test-output-audience-build
run_ci_helper_tests: false
require_simulator: false
- run:
name: Select Where audience
command: |
case "$CIRCLE_NODE_INDEX" in
0) scheme="Where Beta" ;;
1) scheme="Where App Store" ;;
*)
echo "No Where audience for shard $CIRCLE_NODE_INDEX" >&2
exit 1
;;
esac
printf 'export WHERE_AUDIENCE_SCHEME=%q\n' "$scheme" >> "$BASH_ENV"
echo "Selected scheme: $scheme"
- run_with_timeout:
step_name: Build selected Where audience
seconds: 2700
command: >-
set -o pipefail;
mise exec -- tuist xcodebuild build
-workspace Stuff.xcworkspace
-scheme "$WHERE_AUDIENCE_SCHEME"
-destination "generic/platform=iOS Simulator"
2>&1 | tee "$TEST_WORKDIR/build.log"
- collect_diagnostics:
test_workdir: test-output-audience-build
artifact_name: where-audience-build

snapshot:
executor: m4-pro-medium
parallelism: 4
Expand Down Expand Up @@ -379,6 +440,12 @@ workflows:
name: Build & Test (iOS)
requires:
- Build iOS Tests
# Each release audience needs a complete compile. Separate workers keep
# two serial release builds out of the workflow's critical path.
- build-where-audiences:
name: Build Where Audiences
requires:
- Build iOS Tests
- snapshot:
name: Snapshot Tests (iOS)
requires:
Expand Down
25 changes: 21 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,24 @@ easy to corrupt by hand. `./simulator` owns a per-checkout device (see the

`./icons` is the single command for the Where app's alternate icons (see
`./icons --help`). It keeps both asset catalogs and the picker's
`AppIcons.json` manifest in sync. Never hand-edit those files. Never add icon Swift.
Run `./ide --no-open` after adding one.
`AppIcons.json` manifest in sync — never hand-edit those or add icon Swift.
Run `./ide --no-open` after adding one. Icon-set names are independent of
primary/alternate status: each Where audience selects its primary icon in
[`Project.swift`](Project.swift), and every other set remains selectable as an
alternate. Change an audience's primary before asking `./icons` to remove that
asset; the command refuses to delete any configured primary.

### Where build audiences

The Where host targets have three explicit schemes — **Where Development**
(`Debug`), **Where Beta** (`Beta`), and **Where App Store** (`Release`) — whose
audience descriptors live in [`Project.swift`](Project.swift). Keep bundle IDs,
App Group, display name, primary icon, configuration, and the matching
`WHERE_DEVELOPMENT` / `WHERE_BETA` / `WHERE_APP_STORE` compiler condition in
that one descriptor. These custom conditions belong only to the app, widget,
and share-extension targets; package targets receive audience-dependent values
by injection. Development uses its own bundle family and local-only store;
Beta and App Store share the production bundle family and CloudKit store.

### Version and build metadata

Expand Down Expand Up @@ -165,7 +181,7 @@ triage). **Always-on** rules every edit must honor stay in `AGENTS.md` or
`AGENTS.md` says what it is and how it can be used.
- Add SPM library targets in `Package.swift` and wire apps/tests in `Project.swift` (see existing `unitTests` helper. Native-macOS test bundles are declared directly, like `LedgerCoreTests`, since that helper hosts iOS bundles in StuffTestHost). A new module also ships a root `README.md` and `AGENTS.md` — see [Per-module docs](#per-module-docs).
- **CI schemes**: CI runs explicit shared schemes rather than the autogenerated `Stuff-Workspace` scheme. **Stuff-iOS-Tests** covers the iOS bundles. **Ledger-macOS-Tests** (the Ledger app + `LedgerCoreTests`) runs in its own `test-macos` job. The workspace mixes iOS targets with the native-macOS **Ledger** ones. No single xcodebuild destination can build both. Add a new test bundle to the matching scheme in `Project.swift`. If you do not, CI will not run it.
- **CircleCI build handoff**: CircleCI builds both iOS schemes sequentially on one `m4pro.large` builder. The unit worker and parallel snapshot workers attach its products and run without compilation. Each worker validates the build manifest before it starts a simulator. `./snapshot-shards` owns three planned suite assignments and one intake shard. A new suite runs on the intake shard until rebalancing adds it to the plan. Each active snapshot worker must execute exactly its assigned suites.
- **CircleCI build handoff**: CircleCI builds both iOS test schemes sequentially on one `m4pro.large` builder. The unit worker and parallel snapshot workers use its products without compilation. Each test worker validates the build manifest before it starts a simulator. Two `m4pro.medium` build-only shards compile **Where Beta** and **Where App Store** after the shared builder completes. `./snapshot-shards` owns three planned suite assignments and one intake shard. A new suite runs on the intake shard until rebalancing adds it to the plan. Each active snapshot worker must execute exactly its assigned suites.
- **Image snapshots are the exception: one bundle per module, one shared scheme.** Each module owning image references has its own `*SnapshotTests` target over its `SnapshotTests/` folder. All are listed in the single shared **StuffSnapshotTests** scheme and its dedicated CI `snapshot` job. Snapshots are slow and LFS-backed. They are **out of** `Stuff-iOS-Tests`. References under any `__Snapshots__/` directory are Git LFS (`.gitattributes`. The CI job hydrates them explicitly). Framework halves: `Shared/SnapshotKit` (shippable matrix + previews) and `Shared/SnapshotKitTesting` (test-only pipeline, whose own regression bundle **SnapshotKitTestingTests** pixel-probes without LFS and runs in `Stuff-iOS-Tests`).
- **A new image suite gets a target, not a scheme.** Add the `*SnapshotTests` target. List only `SnapshotKitTesting` in `extraPackageProducts`. Add it to the `StuffSnapshotTests` scheme's build and test lists. Never add a scheme or CI job of its own. An image bundle links only what its module needs (the Periscope and Inspector suites don't build against WhereUI at all). References follow the sources automatically via `#filePath`.
- **Separate snapshot bundles are safe because each `.xctest` gets its own `StuffTestHost` process** (measured on Xcode 27 — `ProcessInfo.processIdentifier` probes. Details in the snapshot-bundle comment in [`Project.swift`](Project.swift)). Each bundle statically embeds its own copy of `SnapshotKitTesting`'s capture state. Two copies in one process corrupt each other. Tripwire: if a toolchain ever shares one host process across bundles, re-measure before adding another image bundle.
Expand Down Expand Up @@ -629,7 +645,8 @@ being written off as untestable from a cloud agent.
([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs `format`,
`architecture`, and `test-macos`. CircleCI
([`.circleci/config.yml`](.circleci/config.yml)) runs the iOS `test-ios` and
`snapshot` jobs, which moved there in PR #237. CircleCI passes
`snapshot` jobs, plus build-only checks for **Where Beta** and **Where App
Store**. The iOS tests moved there in PR #237. CircleCI passes
`--skip-architecture` so Bumper does not run twice. Do not read either file as
the whole of CI. See the
[`running-tests`](.agents/skills/running-tests/SKILL.md) skill for simulator
Expand Down
Loading
Loading