Hive Hive
Sign in

fix(cli): move framework search path setup into a graph mapper

GitHub issue · Closed

Metadata
Source
tuist/tuist #11054
Updated
Jun 24, 2026
Domains
CLI Generated projects
Details

What changed

Moves the entire setupFrameworkSearchPath logic out of LinkGenerator into a new FrameworkSearchPathsGraphMapper. The mapper now owns the whole concern for every target: it derives the precompiled and SDK framework search paths from the dependency graph, sets the build settings on the target model (FRAMEWORK_SEARCH_PATHS, and for consolidated targets OTHER_CFLAGS/OTHER_LDFLAGS @resp + OTHER_SWIFT_FLAGS inline -F), and writes the Derived/FrameworkSearchPaths/<Target>.resp file. LinkGenerator no longer touches framework search paths.

Why

Root cause of the recurring <Target>.resp “missing file” build failures on the framework-search-path consolidation (#10228).

DeleteDerivedDirectoryProjectMapper clears each project’s Derived/ subdirectories before regeneration. The .resp was emitted by LinkGenerator as a project side effect, which runs in an earlier generation phase than the mapper side effects, so on a regeneration over a pre-existing Derived/ (typical on CI) the cleanup deleted the just-written file, leaving the build referencing a missing @file:

error: no such file or directory: '@.../Derived/FrameworkSearchPaths/<Target>.resp'

Under Xcode 26 the Swift compiler surfaced earlier variants of the same missing-.resp problem first — Unexpected input file: <srcroot>/@<srcroot>/.../<Target>.resp (integrated SwiftDriver resolving the unexpanded @file as a working-dir-relative input) and unable to handle compilation, expected exactly one compiler job (-Xcc @file reaching the ClangImporter). #11033 (4.195.14) stopped routing the Swift compiler through the response file; this PR fixes the underlying file-deletion for clang/ld.

Every other Derived/ artifact (module maps, Info.plists, generated sources) survives the cleanup because it is written by mappers, in the same side-effect batch as the deletes (delete-then-recreate). Owning the .resp in a graph mapper puts it in that same batch, after the deletes, so it survives — exactly how ModuleMapMapper writes the deps module maps.

Why own it entirely in the mapper (single owner, no sync)

An earlier iteration kept the build-setting injection in LinkGenerator and only moved the file to a mapper, sharing the path/threshold/contents computation between the two. That split ownership and required a shared helper to keep the file and the @file settings in agreement. Moving the whole thing into the mapper removes the split: there is one owner, and nothing to sync. Build settings are written to the target model (base + per-configuration, mirroring ModuleMapMapper, since a per-config override shadows base), so the generated pbxproj is unchanged.

FRAMEWORK_SEARCH_PATHS derivation is a natural fit for a mapper — ExplicitDependencyGraphMapper, StaticXCFrameworkModuleMapGraphMapper, and ModuleMapMapper already derive build settings from the graph in mappers. The mapper is registered after the binary-cache replacement mappers (TargetsToCacheBinariesGraphMapper) in each graph-mapper factory function so it computes against the post-cache graph that produces the ≥20 precompiled xcframework search paths — the same final graph LinkGenerator used to see.

Validation

  • Built the CLI with TuistCacheEE present so the #if canImport(TuistCacheEE) cache-factory registrations are compile-checked; xcodebuild -scheme tuist and build-for-testing -scheme TuistUnitTests both succeed; lint clean.
  • E2E on a generated macOS app with 22 precompiled framework dependencies (crosses the threshold): tuist generate writes Derived/FrameworkSearchPaths/App.resp; a second tuist generate over the existing Derived/ keeps it (before, the cleanup deleted it). Generated settings are functionally identical — OTHER_CFLAGS/OTHER_LDFLAGS reference @…/App.resp, OTHER_SWIFT_FLAGS carries inline -F, and the precompiled paths are absent from FRAMEWORK_SEARCH_PATHS (they live in the resp).
  • Unit test for FrameworkSearchPathConsolidation.compute (consolidates ≥ threshold with SDK-only FRAMEWORK_SEARCH_PATHS + inline -F + the @resp reference; stays inline below threshold). The LinkGenerator framework-search-path tests are removed (that code moved).

Validation gap (please exercise in CI/review)

The E2E repro is a non-cache project, so it exercises the mapper via GraphMapperFactory (non-cache). The binary-cache flows in CacheGraphMapperFactory (automation/build/generation/binaryCacheWarming/…) are where affected customers actually hit this, and I can’t exercise binary caching locally. The mapper is registered per-function after each cache swap and compiles with EE, but a cache build should be run in CI/review to confirm the .resp is written and present for a cached target.

Impact

Restores buildability for projects that regenerate over an existing Derived/ and cross the framework-search-path consolidation threshold (commonly large graphs with many binary-cached dependencies on CI). Completes #11033 (4.195.14).


Note: the branch has three commits (a generation-phase reorder, then a file-only mapper, then this — the consolidated single-owner design). The net diff is just the final design; happy to squash to one commit if preferred.

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments
TA
tuist-atlas[bot] Jun 4, 2026

The fix for the missing .resp file build failures is now available in 4.195.16. Update to this version to get the fix.