Repository navigation
Conversation
Merged
4 of 8 tasks
mfazekas
marked this pull request as ready for review
October 19, 2026 09:37
cipolleschi
approved these changes
Oct 20, 2026
cipolleschi
left a comment
Contributor
There was a problem hiding this comment.
thanks for fixing this
|
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D126299058. |
3 of 4 tasks
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? |
|
@cipolleschi merged this pull request in 87c3e8f. |
3 of 4 tasks
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).
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.
Summary:
Since #57602, a static pod that declares
spm_dependencyis built into the shared products dir, andrewrite_aggregate_modulemap_referencesrewrites the aggregate-fmodule-map-fileflags from<Pod>/<Pod>.modulemapto<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(podrnmapbox-maps, modulernmapbox_maps) the flag keeps pointing atrnmapbox-maps/rnmapbox_maps.modulemap, which no longer exists, and the app fails to build on 0.87, 0.88 and current nightlies: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_dependencypods whose module name differs from the pod nameTest Plan:
test_module_map_references_use_the_module_name_of_pods_built_into_the_shared_products_dirtoscripts/cocoapods/__tests__/spm-test.rb. It fails before the change (thernmapbox-maps/rnmapbox_maps.modulemapreference is left as is) and passes after;ruby spm-test.rb: 9 tests, 25 assertions, 0 failures.react-native@0.89.0-nightly-20261018-27e77216eand@rnmapbox/mapswith its Swift Package Manager opt-in ($RNMapboxMapsSwiftPackageManager = true). Before:pod installwrites-fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/rnmapbox-maps/rnmapbox_maps.modulemap"intoPods-App.debug.xcconfigand the build fails with the error above. After applying this patch to the app'snode_modules/react-native: the flag is${PODS_CONFIGURATION_BUILD_DIR}/rnmapbox_maps.modulemapand 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.