Skip to content

fix(iOS): opt spm_dependency pod targets out of Swift explicit modules#57589

Closed
chrfalch wants to merge 1 commit into
mainfrom
chrfalch/spm-explicit-modules-opt-out
Closed

fix(iOS): opt spm_dependency pod targets out of Swift explicit modules#57589
chrfalch wants to merge 1 commit into
mainfrom
chrfalch/spm-explicit-modules-opt-out

Conversation

@chrfalch

@chrfalch chrfalch commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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 on 0.88.0-nightly-20260717 with:

error: Clang dependency scanner failure:
  …/Debug-iphonesimulator/include/module.modulemap:1:8: error: redefinition of module 'RaTeXFFI'
  …/Debug-iphonesimulator/ReactNativeEnrichedMarkdown/include/module.modulemap:1:8: note: previously defined here

Root cause

A pod target consuming an SPM binary target via spm_dependency sees that target's module.modulemap twice:

  1. Xcode's ProcessXCFramework materializes the xcframework into both the platform-wide products dir (for the SPM package target) and the pod's own TARGET_BUILD_DIR (for the pod target).
  2. spm.rb's existing "Swift package not found" workaround adds the platform-wide products dir to the pod target's SWIFT_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 SwiftDriver invocation 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 = NO on exactly the pod targets spm_dependency touches — the same targets that get the SWIFT_INCLUDE_PATHS workaround 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_dependency with binary SPM targets under Xcode 26 explicit modules

Test Plan

  • Micro-repro (standalone swiftc, two search dirs shipping the same module.modulemap): -explicit-module-build → hard redefinition of module scanner 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.
  • E2E: fresh app on react-native@0.88.0-nightly-20260717-b33de750b + react-native-enriched-markdown@0.8.0-nightly-20260716 with RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1:
    • Unpatched 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).
    • Patched spm.rb: SWIFT_ENABLE_EXPLICIT_MODULES = NO lands 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.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 17, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jul 17, 2026
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

Copy link
Copy Markdown
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.

@chrfalch chrfalch closed this Jul 19, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant