Hive Hive
Sign in

fix(cli): add xcframework Headers root for cached static-objc-behind-dynamic deps (ARCore)

GitHub issue · Closed

Metadata
Source
tuist/tuist #11506
Updated
Jul 5, 2026
Domains
Cache Generated projects
Details

Fixes ARCore (and similar Google SDK xcframeworks) failing to build when consumed from the binary cache.

What changed

StaticXCFrameworkModuleMapGraphMapper now also adds each matching slice’s Headers root to a consuming target’s HEADER_SEARCH_PATHS, in addition to the module map’s own directory. The root is computed as xcframework.path/<library.identifier>/<library.headersPath> and filtered by target.supportedPlatforms, mirroring the existing FRAMEWORK_SEARCH_PATHS logic in the same mapper and GraphTraverser’s header-root computation.

A nested-header test fixture (ARCore-shaped) was added.

Why / root cause

A target that depends on a cached static Objective-C xcframework reached behind a dynamic xcframework (e.g. Google’s ARCore) failed with:

ARCoreGARSession.xcframework/ios-arm64/Headers/ARCoreGARSession/GARAnchor.h:21:9:
'ARCoreGARSession/GARTrackingState.h' file not found
could not build Objective-C module 'ARCoreGARSession'

The mapper only put the module map’s own directory on HEADER_SEARCH_PATHS. Combined with the umbrella-header rewrite (<ARCoreGARSession/X.h><X.h>), that resolves the umbrella’s direct imports. But ARCore’s real headers re-import each other with the framework prefix (#import <ARCoreGARSession/GARTrackingState.h>), which only resolves when the xcframework’s Headers root is also on the search path.

That root used to be supplied by GraphTraverser.librariesPublicHeadersFolders until #11472 excluded the headers of any module-map-bearing xcframework (to fix redefinition of module 'X'). That exclusion is correct for directly-linked xcframeworks — Xcode’s ProcessXCFramework extracts the SDK-matching slice’s headers (and module map) into $(BUILT_PRODUCTS_DIR)/include, which is on the search path. But a static xcframework reached behind a dynamic one is never linked/processed by the consumer, so the include/ copy never exists and the headers became unreachable.

So #11472 traded the rc.1 “redefinition” error for a “missing header” error in the cached static-behind-dynamic path. This PR restores the missing root for that path only.

Why this solution

  • Nested layout (ARCore): the module map lives in a subdirectory, so adding the Headers root does not auto-discover a second module map → no redefinition reintroduced, and the prefixed sibling imports now resolve.
  • Flat layout (GoogleMaps): the root equals the module-map directory and is deduplicated → no behavior change.
  • Directly-linked xcframeworks are untouched (#11472’s GraphTraverser exclusion still applies). This only adds back what the cached static-behind-dynamic path lost.

Alternatives considered: copying the full Headers tree into tuist-derived/XCFrameworks/<name>/Headers to make the derived module self-contained — heavier (copies every header) and a larger change for the same effect.

Impact

Unblocks updating Tuist CLI for projects that cache Google ARCore (and similarly-shaped static ObjC xcframeworks with nested, prefix-self-importing headers). Reported by a customer stuck on 4.135.1; regression window is ≥ 4.195.0.

Should be backported to 4.201.x (same as #11472 → #11482).

How to test locally

  1. tuist generate TuistKit TuistKitTests --no-open
  2. xcodebuild test -workspace Tuist.xcworkspace -scheme Tuist-Workspace -only-testing TuistKitTests/StaticXCFrameworkModuleMapGraphMapperTests CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""

The new test test_map_when_static_xcframework_library_with_nested_module_headers_linked_via_dynamic_xcframework fails without the mapper change (asserts both the module-map dir and the Headers root on HEADER_SEARCH_PATHS) and passes with it. Verified the full suite is 18/18 green.

🤖 Generated with Claude Code

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