Hive Hive
Sign in

fix(cli): avoid static framework module map copies

GitHub issue · Closed

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

What changed

  • Stop copying module maps into static framework products while keeping dynamic framework behavior unchanged.
  • Add mapper coverage for static frameworks that verifies MODULEMAP_FILE is removed without adding a copy script.
  • Add an iOS acceptance fixture where a static ObjC framework target depends on an external ObjC Swift package product with publicHeadersPath: ".", then archive that fixture end to end.

Why

A reported archive topology surfaced a separate module-map duplication failure after the static xcframework header fix. The regression was introduced by #11135 (3069e881b24), which added module-map copying for framework products to support package documentation module imports, but used target.product.isFramework and therefore also applied the dynamic-framework copy behavior to static frameworks.

Source Swift package products represented as ObjC static frameworks already expose their module map to dependent targets through Tuist’s generated dependency module map. The mapper also copied that same module map into Framework.framework/Modules/module.modulemap because static frameworks satisfied target.product.isFramework.

When a dependent ObjC static framework imported a prefixed header such as <ObjCPlayerSupport/PlayerView.h>, clang could discover both module maps for the same umbrella header directory and failed the archive with:

Umbrella for module 'ObjCPlayerSupport' already covers this directory

Approach

Dynamic frameworks still need the canonical Framework.framework/Modules/module.modulemap layout, so that path remains unchanged. Static frameworks do not need that product-level module map copy, and copying it creates a second module-map entry point for the same headers. The mapper now gates the copy build phase on target.product == .framework instead of the broader target.product.isFramework.

The acceptance fixture mirrors the failing shape using a local ObjC package named ObjCPlayerSupport and a static framework target that imports its public header with the framework prefix. The test archives the app and asserts the static framework product does not contain a copied Modules/module.modulemap.

Validation

  • Reproduced the old behavior end to end by re-adding the previous static-framework module-map copy script after generation; xcodebuild archive failed with the exact umbrella-duplication diagnostic above.
  • Validated the fixed generated project end to end with xcodebuild archive; the archive succeeded.
  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistGeneratorAcceptanceTests -only-testing 'TuistGeneratorAcceptanceTests/GenerateAcceptanceTestiOSAppWithObjCStaticFrameworkPackage/ios_app_with_objc_static_framework_package()' ... passed.
  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistUnitTests -only-testing 'TuistGeneratorTests/ModuleMapMapperTests/removes_static_framework_modulemap_without_copy_script()' ... passed.
  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistUnitTests -only-testing 'TuistGeneratorTests/ModuleMapMapperTests/maps_framework_modulemap_to_modulemap_copy_script()' -only-testing 'TuistGeneratorTests/ModuleMapMapperTests/removes_static_framework_modulemap_without_copy_script()' ... passed, covering both the original #11135 dynamic-framework behavior and the new static-framework guard.
  • git diff --check passed for the changed files and fixture.
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
E
esnunes Jul 1, 2026

Reviewed the static-framework module-map fix. The core change is correct and well-scoped: isFramework covers both .framework and .staticFramework, so narrowing to == .framework cleanly drops the harmful copy script for static frameworks — which would otherwise redefine the clang module during xcodebuild archive — while preserving dynamic-framework behavior, independently pinned by the existing maps_framework_modulemap_to_modulemap_copy_script test. Module resolution is unaffected; dependents still receive -fmodule-map-file.