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.