What changed
In LinkGenerator.setupFrameworkSearchPath, the framework-search-path response file is now injected into OTHER_SWIFT_FLAGS as a bare @<resp> token instead of -Xcc @<resp>. OTHER_CFLAGS and OTHER_LDFLAGS are unchanged (they keep @<resp>).
Why
Users on Xcode 26 hit a hard build failure after the CLI starts consolidating framework search paths into a response file:
error: unable to handle compilation, expected exactly one compiler job in '...'
error: clang importer creation failed
Root cause
When a target has at least 20 unique precompiled framework search paths (the consolidation threshold introduced for ARG_MAX relief), Tuist writes those -F paths to Derived/FrameworkSearchPaths/<Target>.resp and references the file via @resp in the compiler/linker flags.
For OTHER_SWIFT_FLAGS the reference was emitted as -Xcc @resp. -Xcc forwards the next token verbatim to Swift’s ClangImporter. Under Xcode 26, ClangImporter’s createInvocation no longer expands @file response files, so the @resp path survives as a literal positional Objective-C input. clang then sees two inputs (the real translation unit plus @resp), produces two -cc1 jobs, and the importer asserts that it expected exactly one — surfacing as the two errors above.
Passing the response file as a bare @resp (no -Xcc) routes it through swift-driver instead, which does expand the response file and forwards the contained -F flags to the clang importer as proper search-path flags. clang never receives a @file token as an input, so only one -cc1 job is produced.
OTHER_CFLAGS (clang driver) and OTHER_LDFLAGS (ld) both expand @file correctly and still need it to stay under ARG_MAX, so those are left as-is.
When this regressed
The -Xcc @resp injection was introduced in #10228 (commit 319f3d8391, 2026-05-26), which added the whole framework-search-path consolidation — the threshold, the .resp file, and the OTHER_SWIFT_FLAGS line — in one change. It first shipped in CLI 4.195.7 and has been latent ever since.
It is not a regression in the generation code in the usual sense: setupFrameworkSearchPath is byte-identical across 4.195.7 → 4.195.12, and for a triggering project the generated pbxproj + resp + deps-modulemap are identical too. The defect only manifests under Xcode 26, whose ClangImporter stopped expanding @file; on earlier Xcode the same -Xcc @resp was harmless.
This also explains why per-version bisections do not point at this file — the only CLI commits in the relevant window are #10984 (cache content-hash change, 4.195.11) and #10996. The likely surfacing mechanism is that #10984 changed the cache content hash, which on upgrade invalidates and repopulates the binary cache (the source of the >= 20 precompiled frameworks), forcing cold rebuilds and shifting which targets cross the consolidation threshold, thereby re-exposing the latent Xcode 26 bug.
Validation
- Unit test
test_setupFrameworkSearchPath_consolidatesIntoResponseFile_whenManyPrecompiledPaths updated to assert OTHER_SWIFT_FLAGS references the bare @resp and contains no -Xcc; passes.
- Built the CLI (
xcodebuild -scheme tuist) — succeeds; lint clean.
- End-to-end on a generated macOS app with 22 precompiled frameworks plus a SwiftPM external dependency (the exact trigger shape), regenerated with the patched CLI and built on Xcode 26.4.1 with a cold module cache:
OTHER_SWIFT_FLAGS now ends in the bare @.../App.resp (the only -Xcc left is the unrelated explicit-modules -fmodule-map-file flag).
- Zero
expected exactly one compiler job / clang importer creation failed errors, and the .resp path is never handed to a clang -cc1 invocation as an input. Swift compilation completes and the build proceeds past it.
Impact
Restores buildability for Xcode 26 projects that cross the framework-search-path consolidation threshold (commonly via Tuist binary caching, where each cached target contributes one search path).