Skip to content

Xcode 27 support: SimulatorKit SharedFrameworks path + SwiftBuild-backend toolchain fixes#56

Merged
onevcat merged 7 commits into
mainfrom
fix/xcode27-simulatorkit-sharedframeworks
Jul 23, 2026
Merged

Xcode 27 support: SimulatorKit SharedFrameworks path + SwiftBuild-backend toolchain fixes#56
onevcat merged 7 commits into
mainfrom
fix/xcode27-simulatorkit-sharedframeworks

Conversation

@onevcat

@onevcat onevcat commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes sim-use fully usable under Xcode 27 (Beta 4+) in the classic Simulator.app / headless simctl workflow, and unbreaks the dev loop (make build / make test / E2E runners) on the SwiftBuild-backend toolchains (Xcode 26.6+ / 27).

1. SimulatorKit relocation (Xcode 27)

Xcode 27 moved SimulatorKit.framework to Contents/SharedFrameworks (Beta 1 shipped without it; Beta 4+ include it again). This PR backports upstream idb commit 98110129 onto our pinned idb ref (patches/idb/xcode27-simulatorkit-sharedframeworks.patch) so the framework loads from the new location with a fallback to the Xcode ≤ 26 path, and the XcodeCompatibility pre-flight accepts either.

The dtuhidd guard messaging is refreshed with what Beta 4 / CoreSimulator 1169.1 testing established: suppression is decided at simulator boot; a simulator booted while Device Hub is open loses tap as well as type; Device Hub attached after a clean boot leaves typing functional.

Scope note: the supported Xcode 27 workflow for now is classic Simulator.app / headless with Device Hub closed (documented in README). Full Device Hub support arrives with the planned migration to current upstream idb — see docs/ai/xxxx-xcode27-support/README.md for the verified plan (upstream's DTUHID transport was validated live, including in the suppressed-at-boot state).

2. SwiftBuild-backend dev-loop fixes (pre-existing on main, reproducible with Xcode 26.6)

  • The new backend emits no LC_RPATH for binary-target XCFrameworks → scripts/stage-fb-frameworks.sh stages framework symlinks into the PackageFrameworks directory dyld still searches (wired into make build/make test and both E2E runners; no-op on the classic layout).
  • The test suites resolved the sim-use binary via swift build --show-bin-path from inside the running swift test, which deadlocks on the package lock the test run now holds → getSimUsePath() prefers SIM_USE_TEST_BINARY (exported by the runners), then derives the path from the test bundle location, and only shells out as a last resort.

Related upstream report from the same investigation: facebook/idb#941. Local follow-up filed as #55 (daemon holds a stale HID connection across simulator reboot; pre-existing, out of scope here).

Verification

Full E2E matrix (scripts/test-runner.sh, 20 suites each), all on this branch's final tree, Device Hub closed:

Leg Selected Xcode iOS runtime Result
A 26.5 26.5 ✅ 20/20
B 26.5 27.0 ✅ 20/20
C 27.0 Beta 4 26.5 ✅ 20/20
D 27.0 Beta 4 27.0 ✅ 20/20

Unit tests (make test, 1094 tests): ✅ on Xcode 26.5, 26.6, and 27 Beta 4 (26.6/Beta 4 previously failed at test-bundle dlopen on main).

Manual spot-check under Xcode 27 Beta 4: drove a real-world app (LINE dev build on an iOS 26.2 simulator) — launch, describe-ui, tab-switch taps, opening a chat, native type into the message field (verified via screenshot), screenshot.

🤖 Generated with Claude Code

onevcat added 7 commits July 22, 2026 18:18
Xcode 27 moved SimulatorKit.framework from Contents/Developer/Library/
PrivateFrameworks to Contents/SharedFrameworks (early betas shipped
without it entirely; Beta 4+ include it again). Backport upstream idb
commit 98110129 onto our pinned idb ref so the XCFramework build
prefers the new location and falls back to the legacy one for
Xcode <= 26, and teach the XcodeCompatibility pre-flight to accept
either path.

Also refresh the dtuhidd guard messaging with what Beta 4 /
CoreSimulator 1169.1 testing established: the legacy-HID disconnection
is decided at simulator boot, a simulator booted while Device Hub is
open now loses tap as well as type, and a Device Hub attached after a
clean boot leaves typing functional (the guard can false-positive
there, hence the documented env override).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
The Xcode 26.6+/27 toolchains switch SwiftPM to the SwiftBuild backend
(products under .build/out/Products/<config>), which broke the dev loop
in two independent ways:

1. The backend emits no LC_RPATH for binary-target XCFrameworks, so the
   sim-use binary and the SimUseTests bundle fail to dlopen the FB*
   frameworks at load time. scripts/stage-fb-frameworks.sh stages
   symlinks into the PackageFrameworks directory dyld still searches
   (no-op on the classic layout) and is wired into make build/test and
   both E2E runners.

2. The test suites located the sim-use binary by shelling out to
   `swift build --show-bin-path` from inside the running `swift test`,
   which now deadlocks on the package lock the test run holds (observed
   as idle swift-build children under swiftpm-testing-helper, including
   during plain unit runs, where E2E-gated suites still resolve the
   path at collection). getSimUsePath now prefers SIM_USE_TEST_BINARY
   (exported by the runners), then derives the products directory from
   the test bundle location, and only shells out as a last resort.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
…elog

README documents the supported Xcode 27 workflow (classic Simulator.app
or headless simctl with Device Hub closed; full Device Hub support
arrives with the planned idb migration). docs/ai/xxxx-xcode27-support
is the full work record: Beta 1 -> Beta 4 deltas, the dtuhidd
suppression matrix on CoreSimulator 1169.1, upstream idb status
(DTUHID transport verified live), the bugs filed upstream and locally
(facebook/idb#941, #55), the SwiftBuild-backend breakages, and the
step-2 migration checklist with a reusable verification harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
…works

Review follow-up: the PackageFrameworks staging only ran inside make
targets and the E2E runners, so a bare `swift test --filter ...` — the
single-suite path CLAUDE.md documents — still failed to dlopen the FB*
frameworks on SwiftBuild-backend toolchains when run from a fresh
scratch directory (reproduced on Xcode 27 Beta 4).

Package.swift now emits explicit rpath entries per XCFramework slice:
@loader_path-relative for the classic and SwiftBuild product layouts
(executable and test-bundle depths differ), plus a repo-root-relative
fallback that covers custom --scratch-path runs started from the repo
root. The staging script stays as a belt-and-suspenders layer.

Verified: fresh-scratch bare `swift test --filter` and a no-staging
non-repo-CWD run both pass on Beta 4; `make test` 1094 green on 26.5
and Beta 4; full 20-suite E2E run green on Beta 4 + iOS 26.5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
- stage-fb-frameworks.sh exits early when scripts/build.sh has not
  produced build_products/XCFrameworks yet, so a fresh checkout gets a
  clean no-op instead of dangling symlinks. Document why the staged
  directory is still pre-created unconditionally otherwise (the layout
  cannot be known without building) and the script's Debug-dev-loop
  scope (release-config local builds are covered by the Package.swift
  rpath entries).
- XcodeCompatibility: build both SimulatorKit candidate paths through
  the same NSString API.
- Work record: note that agent-facing SKILL.md guidance for the
  suppressed-state tap no-op is deliberately deferred to the
  idb-migration work, which rewrites the Device Hub story.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
In the suppressed-at-boot state (simulator booted while Device Hub is
open) taps report success without landing and there is no runtime guard
on the touch path, so the bundled skill is the only place an agent can
learn the symptom and the recovery (quit Device Hub + reboot; view via
classic Simulator.app). Added as a pitfall table row plus a detailed
recipe, phrased as a temporary limitation to be rewritten when the idb
migration lands native Device Hub support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
Review follow-ups:

- The rpath entries Package.swift emits for the SwiftBuild-backend dev
  loop (build_products/... XCFramework slices, including a CWD-relative
  fallback) rode into release binaries, where dyld searches them ahead
  of the appended @executable_path/Frameworks - a shipped binary run
  from a checkout would silently load dev-built frameworks (brew ad-hoc
  resigns the binary, so no library validation blocks the swap).
  Release staging now scrubs them: remove_xcode_rpaths is generalized
  into remove_rpaths_matching, and configure_executable_rpath calls the
  new remove_build_products_rpaths.
- The test bundle re-emitted the CWD-relative entries it already
  inherits from the SimUse dependency, tripping ld's duplicate-rpath
  warning on every link; fbRPathFlags gains includeCWDFallback and the
  test target passes false.
- stage-fb-frameworks.sh cds to the repository root instead of assuming
  the caller's CWD.
- README/CHANGELOG note that the SimulatorKit relocation fix lives in
  the patched FB* XCFrameworks - pre-existing build_products need a
  ./scripts/build.sh dev rerun or the pre-flight passes while the
  framework load still fails.
- Refresh the stale @executable_path comment in Package.swift and the
  work-record wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
@onevcat
onevcat merged commit d823bed into main Jul 23, 2026
4 checks passed
@onevcat
onevcat deleted the fix/xcode27-simulatorkit-sharedframeworks branch July 23, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant