feat(ios): Default to consuming sentry-cocoa as an xcframework#6381
feat(ios): Default to consuming sentry-cocoa as an xcframework#6381alwx wants to merge 21 commits into
Conversation
On React Native >= 0.75, RNSentry now pulls Sentry as a pre-built binary xcframework through the SPM helper exposed by `react_native_pods.rb`, instead of building Sentry from source as a CocoaPod. On older RN versions the SPM helper is unavailable and RNSentry transparently falls back to the CocoaPods source build. Override the choice with `SENTRY_USE_SPM=0` (force CocoaPods) or `SENTRY_USE_SPM=1` (require SPM; errors if the helper is unavailable). Sample-application CI gets one extra `sentry-consumption: cocoapods` job on the production iOS build to keep the fallback path covered. Clang-format script gets a `DerivedData/**` exclusion so the SPM-fetched xcframework headers don't appear in the lint scope. Refs: #5780 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog.
🤖 This preview updates automatically when you update the PR. |
|
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c8143aa. Configure here.
|
It would probably be nice to include it to the next release but no stress |
📲 Install BuildsAndroid
|
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 0d9949d+dirty | 414.88 ms | 428.68 ms | 13.81 ms |
| a736b76+dirty | 405.78 ms | 458.74 ms | 52.96 ms |
| 5569641+dirty | 465.92 ms | 532.22 ms | 66.30 ms |
| 580fb5c+dirty | 515.72 ms | 585.38 ms | 69.66 ms |
| 7ff4d0f+dirty | 403.38 ms | 427.06 ms | 23.68 ms |
| 6177334+dirty | 404.80 ms | 456.74 ms | 51.94 ms |
| 1e5d96d+dirty | 423.33 ms | 482.46 ms | 59.13 ms |
| f170ec3+dirty | 505.96 ms | 551.88 ms | 45.92 ms |
| 20fbd51+dirty | 594.38 ms | 655.35 ms | 60.97 ms |
| f3215d3+dirty | 396.53 ms | 436.66 ms | 40.13 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 0d9949d+dirty | 43.94 MiB | 48.99 MiB | 5.05 MiB |
| a736b76+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 5569641+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 580fb5c+dirty | 49.74 MiB | 54.79 MiB | 5.05 MiB |
| 7ff4d0f+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 6177334+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| 1e5d96d+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| f170ec3+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| 20fbd51+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| f3215d3+dirty | 48.30 MiB | 53.49 MiB | 5.19 MiB |
… hatch Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bc0d8cf+dirty | 412.37 ms | 466.26 ms | 53.89 ms |
| c823bb5+dirty | 409.87 ms | 478.57 ms | 68.70 ms |
| 774257e+dirty | 407.09 ms | 447.46 ms | 40.37 ms |
| ae37560+dirty | 470.40 ms | 564.12 ms | 93.72 ms |
| a3265b6+dirty | 406.86 ms | 449.84 ms | 42.98 ms |
| 5125c43+dirty | 497.18 ms | 543.78 ms | 46.60 ms |
| 1e5d96d+dirty | 519.43 ms | 543.62 ms | 24.19 ms |
| 94af3bd+dirty | 503.48 ms | 542.37 ms | 38.89 ms |
| 3ce5254+dirty | 410.57 ms | 448.48 ms | 37.91 ms |
| 68672fc+dirty | 425.02 ms | 487.56 ms | 62.54 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bc0d8cf+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| c823bb5+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 774257e+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| ae37560+dirty | 48.30 MiB | 53.60 MiB | 5.29 MiB |
| a3265b6+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 5125c43+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 1e5d96d+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
| 94af3bd+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| 3ce5254+dirty | 43.75 MiB | 48.12 MiB | 4.37 MiB |
| 68672fc+dirty | 48.30 MiB | 53.61 MiB | 5.31 MiB |
…ollision The static `Sentry` product ships a signed xcframework whose `Signatures/Sentry.xcframework-ios.signature` file collides with an existing entry during `xcodebuild archive` on Xcode 16/26, failing the archive step. Switching to `Sentry-Dynamic` (dynamic xcframework) avoids the copy path that produces the duplicate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
antonis
left a comment
There was a problem hiding this comment.
Since the dynamic approach didn't resolve the CI build issue and this partially a breaking change for the users I suggest to try the following:
- Keep SPM opt-in till the React Native framework supports it
- Have RNSentry.podspec consume sentry-cocoa as a vendored_frameworks xcframework downloaded from sentry-cocoa's GitHub releases, instead of either s.dependency 'Sentry' (CocoaPods trunk) or SPM.dependency (spm.rb bridge).
Wdyt?
…lision The binary SPM products (`Sentry`, `Sentry-Dynamic`, ...) ship signed xcframeworks whose `Signatures/*.signature` file collides during `xcodebuild archive` on Xcode 16/26. Switching to the `SentrySPM` source-compile product (backed by the `SentryObjCInternal` SPM target) avoids the xcframework path entirely — SPM compiles sentry-cocoa from source, so there is no `Signatures/` directory to duplicate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ture collision" `SentrySPM` doesn't expose the `Sentry` module the way the binary xcframework product does — RNSentry.mm fails to resolve `#import <Sentry/SentryDefines.h>`, breaking every iOS build. Back to `Sentry-Dynamic` (archive-only failure) while we pick a real fix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Consuming sentry-cocoa through Xcode's SPM integration triggers a signature-copy collision during `xcodebuild archive` on Xcode 16/26: xcodebuild: error: "Sentry-Dynamic.xcframework-ios.signature" couldn't be copied to "Signatures" because an item with the same name already exists.: File exists The collision is specific to Xcode's SPM archive pipeline. Consuming the same prebuilt xcframework through CocoaPods' `vendored_frameworks` avoids that pipeline entirely — the archive step goes through the regular CocoaPods embed path, which does not produce the duplicate signature copy. RNSentry.podspec now downloads `Sentry-Dynamic.xcframework.zip` from sentry-cocoa's GitHub release on first `pod install`, verifies the SHA256 checksum against a version-pinned table in `sentry_utils.rb`, and vendors the extracted xcframework via `s.vendored_frameworks`. Subsequent `pod install` runs reuse the cached copy under `packages/core/ios/Vendor/`. Set `SENTRY_USE_XCFRAMEWORK=0` to fall back to the source-built `Sentry` CocoaPod (e.g. for offline builds behind a restrictive proxy). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…odule Sentry-Dynamic.xcframework contains a framework named `Sentry.framework` inside, so `@import Sentry;` needs `-framework Sentry`. CocoaPods derives the `-framework` flag from the xcframework's own filename, so with `Sentry-Dynamic.xcframework` it generates `-framework Sentry-Dynamic` and RNSentry fails to compile with `Module 'Sentry' not found`. Switch to `Sentry.xcframework` (the static product) — enclosing name matches the framework name inside, so CocoaPods generates the correct `-framework Sentry` and the module resolves. Static also means no embed step, so no chance of the archive-signature collision either. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Sentinel-check `Sentry.xcframework/Info.plist` instead of directory existence so an interrupted `unzip` doesn't leave a partial tree that short-circuits future re-downloads (cursor-bot low). - `rm_rf(target_dir)` before extracting so a stale partial tree from an earlier failure doesn't survive the next attempt. - Verify `Info.plist` after extraction so a release with a changed archive layout fails fast with a clear message instead of silently succeeding and later confusing `pod install` (sentry-bot medium). - Honor `SENTRY_USE_SPM=0` as a deprecated alias for the CocoaPods fallback so CI/local envs still exporting the old draft variable don't silently opt into the new xcframework path (cursor-bot medium). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ramework Even with `s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework'`, CocoaPods didn't propagate a framework search path into RNSentry's own compile phase, so `#import <Sentry/SentryDefines.h>` failed with "file not found". Set `FRAMEWORK_SEARCH_PATHS` explicitly via `pod_target_xcconfig` so the pod can resolve the `Sentry` framework module from the vendored xcframework's parent directory. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`s.pod_target_xcconfig` is setter-only in the CocoaPods version used by RN 0.86, so calling `s.pod_target_xcconfig.merge!(…)` crashed pod install with `undefined method 'pod_target_xcconfig' for Pod::Specification`. Accumulate the settings into a local hash across the conditional branches and assign to `s.pod_target_xcconfig` once at the end. Same fix applied to the pre-existing new-arch/RN<0.71 branch (which had the same latent bug). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Assigning `s.pod_target_xcconfig` at the end of the podspec (after `install_modules_dependencies`) clobbered every setting RN's helper had written, so the RN-provided HEADER_SEARCH_PATHS were dropped and every `#import <Sentry/…>` (as well as any other framework header lookup) failed during RNSentry's compile phase. Assign the base `pod_target_xcconfig` (with DEFINES_MODULE and the Sentry FRAMEWORK_SEARCH_PATHS) BEFORE `install_modules_dependencies` so it merges its RN settings on top rather than the other way around. For the RN < 0.71 branch — where `install_modules_dependencies` doesn't exist — the local hash is mutated after that assignment, so re-assign inside that branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…H_PATHS Confirmed by local repro: Xcode's `-F <dir>` does NOT descend into an `.xcframework` bundle at search-path lookup time. Given `FRAMEWORK_SEARCH_PATHS = ".../ios/Vendor"`, Xcode only sees `Sentry.xcframework` as an opaque directory and never finds `Sentry.framework` inside — so `#import <Sentry/SentryDefines.h>` fails with `Module 'Sentry' not found`. Instead of a single parent-of-xcframework path, enumerate every slice directory inside the extracted xcframework (via `Dir.children` — no plist gem needed) and register each as a framework search path. Xcode then picks the matching slice based on the target platform/arch. The list is computed at pod-install time, so future sentry-cocoa releases can add new slices without a code change. Apply the same paths to `s.user_target_xcconfig` so the host app target (any user code writing `#import <Sentry/…>` directly) also resolves the module. Verified locally: RNSentry pod and the sample app both compile — only the RN bundle script (unrelated to Sentry) blocks the local build. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two problems with the previous flat-slice-list approach: 1. Xcode's Swift module precompiler walked slices that don't match the current build target (e.g. watchos-* on an iOS build) and errored with 'unsupported Swift architecture'. Fix: split slices by target SDK and emit conditional FRAMEWORK_SEARCH_PATHS[sdk=...*] entries so each SDK build only sees the slices it can actually consume. 2. PODS_TARGET_SRCROOT is defined in a pod's own xcconfig but not in the aggregate/user-target xcconfig. Using it in user_target_xcconfig made the app-target search paths resolve to nothing, so app code writing @import Sentry failed with 'Module Sentry not found'. Use PODS_ROOT/../.. for the user-target xcconfig - from the app's Pods dir that's the RN app root where node_modules/@sentry/react-native/ lives. Slice-to-SDK mapping is computed at pod-install time from the extracted xcframework so future sentry-cocoa releases pick up new slices automatically. Verified locally: sample's SentryNativeInitializer.m now resolves @import Sentry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1bbd6f7. Configure here.
Two follow-up fixes for the vendored xcframework path:
1. RNSentryCocoaTester (in packages/core/RNSentryCocoaTester/) had its
test target fail with 'Sentry/Sentry.h' file not found because its
Podfile layout is different from the RN sample apps — the previous
${PODS_ROOT}/../.. path assumed a RN app root with node_modules,
which doesn't apply here. Switch user_target_xcconfig to the
absolute path known at pod-install time (via File.expand_path on
the podspec dir). Absolute paths are baked into Pods xcconfigs on
every install, so shareability across machines isn't a concern —
Pods/ is regenerated per install anyway. This also removes the need
for a separate 'pod_target' vs 'user_target' path.
2. Build RN 0.86.0 legacy hermes ios production dynamic (use_frameworks!
:linkage => :dynamic mode) failed with:
Undefined symbols: __swift_FORCE_LOAD_$_swiftCompatibility56
Sentry.xcframework contains Swift code, but RNSentry.podspec's
source_files was Objective-C++ only, so Xcode never auto-linked
the Swift runtime compatibility libraries required by the static
Swift symbols embedded in Sentry.xcframework. Add an empty
RNSentrySwiftLinkStub.swift and include `.swift` in source_files
glob so Xcode treats RNSentry as Swift-consuming and pulls in the
compat libs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adding a Swift file to RNSentry.podspec's source_files (to force Xcode to link Swift runtime compat libs when consuming Sentry.xcframework) made CocoaPods treat RNSentry as a 'Swift pod'. On RN < 0.75, React-hermes doesn't define modular headers, so pod install fails: The Swift pod `RNSentry` depends upon `React-hermes`, which does not define modules. The Swift compat lib linkage is only needed when linking Sentry.xcframework's static Swift symbols into a dynamic framework (use_frameworks! :dynamic), which is a RN 0.86+ scenario anyway. Gate the .swift file on RN >= 0.75 to preserve pod install on the older matrix cells. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 3834.15 ms | 1218.43 ms | -2615.73 ms |
| 882f8ae+dirty | 3840.30 ms | 1224.41 ms | -2615.88 ms |
| 15d4514+dirty | 3840.17 ms | 1225.79 ms | -2614.38 ms |
| 0b5a379+dirty | 3828.91 ms | 1214.12 ms | -2614.79 ms |
| f3215d3+dirty | 3842.73 ms | 1219.33 ms | -2623.40 ms |
| ab203f9+dirty | 3848.07 ms | 1219.71 ms | -2628.35 ms |
| 6177334+dirty | 3834.85 ms | 1217.58 ms | -2617.28 ms |
| 5257d80+dirty | 3854.39 ms | 1234.28 ms | -2620.11 ms |
| 9474ead+dirty | 3864.29 ms | 1223.55 ms | -2640.74 ms |
| 1122a96+dirty | 3823.10 ms | 1218.64 ms | -2604.46 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 21a1e70+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 882f8ae+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 15d4514+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 0b5a379+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
| ab203f9+dirty | 4.98 MiB | 6.51 MiB | 1.53 MiB |
| 6177334+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 5257d80+dirty | 5.15 MiB | 6.69 MiB | 1.54 MiB |
| 9474ead+dirty | 5.15 MiB | 6.71 MiB | 1.55 MiB |
| 1122a96+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| eb93136+dirty | 3846.51 ms | 1226.13 ms | -2620.39 ms |
| ad66da3+dirty | 3855.02 ms | 1213.43 ms | -2641.59 ms |
| b04af96+dirty | 3830.54 ms | 1206.11 ms | -2624.44 ms |
| 9210ae6+dirty | 3834.11 ms | 1216.64 ms | -2617.47 ms |
| ca9d079+dirty | 3818.62 ms | 1216.72 ms | -2601.90 ms |
| 09a902f+dirty | 3847.65 ms | 1221.31 ms | -2626.34 ms |
| 5a21b51+dirty | 3837.87 ms | 1223.47 ms | -2614.40 ms |
| d038a14+dirty | 3831.11 ms | 1216.30 ms | -2614.81 ms |
| 64630e5+dirty | 3845.49 ms | 1215.19 ms | -2630.30 ms |
| f3215d3+dirty | 3846.08 ms | 1231.85 ms | -2614.23 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| eb93136+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| ad66da3+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| b04af96+dirty | 4.98 MiB | 6.54 MiB | 1.56 MiB |
| 9210ae6+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| ca9d079+dirty | 5.15 MiB | 6.69 MiB | 1.53 MiB |
| 09a902f+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| 5a21b51+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| d038a14+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 64630e5+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| f3215d3+dirty | 5.15 MiB | 6.67 MiB | 1.52 MiB |
- Move the SPM/xcframework entry out of the already-released `## 8.17.0` section into a new `## Unreleased` section (danger check). The merge from main pulled 8.17.0's release notes on top and swept the entry inside them. - Add a private constant to RNSentrySwiftLinkStub.swift. A pure-comment Swift file compiles to nothing, which would defeat the whole reason for the stub (forcing Xcode to link Swift runtime compatibility libs when consuming Sentry.xcframework's static Swift symbols). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

📢 Type of change
📜 Description
Vendor
sentry-cocoaas a prebuiltSentry-Dynamic.xcframework(downloaded from the GitHub release and SHA256-verified atpod installtime), instead of buildingSentryfrom source as a CocoaPod. Sidesteps the Xcode 16/26 archive bug that hits when the same xcframework is consumed through Xcode's SPM integration ("Sentry-Dynamic.xcframework-ios.signature" couldn't be copied to "Signatures"…).Override with:
SENTRY_USE_XCFRAMEWORK=0— fall back to the source-builtSentryCocoaPod (e.g. offline builds behind a restrictive proxy).💡 Motivation and Context
CocoaPods source builds of
sentry-cocoaare slow and diverge from where the RN community is moving (prebuilt binaries). SPM was the first attempt, but Xcode 16/26 archives fail with a duplicate-signature error on any signed SPM binary xcframework. Consuming the same xcframework through CocoaPods'vendored_frameworksavoids the Xcode-SPM archive pipeline and works on the full RN version range.💚 How did you test it?
CI: sample-application (iOS matrix, both
xcframeworkdefault andcocoapodsfallback), iOS Size Analysis (archive), metrics (fastlane archive).📝 Checklist
sendDefaultPIIis enabled🔮 Next steps