Skip to content

fix(iOS): use the module name when rewriting module map paths of spm_dependency pods - #59182

Closed
mfazekas wants to merge 1 commit into
react:mainfrom
mfazekas:fix-spm-modulemap-module-name
Closed

mfazekas wants to merge 1 commit into
react:mainfrom
mfazekas:fix-spm-modulemap-module-name

Conversation

@mfazekas

Copy link
Copy Markdown
Contributor

Summary:

Since #57602, a static pod that declares spm_dependency is built into the shared products dir, and rewrite_aggregate_modulemap_references rewrites the aggregate -fmodule-map-file flags from <Pod>/<Pod>.modulemap to <Pod>.modulemap. CocoaPods names the module map after the pod's module, though (${PODS_CONFIGURATION_BUILD_DIR}/<label>/<product_module_name>.modulemap), and the module name differs from the pod name whenever the pod name isn't a C identifier. For @rnmapbox/maps (pod rnmapbox-maps, module rnmapbox_maps) the flag keeps pointing at rnmapbox-maps/rnmapbox_maps.modulemap, which no longer exists, and the app fails to build on 0.87, 0.88 and current nightlies:

error: module map file '${PODS_CONFIGURATION_BUILD_DIR}/rnmapbox-maps/rnmapbox_maps.modulemap' not found

This takes the module name from the aggregate target's pod targets instead of assuming it equals the pod name.

Changelog:

[IOS] [FIXED] - Fix "module map file not found" for spm_dependency pods whose module name differs from the pod name

Test Plan:

  1. Added test_module_map_references_use_the_module_name_of_pods_built_into_the_shared_products_dir to scripts/cocoapods/__tests__/spm-test.rb. It fails before the change (the rnmapbox-maps/rnmapbox_maps.modulemap reference is left as is) and passes after; ruby spm-test.rb: 9 tests, 25 assertions, 0 failures.
  2. Created an app with react-native@0.89.0-nightly-20261018-27e77216e and @rnmapbox/maps with its Swift Package Manager opt-in ($RNMapboxMapsSwiftPackageManager = true). Before: pod install writes -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/rnmapbox-maps/rnmapbox_maps.modulemap" into Pods-App.debug.xcconfig and the build fails with the error above. After applying this patch to the app's node_modules/react-native: the flag is ${PODS_CONFIGURATION_BUILD_DIR}/rnmapbox_maps.modulemap and the Debug build succeeds. With the equivalent path rewrite applied on the library side, the app also launches on the simulator and shows the map, and a Release archive succeeds.

@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 Oct 19, 2026
@mfazekas
mfazekas marked this pull request as ready for review October 19, 2026 09:37
@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 Oct 19, 2026

@cipolleschi cipolleschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for fixing this

@meta-codesync

meta-codesync Bot commented Oct 20, 2026

Copy link
Copy Markdown

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D126299058.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @mfazekas in 87c3e8f

When will my fix make it into a release? | How to file a pick request?

@meta-codesync meta-codesync Bot added the Merged This PR has been merged. label Oct 21, 2026
@react-native-bot react-native-bot added the Merged This PR has been merged. label Oct 21, 2026
@meta-codesync

meta-codesync Bot commented Oct 21, 2026

Copy link
Copy Markdown

@cipolleschi merged this pull request in 87c3e8f.

chrfalch added a commit to expo/expo that referenced this pull request Oct 22, 2026
…modules (#52017)

# Why

Fixes #51652.

With `useFrameworks: "dynamic"` and precompiled modules on, `pod
install` fails for apps that use `@stripe/stripe-react-native` 0.81.0.
Expo builds every pod that depends on React-Core as a static library.
Stripe gets its iOS SDK through React Native's `spm_dependency`, and its
own post-install check stops the install unless its pod is a dynamic
framework. Stripe's error message tells the user to turn on dynamic
frameworks, which they already did. Today the only workaround is to turn
off precompiled modules.

# How

Expo now records which pods call React Native's `spm_dependency`. With
dynamic linkage, it keeps those pods and all their dependencies out of
the static downgrade.

- **Recording.** React Native's `SPMManager` has no public reader for
these pods. `use_expo_modules!` prepends a small module on
`SPMManager#dependency`, which records the root pod name and calls
`super`. It does nothing if `SPMManager` is not loaded, and it does not
read React Native's private state.
- **Whole dependency graph.** CocoaPods rejects a dynamic pod with any
static recursive dependency
(`verify_no_static_framework_transitive_dependencies`). So the pod and
every one of its `recursive_dependent_targets` keep their linkage.
Keeping only the unbuilt dependencies failed validation on
`ReactCodegen`.
- **Core pods still win.** `framework_modules_to_patch`
(ExpoModulesCore, ExpoModulesJSI, Expo, ReactAppDependencyProvider,
expo-dev-menu, plus `forceStaticLinking`) is forced static again later
in `installer.rb`. If an `spm_dependency` pod's graph contains one of
these, Expo keeps today's result for that pod and prints which pod
blocks it. Such pods work today, because React Native's SPM support
handles static pods, so this avoids a regression.
- **Scope.** This applies only with `:dynamic` linkage. With no
`spm_dependency` pods, or with static linkage, the downgrade set is the
same as before.
- **Refactor for testing.** The downgrade decision moved into
`pods_to_downgrade(pod_targets, linkage:, framework_modules_to_patch:,
spm_dependency_pods:)`, so it can be tested with fake targets.
- **@react-native-firebase compatibility (second commit).**
`@react-native-firebase` 27.0.0 is also SPM-only and calls
`spm_dependency`. It has its own post-install workaround for precompiled
modules (`rnfirebase_restore_dynamic_linkage_after_expo_prebuilt!`).
That workaround calls `Pod::Target#build_type` on every RNFB pod, and
CocoaPods keeps that method private. It only worked because Expo's
downgrade defined a public `build_type` on each downgraded pod. With the
first commit alone, RNFB pods stay dynamic and `pod install` stops with
`NoMethodError: private method 'build_type'`. That would break Firebase
apps that install today. Expo now gives every pod target that it does
not downgrade a public `build_type` that returns the real value through
`super`. RNFB's workaround then sees its pods as dynamic and does
nothing.

Stripe has a related open PR, stripe/stripe-react-native#2757. It lets
Stripe accept a static pod, but it also needs react/react-native#59182,
which is not in RN 0.88.0. Stripe still accepts a dynamic pod, so the
two changes do not conflict.

In the Stripe app, the downgrade list goes from 85 pods to 25.
`ReactCodegen` is now a dynamic framework in that app. Stripe's SPM
products link statically into `stripe_react_native.framework`.

# Test Plan

**Unit tests.** New file:
`e2e/__tests__/precompiled-pods-to-downgrade-test.ts`, 10 tests. It runs
the real `precompiled_modules.rb` in Ruby with fake pod targets. Before
the first commit, 8 of 9 failed. The `build_type` test failed before the
second commit with the same private-method error that RNFB hits. All 10
pass on Ruby 3.3.6 and on the system Ruby 2.6.10. The reviewer also
removed parts of the fix on purpose: removing the core-pod check makes 2
tests fail, removing the dynamic-only check makes 1 fail, and removing
the public `build_type` makes 1 fail.

```
jest --no-watchman e2e/__tests__/precompiled-pods-to-downgrade-test.ts
Tests: 10 passed, 10 total
```

**Repro app.** The app comes from
https://github.com/glowcore/expo-58-stripe-spm-repro, with
`expo-updates` added. Versions: SDK 58, RN 0.88.0, Stripe 0.81.0,
CocoaPods 1.16.2, Xcode 27.1. The fix was applied to the app's
`node_modules`.

| Check | Without fix | With fix |
|---|---|---|
| `pod install` | fails: "requires dynamic frameworks, but
stripe-react-native is building as a static library" | passes, `Keeping
USE_FRAMEWORKS for ... (stripe-react-native)` |
| Debug simulator build | – | succeeds |
| Release simulator build (earlier version of the change) | – | succeeds
|
| Debug device build (`CODE_SIGNING_ALLOWED=NO`) | – | succeeds |
| Release archive (`CODE_SIGNING_ALLOWED=NO`) | – | succeeds.
`stripe_react_native.framework` is dynamic, and `otool -L` shows only
`@rpath`/system paths |
| Launch on the iOS simulator | – | renders. `StripeSdk` is present,
`initialise` resolves, `CardField` is registered. No dyld errors and no
duplicate classes |

The build, archive and launch rows ran before the second commit. With
the second commit, `pod install` passes again in this app.

**Second app: Stripe + Firebase + Sentry.** The same app with
`@react-native-firebase/app` and `/messaging` 27.0.0 (published code,
unchanged) and `@sentry/react-native` 8.28.0.

| Check | Result |
|---|---|
| `pod install`, no fix | fails: Stripe's dynamic-framework error |
| `pod install`, first commit only | fails: RNFB `NoMethodError: private
method 'build_type'` |
| `pod install`, both commits | passes, `Keeping USE_FRAMEWORKS for ...
(RNFBApp, RNFBMessaging, stripe-react-native)`. `RNSentry` stays static,
as before |
| Debug simulator build, both commits (earlier form of the second
commit) | succeeds |
| `pod install` with precompiled modules off | passes |

**Not tested:** running a Release build, running on a physical device,
launching the Firebase app, and CocoaPods 1.17.0.

**Local package suite.** 18 tests in `e2e/monorepo-test.ts` fail in my
worktree, because they need the package's `build/`, which is not there.
The change touches only Ruby and adds a test file.

# Checklist

- [x] I added a `changeset`
(`.changeset/autolinking-keep-spm-dependency-pods-dynamic.md`).
- [ ] `et check-packages` passes. Not run yet; see the note above.
- [x] This diff works with `npx expo prebuild` (tested in the repro
app).
- [x] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md).
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. Merged This PR has been merged. 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.

3 participants