Hive Hive
Sign in

fix(cli): consume nested static-objc xcframeworks via their own module map (ARCore)

GitHub issue · Closed

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

Corrects the ARCore caching fix from #11506, which traded one build failure for another. Reproduced and guarded by a new acceptance test.

What changed

StaticXCFrameworkModuleMapGraphMapper now distinguishes nested ARCore-style static Objective-C xcframeworks from flat ones and consumes nested ones through the xcframework’s own module map:

  • Nested (module map + headers in a Headers/<ModuleName>/ subdirectory): -fmodule-map-file points at the xcframework’s original module map, and the Headers root (parent of the <ModuleName>/ subdir) is the consumer’s HEADER_SEARCH_PATHS. No derived copy or umbrella rewrite is generated.
  • Flat (headers directly next to the module map): unchanged — derived module map + prefix-stripped umbrella, as before.

A new acceptance test (generated_macos_tool_with_cached_nested_header_xcframework) and a nested unit test were added. Existing flat/framework/relocation unit tests are untouched.

Why / root cause

ARCore’s static xcframeworks (verified against ARCoreGARSession 1.54.0) lay their public API out as:

ARCoreGARSession.xcframework/ios-arm64/Headers/ARCoreGARSession/
module.modulemap # module ARCoreGARSession { umbrella header "…"; module * { export * } }
ARCoreGARSession.h # #import <ARCoreGARSession/GARAnchor.h> … (framework-prefixed)
GARAnchor.h # #import <ARCoreGARSession/GARTrackingState.h> (prefixed sibling import)

When such an xcframework is reached behind a dynamic framework through the binary cache, it routes through this mapper (it is never linked directly, so Xcode’s ProcessXCFramework never extracts its headers to $(BUILT_PRODUCTS_DIR)/include).

  • rc.2: the mapper put only the Headers/<Module>/ subdir on the search path, so the headers’ own <Module/Sibling.h> cross-imports could not resolve → 'ARCoreGARSession/GARTrackingState.h' file not foundcould not build Objective-C module 'ARCoreGARSession'.
  • #11506 (rc.3): added the xcframework’s Headers root to the search path. The imports resolved, but the original module map (sitting next to the headers, with module * { export * } wildcard submodules) became reachable alongside the derived copy referenced via -fmodule-map-file → the module was defined twice → import of shadowed module 'GARAnchor'could not build Objective-C module 'ARCoreGeospatial'.

Both are the same flat-vs-nested split the mapper never modeled. For nested layouts the umbrella already imports with the <Module/...> prefix, so the original module map + Headers root resolves everything with the module defined exactly once — no derived copy, no rewrite, no second module map.

Why this approach over the obvious alternative

A unified “copy the whole module-map directory into a hermetic derived tree” also works and was validated, but it changes HEADER_SEARCH_PATHS/-fmodule-map-file/side-effects for every static-objc library xcframework — large churn across ~9 existing unit tests and the cross-project path-relocation logic they cover. Branching on layout keeps the proven flat path (and its tests) byte-for-byte unchanged and touches only the nested case.

Validation

Built a faithful two-module fixture — NestedObjC / NestedObjCKit (static .a Objective-C xcframeworks with nested, prefix-self-importing headers, where NestedObjCKit imports NestedObjC, mirroring ARCore Geospatial → GARSession), linked by a dynamic Library that is cached and consumed by Tool. Run with real tuist binaries:

tuist result
4.200.5 'NestedObjC/TrackingState.h' file not found
4.201.0-rc.3 (#11506) import of shadowed module 'Anchor' → can’t build NestedObjCKit
this PR BUILD SUCCEEDED
  • StaticXCFrameworkModuleMapGraphMapperTests: 18/18 pass (new nested test + all existing).
  • End-to-end with a tuist built from this branch: tuist cache Librarytuist generate Toolxcodebuild build Tool succeeds.

Impact / follow-up

Unblocks customers caching ARCore (and similarly-shaped static ObjC xcframeworks with nested prefix-self-importing headers). 4.201.0-rc.3 ships the broken (#11506) fix and should not be used — this needs to be backported to releases/4.201.x and a fresh RC (rc.4) cut.

How to test locally

cd examples/xcode/generated_macos_tool_with_cached_nested_header_xcframework
tuist cache Library
tuist generate Tool --no-open
xcodebuild build -workspace NestedHeaderXCFramework.xcworkspace -scheme Tool -destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""

(NestedObjCSources/build.sh regenerates the prebuilt xcframeworks.)

🤖 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