fix(iOS): opt spm_dependency pod targets out of Swift explicit modules#57589
Closed
chrfalch wants to merge 1 commit into
Closed
fix(iOS): opt spm_dependency pod targets out of Swift explicit modules#57589chrfalch wants to merge 1 commit into
chrfalch wants to merge 1 commit into
Conversation
A pod target consuming an SPM binary target via spm_dependency sees the binary target's module.modulemap twice: Xcode processes the xcframework into both the platform-wide products dir and the pod's TARGET_BUILD_DIR, and the existing SWIFT_INCLUDE_PATHS workaround makes the former visible. Implicit modules tolerate the duplicate definition; the explicit-modules dependency scanner (the default starting with Xcode 26) fails with "redefinition of module". Before the VFS-overlay removal these targets carried -ivfsoverlay, which disqualified them from explicit modules and masked the problem; the overlay's removal made them eligible and the nightly-tests react-native-enriched-markdown (RaTeX) job went red. Set SWIFT_ENABLE_EXPLICIT_MODULES=NO on exactly the targets spm_dependency touches, restoring the previous compile mode for the CocoaPods+SPM interop path only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chrfalch
force-pushed
the
chrfalch/spm-explicit-modules-opt-out
branch
from
July 17, 2026 15:03
c13a030 to
e5908b4
Compare
Collaborator
Author
|
Closing in favor of #57602. Further investigation on Xcode 26.3 (the nightly-tests runner version) showed the explicit-modules opt-out is not sufficient: 26.3's clang rejects the duplicate RaTeXFFI module map under implicit modules as well, and the failing nightly already contains the equivalent podspec-side opt-out (software-mansion/react-native-enriched-markdown#546). The duplicate itself is unavoidable via search paths — the package swiftmodule serializes the products-root include path — so #57602 removes the duplication at its source by building spm_dependency pod targets into the shared products dir. |
meta-codesync Bot
pushed a commit
that referenced
this pull request
Jul 20, 2026
…ods into the shared products dir (#57602) Summary: The nightly-tests job `[ios] react-native-enriched-markdown` fails on Xcode 26.3 (the CI runner version) with `error: redefinition of module 'RaTeXFFI'`. The library declares an `spm_dependency` on a Swift package (RaTeX) containing a C-family target, and Xcode emits the package's generated `include/module.modulemap` **twice**: into the shared products dir and into the pod's own `CONFIGURATION_BUILD_DIR`. Both copies are unavoidably visible to the pod's compile: - the package's `.swiftmodule` **serializes** the products-root include path in its recorded clang args and replays it on import, and - Xcode's built-in `-I$(BUILT_PRODUCTS_DIR)/include` exposes the per-pod copy. Clang parses two files defining the same module and hard-errors on Xcode 26.3 (26.5+ tolerates the duplicate). Because the root path is serialized inside the swiftmodule, no search-path change can hide it — and the `SWIFT_ENABLE_EXPLICIT_MODULES=NO` opt-out (#57589, and the equivalent podspec-side opt-out already shipped in react-native-enriched-markdown) is confirmed insufficient: the failing nightly contains it. **Fix**: build static-library `spm_dependency` pod targets straight into the shared products dir (`CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}`), so both module-map references resolve to the *same file* (clang dedupes same-path module maps). This also places the package swiftmodule on the pod's default search path, which replaces the old `SWIFT_INCLUDE_PATHS` "Swift package not found" workaround for these targets. The aggregate xcconfigs' `-fmodule-map-file` references are rewritten to the flattened location by mutating the shared in-memory `Config` objects (later `post_install` steps re-save those same objects, so a plain file edit would be clobbered). `USE_FRAMEWORKS=dynamic` keeps the previous behavior unchanged. Supersedes #57589. ## Changelog: [IOS] [FIXED] - Fix "redefinition of module" build failure on Xcode 26.3 for pods using spm_dependency with prebuilt React Native core Pull Request resolved: #57602 Test Plan: All on a fresh RN-nightly app with `react-native-enriched-markdown` under `RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1` (the nightly-tests configuration): - Red baseline reproduced CI exactly on Xcode 26.3 (13× `redefinition of module 'RaTeXFFI'`). - With this change (plain `pod install`, clean DerivedData): **BUILD SUCCEEDED on Xcode 26.3, 26.6, and 27.0 beta 3**, Debug and Release, zero module errors. - `USE_FRAMEWORKS=dynamic`: unchanged behavior (legacy code path taken, verified via pod install log). - Regression net without any `spm_dependency` pod (change must be inert): helloworld × {prebuilt, source} × {static, dynamic} × Debug — 4/4 build + install + launch PASS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cortinico Differential Revision: D112796678 Pulled By: cipolleschi fbshipit-source-id: f6e988fae8954b3b245180e6a66d600a6406a75e
react-native-bot
pushed a commit
that referenced
this pull request
Jul 20, 2026
…ods into the shared products dir (#57602) Summary: The nightly-tests job `[ios] react-native-enriched-markdown` fails on Xcode 26.3 (the CI runner version) with `error: redefinition of module 'RaTeXFFI'`. The library declares an `spm_dependency` on a Swift package (RaTeX) containing a C-family target, and Xcode emits the package's generated `include/module.modulemap` **twice**: into the shared products dir and into the pod's own `CONFIGURATION_BUILD_DIR`. Both copies are unavoidably visible to the pod's compile: - the package's `.swiftmodule` **serializes** the products-root include path in its recorded clang args and replays it on import, and - Xcode's built-in `-I$(BUILT_PRODUCTS_DIR)/include` exposes the per-pod copy. Clang parses two files defining the same module and hard-errors on Xcode 26.3 (26.5+ tolerates the duplicate). Because the root path is serialized inside the swiftmodule, no search-path change can hide it — and the `SWIFT_ENABLE_EXPLICIT_MODULES=NO` opt-out (#57589, and the equivalent podspec-side opt-out already shipped in react-native-enriched-markdown) is confirmed insufficient: the failing nightly contains it. **Fix**: build static-library `spm_dependency` pod targets straight into the shared products dir (`CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}`), so both module-map references resolve to the *same file* (clang dedupes same-path module maps). This also places the package swiftmodule on the pod's default search path, which replaces the old `SWIFT_INCLUDE_PATHS` "Swift package not found" workaround for these targets. The aggregate xcconfigs' `-fmodule-map-file` references are rewritten to the flattened location by mutating the shared in-memory `Config` objects (later `post_install` steps re-save those same objects, so a plain file edit would be clobbered). `USE_FRAMEWORKS=dynamic` keeps the previous behavior unchanged. Supersedes #57589. ## Changelog: [IOS] [FIXED] - Fix "redefinition of module" build failure on Xcode 26.3 for pods using spm_dependency with prebuilt React Native core Pull Request resolved: #57602 Test Plan: All on a fresh RN-nightly app with `react-native-enriched-markdown` under `RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1` (the nightly-tests configuration): - Red baseline reproduced CI exactly on Xcode 26.3 (13× `redefinition of module 'RaTeXFFI'`). - With this change (plain `pod install`, clean DerivedData): **BUILD SUCCEEDED on Xcode 26.3, 26.6, and 27.0 beta 3**, Debug and Release, zero module errors. - `USE_FRAMEWORKS=dynamic`: unchanged behavior (legacy code path taken, verified via pod install log). - Regression net without any `spm_dependency` pod (change must be inert): helloworld × {prebuilt, source} × {static, dynamic} × Debug — 4/4 build + install + launch PASS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: cortinico Differential Revision: D112796678 Pulled By: cipolleschi fbshipit-source-id: f6e988fae8954b3b245180e6a66d600a6406a75e
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(iOS): opt spm_dependency pod targets out of Swift explicit modules
Summary
After the VFS-overlay removal landed (#57285 / #57332 stack), the nightly-tests job for
react-native-enriched-markdown(iOS) started failing on0.88.0-nightly-20260717with:Root cause
A pod target consuming an SPM binary target via
spm_dependencysees that target'smodule.modulemaptwice:ProcessXCFrameworkmaterializes the xcframework into both the platform-wide products dir (for the SPM package target) and the pod's ownTARGET_BUILD_DIR(for the pod target).spm.rb's existing "Swift package not found" workaround adds the platform-wide products dir to the pod target'sSWIFT_INCLUDE_PATHS, making the second copy visible.This duplication has always existed and is benign under implicit modules. Before this stack, every pod consuming the prebuilt core carried
-Xcc -ivfsoverlay React-VFS.yaml, which disqualified those targets from Xcode 26's default explicit modules mode — so they silently fell back to implicit modules. The VFS removal (an explicit goal of the stack) made these targets eligible for explicit module builds, whose dependency scanner loads every reachable module map eagerly and hard-errors on the duplicate definition.Verified by diffing the same target's
SwiftDriverinvocation between the last green nightly (07-15:-ivfsoverlay, no-explicit-module-build) and the first red one (07-17:-fmodule-map-file,-explicit-module-build).Fix
Set
SWIFT_ENABLE_EXPLICIT_MODULES = NOon exactly the pod targetsspm_dependencytouches — the same targets that get theSWIFT_INCLUDE_PATHSworkaround that makes the duplicate visible. This restores the pre-stack compile mode for the CocoaPods+SPM interop path only; explicit modules stay enabled everywhere else.Changelog:
[IOS] [FIXED] - Fix "redefinition of module" errors for libraries using
spm_dependencywith binary SPM targets under Xcode 26 explicit modulesTest Plan
swiftc, two search dirs shipping the samemodule.modulemap):-explicit-module-build→ hardredefinition of modulescanner error (the CI failure shape); implicit modules → diagnostic printed but exit 0. This is the tolerated-vs-fatal boundary the fix moves the target across.react-native@0.88.0-nightly-20260717-b33de750b+react-native-enriched-markdown@0.8.0-nightly-20260716withRCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1:spm.rb: pod target compiles with-explicit-module-build(the CI failure precondition; the CI redefinition itself is Xcode-26.3-specific — 26.6's scanner changed modulemap lookup, so the error needs 26.3 to fire byte-for-byte).spm.rb:SWIFT_ENABLE_EXPLICIT_MODULES = NOlands in both configurations of the pod target, its SwiftDriver invocation no longer contains-explicit-module-build, and the build succeeds with the library's own dedup script disabled and both duplicate module maps forced present — proving the fix suffices on its own.