Add a hermetic Bazel build alongside SwiftPM - #246
Merged
Conversation
obj-p
commented
Jun 21, 2026
obj-p
commented
Jun 21, 2026
Owner
Author
There was a problem hiding this comment.
Should we keep tools in bazel/tools? What is the idiomatic choice. My thinking is keeping Bazel related things in bazel.
Owner
Author
There was a problem hiding this comment.
This one is a hard constraint, not a preference: tools/bazel is the bazelisk wrapper, and bazelisk specifically looks for tools/bazel at the workspace root and execs it (ours also sets PREVIEWSMCP_REPO_ROOT). Moving it under bazel/ would make bazelisk no longer find it, silently disabling the wrapper. So it stays at the root tools/ path.
Build previewsmcp, its full module graph, and all test suites with Bazel, so the product builds, runs, and renders without the SwiftPM glue scripts. SwiftPM is not yet retired; Package.swift still works. Build system: - Hermetic LLVM from the Swift fork via rules_foreign_cc cmake() (libLLVM.dylib in ~3.4min), pinned + sparse clone in bazel/llvm.bzl. orc runtime, host llvm-tblgen, and the iossim LLVM TargetProcess + orc cross-builds via an apple_support platform transition (bazel/transitions.bzl). - Swift graph via rules_swift + rules_swift_package_manager (SPM deps from Package.resolved). The 4 SPM build-tool plugins become Bazel rules (version stamping, host-app source embedding, resource bundling). - PreviewAgent (the macOS JIT executor) as a cc_binary on libLLVM. server.o (the iOS in-sim executor object) via a custom cc_object rule (bazel/object.bzl) that compiles for iossim while reusing the cached macOS libLLVM headers. JIT runtime resources are resolved from the binary's runfiles (env override for tests, runfiles search for bazel run), retiring the need for a SwiftPM resource bundle. macOS and iOS previews both render end-to-end. Tests: all 8 suites run as swift_test (tags local/exclusive). A tools/bazel wrapper exports the workspace root so integration tests find the checked-in examples; the Bazel-built previewsmcp binary is supplied via runfiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
examples/bazel is a nested Bazel workspace and the SwiftPM .build dirs carry foreign BUILD files; both broke the //... wildcard CI and bootstrap rely on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
One-file SwiftUI library product (the preview setup protocol), no deps. Last SPM target not yet in the Bazel graph. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
Bazel is now the build for development, tests, and CI. SwiftPM is kept as the published manifest so other projects can depend on the library products (notably PreviewsSetupKit) and to feed the Bazel external-dependency sync. - Brewfile: add bazelisk. - AGENTS.md: Setup and Build & Test lead with bazel build/test/run; add a "Consuming via SwiftPM" section; the PR-verification step runs bazel test (swift test kept as a fallback). Note that the iOS JIT resources and LLVM are built by Bazel automatically, so the build-jit-llvm scripts are not needed under Bazel. - bootstrap skill: build with `bazel build //...`; --jit is now only for the SwiftPM JIT path; verify bazel availability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
…epo path orc_rt and orc_rt_iossim hardcoded the bzlmod-canonical external path ($$EXT_BUILD_ROOT$$/external/+llvm_repository+llvm_src/llvm/cmake/modules) for LLVM_CMAKE_DIR, which breaks if the module-extension or repo name changes. Add a small stage_dir rule that filters the llvm/cmake/modules files out of @llvm_src into a declared directory (//:llvm_cmake_modules), and pass it to both cmake targets via build_data + $(execpath), the same pattern //:iossim_orc already uses for llvm-tblgen. No canonical repo name is hardcoded. Both orc archives rebuild; PreviewsJITLinkTests passes on the rebuilt runtime. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
new ios-host/app/Shell app (ShellMain.m, Info.plist, entitlements, icon), emitting an IOSShellAppSource enum that PreviewsIOS now references. Extend the Bazel embed_host_app_source rule + header fragment to match: four new shell inputs, four _shell*Base64 lets, and the IOSShellAppSource enum. Wire the Shell files through ios-host/app exports. PreviewsIOS compiles and all 8 suites pass, iOS render included. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
…244) Earlier I deleted IOSSimSpikeTests as obsolete scaffolding. Per #244 it is not a spike: it is the real end-to-end iOS suite (host-app build, sim boot, flash-free respawn, relaunch, JIT-over-EPC render, agent->shell redirect). Restore it from main and address #244 part 1: - Rename IOSSimSpikeTests -> IOSPreviewE2ETests (file, suite, SimSpikeSupport -> IOSPreviewE2ESupport); refresh the stale "Phase 0 spike" doc. - Give it its own swift_test target (excluded from PreviewsJITLinkTests) wired with the iOS JIT resources; locate packageRoot + fixtures via PREVIEWSMCP_REPO_ROOT. - Mark the suite @suite(.serialized): all 18 tests share one simulator, one host-app bundle ID, and one IOSHostBuilder workDir, so concurrent runs clobber the host-app source mid-build ("modified during the build"). Serial is correct and also fixes the same spurious failures under `swift test`. 18/18 pass (the standalone iossim-executor tests self-skip when that artifact is absent). The worktree-level isolation #244 mentions is a separate concern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVPJon8ZA1CLZcPhKTUng9
Adopt rules_xcodeproj 4.1.0 (dev_dependency) so Xcode builds-with-Bazel across the LLVM/JIT/iOS graph. A swift_binary's data/runfiles are not staged by BwB (rules_xcodeproj#828), so previewsmcp's iOS JIT resources are absent when Run from Xcode. The xcschemes Run pre-action builds the runfiles tree in the workspace output base and points RUNFILES_DIR at it, giving Xcode Run the same resource fidelity as `bazel run //Sources/previewsmcp`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…reviewsSetupKit Reshape the repo around the Bazel-primary model. All first-party Bazel code moves from Sources/ into a previewsmcp/ umbrella (modules as previewsmcp/<X>, the binary at previewsmcp/cli with target name previewsmcp). Tests/ and ios-host/ (agent/shell/executor) move under previewsmcp/ too. bazel/ and the root LLVM cmake targets stay at root. examples/ is untouched. Sources/ now holds only PreviewsSetupKit, the one SwiftPM-exposed library. Package.swift is slimmed to expose just PreviewsSetupKit, keeping the three external .package() declarations so rules_swift_package_manager's from_package still syncs them. The SwiftPM-only GenerateVersionTool / EmbedHostAppSourceTool / Plugins are deleted (Bazel's version.bzl / embed.bzl replace them). .mcp.json launches via scripts/previewsmcp, which builds //previewsmcp/cli and execs the Bazel binary (build output to stderr to keep MCP stdio clean). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…ecutor
The Bazel build no longer needs the SwiftPM LLVM build or the spike standalone
executor. Delete scripts/build-jit-llvm*.sh, ios-host/executor/{build.sh,
main.cpp,bundle.sh}, and the standalone-executor tests in IOSPreviewE2ETests
(the withRemoteSession/spawnExecutor path that linked objects via a
simctl-spawned iossim-executor against third_party/.build-iossim artifacts).
Production keeps server.cpp/server.h (compiled to server.o via cc_object and
embedded in the agent app) and the IOSAgentBuilder-based e2e tests.
Move the LLVM fork patches to a top-level patches/ (they patch a fetched
dependency, not build logic) and update MODULE.bazel's llvm_repository. Strip
the build-jit-llvm and third_party references from CI, AGENTS.md, and the
bootstrap skill.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…project Providing custom xcschemes had disabled rules_xcodeproj scheme autogeneration, leaving only the previewsmcp scheme. Set scheme_autogeneration_mode = "all", add the swift_test targets to top_level_targets so they get test schemes (which required making them publicly visible to the generator), and exclude the external-dependency schemes (NIO, ArgumentParser, SwiftSyntax, etc.) via scheme_autogeneration_config. The project now shows the previewsmcp run scheme, the nine first-party libraries, and the nine test suites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…esolution) Two pre-existing issues surfaced only when the full-suite gate is run as the `//...` wildcard (the handoff ran the 9 explicit test labels instead). Both reproduce on the base bazel-migration, so neither is a restructure regression. 1. `//...` builds the iOS-only foreign_cc cmake targets `iossim_orc` and `orc_rt_iossim` in the host (macOS) config as top-level targets. They set `CMAKE_SYSTEM_NAME=iOS` but get the macOS toolchain/SDK, so CMake's CheckAtomic try-compile fails and aborts configure with a misleading "Host compiler appears to require libatomic". These targets are only meant to be consumed via the `ios_sim_build` transition. Mark them `target_compatible_with = ["@platforms//os:ios"]` so `//...` skips them as incompatible in the host config while the transition still builds them. 2. `IOSPreviewE2ESupport.compileForIOSSim` resolved the Fixtures dir via `#filePath` only, which `bazel test` file-prefix-maps into runfiles where `Fixtures/` is not staged, failing the two `answer.c` tests. Resolve it via `PREVIEWSMCP_REPO_ROOT` first (mirroring the canonical `FixtureSupport.fixturesDirectory`), keeping `#filePath` as the swift-test fallback. After both fixes the 6 non-sim suites + CLI/MCP integration are green and IOSPreviewE2ETests is back to the documented 11/12 (one rotating sim-health flake on the JIT-render-over-sim tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…move `IOSPreviewE2ETests.packageRoot` and `ExamplesSplitE2ETests.repoRoot` derive the repo root from `#filePath` when `PREVIEWSMCP_REPO_ROOT` is unset. The restructure moved these files one level deeper (under `previewsmcp/`), but the fallback still stripped only three path components, landing at `<repo>/previewsmcp/` instead of `<repo>/`. Add the fourth `deletingLastPathComponent()` so the examples/spm paths resolve when the suite is run without the `tools/bazel` env passthrough. Masked today because `.bazelrc` forwards `PREVIEWSMCP_REPO_ROOT` and `tools/bazel` sets it, so the env branch normally wins. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
The Bazel build compiles LLVM + the orc runtime hermetically via foreign_cc, so nothing on this branch produces third_party/ anymore (the SwiftPM-JIT teardown removed the last reference). Drop the now-dead ignore line. The physical third_party/ directory still belongs to the SwiftPM-JIT path on main and is local-only; reclaim it with `rm -rf third_party` after this lands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…site) CI's stale `Sources/ Tests/` swiftlint paths were hiding these in the moved code. Clearing them so the upcoming hermetic-lint Bazel gate can be a hard `bazel test` failure rather than advisory (#245). - PreviewSession.storedWindowFrame: return a named `WindowFrame` struct instead of a 4-member tuple (large_tuple). All four callers use member access, so the change is a drop-in. - LogRotationTests: make `tempDir()` and the four @test funcs `throws`, replacing eight `try!` with `try` (force_try). - BuildSystemTests: move the trailing XcodeBuildSystem/manifest test block into a same-file `extension` so the struct body drops under the type_body_length error threshold (no test moved out of the suite, no content change). Verified: swiftlint error-severity count 0; swift-format --strict clean; PreviewsCoreTests + PreviewsJITLinkTests pass; PreviewsMacOS builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
obj-p
marked this pull request as draft
June 23, 2026 02:31
…ls/lint Pin the tools as Bazel downloads (not host PATH) and drive them with a single run-based entry point, so CI / the local merge queue (#230) gates lint without host installs. This is the run-based model that aspect_rules_lint itself uses for format checks; an aspect was evaluated and rejected (no ruleset supports Apple swift-format or swiftlint, caching is irrelevant at ~10 modules, and the sandbox would break .swiftlint.yml's path-based excludes). - MODULE.bazel: http_file buildifier v8.5.1 + clang-format 19 (muttleyxd static), http_archive swiftlint 0.63.3; add rules_shell as a direct dep. - tools/lint/lint.sh + BUILD.bazel: `bazel run //tools/lint:format` (fix) and `:check` (verify). Each tool resolved via runfiles rlocation, run over $BUILD_WORKSPACE_DIRECTORY so existing .swift-format/.swiftlint.yml/.clang-format configs and excludes apply unchanged. swiftlint is check-only (non-strict, so warnings stay advisory like the prior CI). swift-format is wired but inert until its from-source binary lands (next commit). - Apply the one-time buildifier (BUILD/.bzl/MODULE) and clang-format (C/C++) formatting. Verified: `bazel run //tools/lint:check` exits 0, //previewsmcp/... builds clean after the C/C++ reformat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
Apple swift-format ships no prebuilt binary and pulls a multi-package source
graph (swift-syntax et al), so the only hermetic options were a repository_rule
that recompiles it on every fresh/ephemeral runner (not remote-cacheable) or
hosting our own built binary. nicklockwood SwiftFormat ships a single official
universal macOS binary, so it drops in as an http_archive exactly like the other
three tools: one cached, sha-pinned download everywhere, remote-cache friendly,
zero consumer pollution. Decision made after an investigation that found the
caching profile is identical to prebuild-and-publish, so the tiebreaker was not
hosting an artifact.
- MODULE.bazel: pin SwiftFormat 0.61.1 via http_archive; drop the swift-format
from-source repo rule (bazel/swift_format.bzl).
- .swiftformat: maxwidth 120, exclude the generated embed sources, and DISABLE
hoistTry, hoistAwait, preferKeyPath. The first two are required: hoistAwait
rewrote `let x = (try? await f())` into the invalid `let await x = (try? f())`.
preferKeyPath rewrote `map { $0.schema }` into `map(\.schema)`, which crashed
swiftc SILGen in MCPServer.mcpToolSchemas().
- Reformat the Swift tree (170/192 files). SwiftFormat is more vertical than
swift-format, so bump the swiftlint error ceilings (type_body_length 1000->1100,
file_length 1500->1600); warnings unchanged.
- Re-pin IOSAgentBuilderHashTests: reformatting AgentApp.swift changed the
embedded agent-app source hash.
- lint.sh/BUILD: swap swift-format for swiftformat (lint via `<paths> --lint`).
Verified: `bazel run //tools/lint:check` exits 0 (all four tools), SwiftFormat
idempotent (0/192), zero swiftlint errors, all six non-sim test suites pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
Make the Bazel-pinned tools the single source of truth so a local commit and the
merge gate can never disagree on tool versions.
- lint.sh: unify file selection on git (git ls-files for the whole tree,
git diff --cached for staged) instead of find, and add a `staged` mode that
lints only the staged files. Swift tools skip bazel/ tooling fragments
(header.swift is generated DO-NOT-EDIT); clang-format skips test Fixtures.
- tools/lint: add the //tools/lint:staged target.
- .githooks/pre-commit: replace host swift-format/swiftlint/clang-format with
`bazel run //tools/lint:staged`.
- Brewfile: drop swift-format, swiftlint, clang-format (now hermetic); keep only
bazelisk.
- AGENTS.md + bootstrap skill: document `bazel run //tools/lint:{format,check,staged}`
as the lint entry points and that the merge gate runs `:check`.
Verified: `bazel run //tools/lint:check` exits 0; staged mode flags only staged
violations and exits non-zero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXqo6GCpm1xTPc2YMicCna
…LVM rebuild `bazel clean` (or a fresh output base) otherwise rebuilds the foreign_cc LLVM graph cold (~5.5 min). Point `--disk_cache` at ~/.cache/bazel-disk (Bazel expands ~, so the checked-in path stays username-free) to back the normal output base with a content-addressed store. Verified: warm build 333s populates a 447MB cache; after `bazel clean` the rebuild is 3.5s with 132 disk cache hits and 0 actions executed (LLVM served from cache, never rebuilt). Part of #272; the shared remote cache is a separate, runner-model-gated step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integration suite spawns a `previewsmcp serve` daemon that persists past the run by design (cleanup is the next suite's cleanSlate, not teardown). The shared CLIRunner falls back to the real ~/.previewsmcp when PREVIEWSMCP_SOCKET_DIR is unset, and this Bazel target never set it, so `bazel test` left a daemon in the user's real daemon dir and its cleanSlate killed any live session daemon there. CI already isolates this to /tmp (ci.yml PREVIEWSMCP_SOCKET_DIR); mirror that for the Bazel target. Verified: the suite's serve.log/sessions land in /tmp/previewsmcp-bazel-cli, ~/.previewsmcp is untouched, and a concurrently-running session daemon survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `try-import %workspace%/.bazelrc.user` so each machine supplies its own `--remote_cache` (and upload gating) out of a gitignored `.bazelrc.user`, keeping the private endpoint out of the repo. A missing file is a silent no-op, so an external clone builds locally with no warnings. The merge-queue VM drops its own `.bazelrc.user` during provisioning. Backed by a self-hosted bazel-remote on the Mac mini over Tailscale. Verified: a fresh output base with no disk cache and the remote supplied only through the try-import'd file is a full remote cache hit (132 hits, 0 actions executed, no foreign_cc LLVM rebuild) -- a 6.5min cold build collapses to ~90s. Completes the remote half of #272. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The //:xcodeproj `previewsmcp` Run scheme pre-action built the binary with `>/dev/null 2>&1 || bazel ...`, so a broken build produced no diagnostic and Run launched against stale-or-absent runfiles. Pick bazelisk-or-bazel by presence and run the build unsilenced, so a failure surfaces in the pre-action log and aborts Run instead of yielding a confusing runtime error. The two-output-base split (binary from the rxp base, RUNFILES_DIR from the normal base) is left as-is: it is forced by rules_xcodeproj#828 (BwB does not stage a swift_binary's runfiles), not cleanly removable until upstream lands. Part of #270. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion fallback Code-review follow-ups on this branch's caching/cleanup work: - MCPIntegrationTests now pins PREVIEWSMCP_SOCKET_DIR=/tmp/previewsmcp-bazel-mcp like CLIIntegrationTests, so neither daemon-touching suite touches the real ~/.previewsmcp (MCPTestServer spawned `serve` on the inherited env and only DaemonLifecycleTests overrode the dir per-spawn, leaving the suite half-isolated). - Restore the `bazelisk || bazel` fallback in the //:xcodeproj runfiles pre-action (the `command -v` rewrite dropped it); still unsilenced, so it fails loudly when both fail. - Register both bazel socket dirs in AGENTS.md so the per-suite convention has one source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the disk `--disk_cache` has no GC by default, so ~/.cache/bazel-disk grows without bound (the LLVM artifacts are large) until the disk fills. Set `--experimental_disk_cache_gc_max_size=50G`; the Bazel server's idle GC then evicts LRU entries to keep the cache under the cap. Surfaced by code review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # Package.swift # previewsmcp/PreviewsIOS/AVCCVideoStream.swift # previewsmcp/PreviewsIOS/FrameSource.swift # previewsmcp/PreviewsIOS/H264Encoder.swift # previewsmcp/PreviewsIOS/InputSink.swift # previewsmcp/PreviewsIOS/PreviewAppServer.swift # previewsmcp/PreviewsIOS/Resources/client.html # previewsmcp/PreviewsIOS/SimulatorManager.swift # previewsmcp/Tests/MCPIntegrationTests/IOSAppServerTests.swift # previewsmcp/Tests/MCPIntegrationTests/IOSHIDInputTests.swift # previewsmcp/Tests/PreviewsIOSTests/H264EncoderTests.swift # previewsmcp/Tests/PreviewsIOSTests/SimulatorManagerTests.swift
The JIT render fixtures call NSBitmapImageRep with only `import SwiftUI`. That compiled while SwiftUI transitively re-exported AppKit, but Xcode 26.2's SwiftUI no longer does, so the fixtures fail with "cannot find 'NSBitmapImageRep' in scope". Add `import AppKit` to the affected fixture templates. Pre-existing environment drift (reproduces before this branch's merge of main; hits the SwiftPM path on main too), surfaced by the pre-merge full-suite gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
obj-p
marked this pull request as ready for review
June 24, 2026 13:45
obj-p
added a commit
that referenced
this pull request
Jun 24, 2026
The hermetic Bazel migration (#246) accidentally tracked a worktree-local `third_party -> ../../third_party` symlink (staged by a `git add -A` during the merge resolution). Bazel builds LLVM from source via foreign_cc and the slim Package.swift no longer references the prebuilt artifacts, so the symlink is unused and dangles in any fresh clone. Remove it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 9, 2026
obj-p
added a commit
that referenced
this pull request
Jul 11, 2026
…e-gate trial (#361) * CI on Bazel: rewrite workflows for the post-#246 tree, start the merge-gate trial The old workflows ran swift build / swift test against the slim Package.swift stub, so CI would green-light without testing anything real, and release.yml would package the stub binary. Both now run Bazel: ci.yml is one macos-15 job (Xcode 26.2 to match the .bazelrc pin; the image bundles the iOS 26.2 sim runtime and iPhone 17 devicetype SimulatorTestDevices needs) doing bazel test //... plus the hermetic lint, with the Bazel disk cache under actions/cache. release.yml builds -c opt //previewsmcp/cli:previewsmcp. cache-warm.yml existed to keep SPM caches alive and goes away with them. CI runs as a non-required signal while we collect flake and wall-clock data; the ruleset making it the merge gate comes after a green track record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Pin host_macos_minimum_os so exec tools run on runners older than the SDK First trial run failed in 3 minutes: rules_swift's test_discoverer is built in the exec configuration, which defaults its minimum OS to the SDK version (26.2), and dyld on the macos-15 runner refuses to load it. Hosts running macOS 26 never see this. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Explicit timeouts for the sim-heavy test targets Trial run 2: all four sim/integration targets died at Bazel's default 300s cap on the GHA runner (slower cores + per-run first-boot device creation), while passing locally in 20-180s. The suites bound their own tests with .timeLimit traits, so eternal delegates the per-test caps to them; PreviewsJITLinkTests ran 189s on the runner, close enough to 300 to bump to long. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * CI moves to the self-hosted Mac mini runner Hosted macos-15 measured 50-70 min warm against ~5-7 min for the same suite locally, so the trial pivots to a self-hosted Apple Silicon runner. The persistent host replaces three steps: DEVELOPER_DIR pins Xcode per-job instead of sudo xcode-select, ~/.cache/bazel-disk persists natively so actions/cache goes away, and Spotlight is disabled once at host setup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Lint before test: seconds of lint fail fast ahead of minutes of tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * iOS preview_start test calls get a 600s bound instead of the 60s default First-ever run on a fresh machine (the mini's first CI job) pays device first-boot plus a cold SwiftPM build of the example inside preview_start, minutes by design; the generic 60s callTool bound failed exactly the two iOS suites that used it while IOSAppServerTests survived only because callToolResult carries no timeout at all. 600 seconds is a bound, not a wait: warm runs stay ~17s, and the suite .timeLimit traits still cap the tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Event-pump probe gets a 10s dispatch budget The mini's first CI run failed the 2s budget under full build+test load while the agent kept answering runOnMain polls — the main thread was alive, the posted event's run-loop dequeue was just late. The probe passes over SSH on the same host in 1.3s, so dispatch itself works there; the budget was sized for an idle laptop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * checkout@v7/cache@v6 (Node 20 deprecation) + weekly scheduled CI heartbeat The schedule replaces what cache-warm.yml did for the hosted design: with the disk cache living on the runner there is nothing to keep alive, but a weekly run on a quiet repo still catches host/toolchain drift before a PR does, and is a standing flake probe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Docs catch up with the CI rewrite AGENTS.md still said CI was disabled and described the retired two-job hosted topology (build-and-test/ios-tests socket-dir split, cross-job daemon contention); setup-cache.md still documented the SPM actions/cache keys. Both now describe the single-job self-hosted reality. The parity spec's historical baseline reference stays as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remove inert skip-on-CI guards; bound IOSAppServerTests preview_start at 600s The three .disabled(if: CI != nil) traits stopped meaning anything the moment tests moved under Bazel: test envs are hermetic, nothing forwards CI, so the sim suites ran on the mini only by accident of env-scrubbing — and anyone adding --test_env=CI later would silently skip all three and revert the migration with no failing signal. The guards' original referent (GHA macos-15 under combined load wedging CoreSimulator, PR #141) is retired hardware for this suite, and an unprovisioned host still skips via the nil-device guard. callToolResult gains an optional bounded path reusing withTimeout + the stderr dump, and appServerEndToEnd's preview_start uses it at 600s — the last of the three iOS starts that could hang silently to its .timeLimit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Failure-diagnostics step: capture sim/daemon state while it is still true Code review flagged that bazel test --test_output=errors dropped the old workflow's on-failure daemon/sim dumps. On the persistent runner that state survives the job but not the next run; printing it into the failing run's log keeps the weekly flake probe actionable (#350 specimen collection included). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds previewsmcp, its full module graph, and all test suites with Bazel, so the product builds, runs, and renders without the SwiftPM glue scripts. SwiftPM is not retired:
Package.swiftstays as the published manifest for consumers and feeds the Bazel dependency sync, so the two build systems coexist.Build system
rules_foreign_cccmake()(libLLVM.dylib in ~3.4 min), pinned + sparse clone inbazel/llvm.bzl. orc runtime, hostllvm-tblgen, and the iossim LLVM TargetProcess + orc cross-builds go through an apple_support platform transition (bazel/transitions.bzl).rules_swift+rules_swift_package_manager(SPM deps read fromPackage.resolved). The 4 SPM build-tool plugins become Bazel rules (version stamping, host-app source embedding incl. the#241Shell app, resource bundling).cc_binaryon libLLVM. server.o (iOS in-sim executor object) via a customcc_objectrule (bazel/object.bzl) that compiles for iossim while reusing the cached macOS libLLVM headers. LLVM cmake modules referenced by label (//:llvm_cmake_modules), no bzlmod-canonical path hardcoded.Runtime + tests
bazel run), so the SwiftPM resource bundle is not needed. macOS and iOS previews both render end-to-end, in tests and viabazel run.swift_test(tagslocal/exclusive). Atools/bazelwrapper exports the workspace root so integration tests find the checked-inexamples/. The iOS end-to-end suite is renamedIOSPreviewE2ETests(per Rename IOSSimSpikeTests + fix test isolation (no longer a spike) #244) and@Suite(.serialized).bazel test //Tests/...— all 9 targets green (macOS + iOS render, daemon integration, e2e).Docs
AGENTS.md, thebootstrapskill, andBrewfilelead with Bazel and keep SwiftPM for consumers.Follow-ups (optional)
IOSPreviewE2ETestscan run in parallel.🤖 Generated with Claude Code