Conversation
React Native 0.87 added an opt-in Swift Package Manager integration, and `npx react-native spm` refuses to set up an app whose autolinked library ships no `Package.swift`. Add one, so the SDK can be consumed that way. Three SwiftPM constraints shape the layout: * No mixed-language targets, so the Swift sources move to `ios/Swift/` and build as their own `RNSentrySwift` target. `.m` callers reach it through `ios/RNSentrySwiftBridge.h`, which picks the pod's or the package's generated header. * `@import` is rejected in Objective-C++ and enabling C++ modules breaks React Native's C++ headers, so `.mm` callers go through the new `RNSentryInternalWrapper`, a plain Objective-C forwarder. * No header maps, so the public headers are mirrored under `ios/include/RNSentry/` (the target's `publicHeadersPath`) to keep `#import <RNSentry/RNSentrySDK.h>` resolving. The podspec excludes the mirrors. The autolinked target name is pinned to `RNSentry` in both places React Native reads it, since the name derived from `@sentry/react-native` collides with React Native's reserved `ReactNative` and is also the prefix consumers import our headers under. CocoaPods is unaffected and stays the default.
|
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. |
`codegenConfig` declared no `ios.componentProvider`, so `RCTThirdPartyComponentsProvider` carried no entry for `RNSentryReplayMask` and `RNSentryReplayUnmask`. React Native then resolved them through the legacy view manager interop layer, which 0.87 lets an app turn off with `RCT_REMOVE_LEGACY_COMPONENT_INTEROP` — and without it the components fall back to `UnimplementedView`, which leaves nothing for sentry-cocoa to redact, since it masks by view class. Map both component names to their view classes, the same way other community libraries do.
Nothing covered the SwiftPM path, so an autolinking or manifest regression would only surface in a user's project. The app is generated per run from the React Native template instead of committed: `react-native spm` stops on any autolinked dependency that ships no `Package.swift`, and most community libraries still don't, so the existing sample app cannot take this path. The job packs the SDK with `yarn pack` (which resolves the `workspace:` ranges), installs the tarball like a user would, and asserts that both RNSentry and sentry-cocoa reach the app binary — a green build alone would not catch a dependency that silently dropped out of the graph. Also add `Package.swift` and `react-native.config.js` to the change filters, since both drive the iOS build.
sentry-cocoa's manifest declares a binary target per distribution
variant, and SwiftPM downloads every artifact of a resolved package, not
only the ones the selected product needs. Depending on the package for
the one variant we use therefore pulled all seven archives: 2.9 GB in
DerivedData for the 339 MB we need, and seven chances for a failed
download to break the build — CI hit a GitHub 500 on
`SentryObjC-Dynamic.xcframework.zip`, which the build never uses.
Declare the `Sentry.xcframework` archive as our own binary target, the
same archive and checksum `pod install` already verifies. That leaves
one download. `update-cocoa.sh` keeps the version and the checksum in
step with the podspec.
`.linkedLibrary("c++")` replaces `SentryCppHelper`, an empty target that
sentry-cocoa pairs with the binary target to carry exactly that setting.
Reported upstream as getsentry/sentry-cocoa#9146.
📲 Install BuildsAndroid
|
iOS (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2cca10e+dirty | 3857.27 ms | 1232.14 ms | -2625.13 ms |
| 7a7af85+dirty | 3865.35 ms | 1235.00 ms | -2630.35 ms |
| 9eb54c2+dirty | 3860.19 ms | 1234.41 ms | -2625.77 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2cca10e+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 7a7af85+dirty | 5.15 MiB | 6.92 MiB | 1.77 MiB |
| 9eb54c2+dirty | 5.15 MiB | 6.90 MiB | 1.75 MiB |
`@sentry/react-native` depends on `@sentry/expo-upload-sourcemaps` with a `workspace:` range, which `yarn pack` rewrites to the version being released. The job packed and installed the core tarball only, so npm fetched that dependency from the registry — and on a `release/**` branch the bumped version is not published yet, which fails with ETARGET before the build even starts. Pack both workspaces through `yarn build:tarball` and install both tarballs, the way `buildandtest.yml` already does. `build:tarball` also restores the executable bits that `yarn pack` drops. Reported by Warden.
Android (legacy) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a3265b6+dirty | 406.86 ms | 449.84 ms | 42.98 ms |
| 3a829f0+dirty | 474.65 ms | 518.24 ms | 43.59 ms |
| eb55864+dirty | 557.53 ms | 588.57 ms | 31.03 ms |
| 9c84b9a+dirty | 520.74 ms | 539.38 ms | 18.64 ms |
| 26843eb+dirty | 532.15 ms | 624.13 ms | 91.98 ms |
| a0a3177+dirty | 441.27 ms | 499.86 ms | 58.59 ms |
| 3d536d1+dirty | 524.34 ms | 547.32 ms | 22.98 ms |
| 7a89652+dirty | 537.76 ms | 567.84 ms | 30.08 ms |
| 5125c43+dirty | 497.18 ms | 543.78 ms | 46.60 ms |
| 3b6e9f9+dirty | 442.70 ms | 486.44 ms | 43.74 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a3265b6+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 3a829f0+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| eb55864+dirty | 50.56 MiB | 56.46 MiB | 5.90 MiB |
| 9c84b9a+dirty | 49.74 MiB | 55.36 MiB | 5.62 MiB |
| 26843eb+dirty | 49.74 MiB | 55.26 MiB | 5.52 MiB |
| a0a3177+dirty | 49.74 MiB | 55.37 MiB | 5.63 MiB |
| 3d536d1+dirty | 49.74 MiB | 55.26 MiB | 5.52 MiB |
| 7a89652+dirty | 48.30 MiB | 53.60 MiB | 5.30 MiB |
| 5125c43+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 3b6e9f9+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
Previous results on branch: alwx/feature/full-spm-support
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a7af85+dirty | 542.80 ms | 585.04 ms | 42.24 ms |
| 2cca10e+dirty | 434.78 ms | 457.98 ms | 23.20 ms |
| 9eb54c2+dirty | 421.29 ms | 435.85 ms | 14.56 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a7af85+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 2cca10e+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 9eb54c2+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
Android (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bf168a4+dirty | 430.60 ms | 459.31 ms | 28.71 ms |
| 0bd8916+dirty | 400.15 ms | 442.72 ms | 42.57 ms |
| a2585ce+dirty | 414.04 ms | 456.83 ms | 42.79 ms |
| 9c84b9a+dirty | 429.26 ms | 448.90 ms | 19.64 ms |
| bc0d8cf+dirty | 407.66 ms | 461.35 ms | 53.69 ms |
| a736b76+dirty | 405.78 ms | 458.74 ms | 52.96 ms |
| 1122a96+dirty | 510.16 ms | 542.00 ms | 31.84 ms |
| 267d3ed+dirty | 424.69 ms | 483.70 ms | 59.01 ms |
| 6177334+dirty | 404.80 ms | 456.74 ms | 51.94 ms |
| 7887847+dirty | 420.47 ms | 460.55 ms | 40.08 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| bf168a4+dirty | 49.74 MiB | 55.09 MiB | 5.35 MiB |
| 0bd8916+dirty | 48.30 MiB | 53.57 MiB | 5.26 MiB |
| a2585ce+dirty | 49.74 MiB | 55.36 MiB | 5.61 MiB |
| 9c84b9a+dirty | 49.74 MiB | 55.36 MiB | 5.62 MiB |
| bc0d8cf+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| a736b76+dirty | 48.30 MiB | 53.48 MiB | 5.18 MiB |
| 1122a96+dirty | 48.30 MiB | 53.54 MiB | 5.24 MiB |
| 267d3ed+dirty | 48.30 MiB | 53.58 MiB | 5.28 MiB |
| 6177334+dirty | 48.30 MiB | 53.54 MiB | 5.23 MiB |
| 7887847+dirty | 49.74 MiB | 54.81 MiB | 5.07 MiB |
Previous results on branch: alwx/feature/full-spm-support
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a7af85+dirty | 437.28 ms | 481.92 ms | 44.64 ms |
| 2cca10e+dirty | 466.67 ms | 512.09 ms | 45.42 ms |
| 9eb54c2+dirty | 423.14 ms | 456.60 ms | 33.46 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7a7af85+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 2cca10e+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
| 9eb54c2+dirty | 50.56 MiB | 56.51 MiB | 5.95 MiB |
iOS (new) Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7d6fd3a+dirty | 1210.89 ms | 1217.63 ms | 6.74 ms |
| 774257e+dirty | 3821.35 ms | 1211.96 ms | -2609.39 ms |
| 4e0b819+dirty | 3828.96 ms | 1205.64 ms | -2623.32 ms |
| d038a14+dirty | 3831.11 ms | 1216.30 ms | -2614.81 ms |
| 882f8ae+dirty | 3842.51 ms | 1230.40 ms | -2612.11 ms |
| 5125c43+dirty | 3827.94 ms | 1208.79 ms | -2619.15 ms |
| 15d4514+dirty | 3843.73 ms | 1228.09 ms | -2615.64 ms |
| 3d31fcf+dirty | 3857.46 ms | 1237.17 ms | -2620.29 ms |
| 4b87b12+dirty | 1199.49 ms | 1199.78 ms | 0.29 ms |
| 5c1e987+dirty | 1208.43 ms | 1220.72 ms | 12.29 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 7d6fd3a+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| 774257e+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 4e0b819+dirty | 4.98 MiB | 6.46 MiB | 1.49 MiB |
| d038a14+dirty | 5.15 MiB | 6.67 MiB | 1.51 MiB |
| 882f8ae+dirty | 5.15 MiB | 6.70 MiB | 1.54 MiB |
| 5125c43+dirty | 5.15 MiB | 6.68 MiB | 1.53 MiB |
| 15d4514+dirty | 5.15 MiB | 6.70 MiB | 1.55 MiB |
| 3d31fcf+dirty | 4.98 MiB | 6.56 MiB | 1.58 MiB |
| 4b87b12+dirty | 3.38 MiB | 4.77 MiB | 1.39 MiB |
| 5c1e987+dirty | 3.38 MiB | 4.73 MiB | 1.35 MiB |
Previous results on branch: alwx/feature/full-spm-support
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2cca10e+dirty | 3863.70 ms | 1235.02 ms | -2628.68 ms |
| 7a7af85+dirty | 3864.15 ms | 1222.57 ms | -2641.57 ms |
| 9eb54c2+dirty | 3811.25 ms | 3721.77 ms | -89.48 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 2cca10e+dirty | 5.15 MiB | 6.93 MiB | 1.78 MiB |
| 7a7af85+dirty | 5.15 MiB | 6.92 MiB | 1.77 MiB |
| 9eb54c2+dirty | 5.15 MiB | 6.90 MiB | 1.75 MiB |
The wrapper mirrored `RNSentryInternal` without its platform gating, so the macOS, tvOS and visionOS sample builds failed to compile it. `setCurrentScreen:`, `captureScreenshots` and `captureViewHierarchy` exist for iOS, tvOS and visionOS only — `RNSentryInternal` declares no stubs for the other platforms — so the mirror now carries the guard the call sites in `RNSentry.mm` already use. `collectProfileBetween:and:forTrace:` was a second, older problem: the watchOS/tvOS/visionOS stub was missing the explicit `@objc` selector that its counterpart declares, so the selector differed by platform. Nothing noticed because the only caller sits behind `SENTRY_TARGET_PROFILING_SUPPORTED`. Declare it on the stub too.
main moved to 9.29.1 while this branch was open, so the SwiftPM path stayed a patch behind the CocoaPods one. Take the version and the checksum from `sentry_utils.rb`, which the CocoaPods path already verifies — a clean resolve accepts them, which also confirms both consumers hash the same archive.
The SwiftPM link check searched the app binary for `sentry-cocoa`. That string is incidental to the prebuilt dependency, so an upstream change could drop it and fail a valid build. Look for `SentryOptions` instead: the linker keeps the name of every Objective-C class it links, so the marker is present whenever the library is. Reported by Warden.
…-support # Conflicts: # CHANGELOG.md
The 9.29.2 bump (#6785, #6787) landed on main while this branch was open and ran update-cocoa.sh before it learned about Package.swift, so the SwiftPM manifest still pinned 9.29.1. Checksum matches sentry-cocoa's own Package.swift at tag 9.29.2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#6786 added registerReplayTraceId on main while this branch was moving the .mm callers off the Swift module. git merged both cleanly, but the result calls RNSentryInternal directly from RNSentry.mm, which no longer imports the generated Swift header -- breaking every iOS build, CocoaPods included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <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.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 69a15e6. Configure here.
The existing check greps for class names, which reach the binary even when their categories do not. sentry-cocoa ships category-only object files (SentryReplayNetworkDetails+Capture, Options+Dictionary, SentryNSNotificationCenterWrapper) that nothing references, so the linker drops them from the static archive unless it is force-loaded -- #6609. CocoaPods uses -force_load for this; the SwiftPM path has no equivalent, so measure whether it actually matters here. The check confirms the canary selector still exists upstream before treating its absence in the app binary as a failure, so a sentry-cocoa rename warns instead of failing spuriously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A path or layout change made `find` return nothing, which took the same soft-pass branch as a renamed upstream selector and skipped the assertion entirely -- the one SwiftPM guard against the #6609 dead-strip crash. Split the two: a missing archive fails, a stale canary still warns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Unanchored, `!Package.swift` re-included the file at any depth, so a machine that had run the Cocoa tests also shipped the gitignored `RNSentryCocoaTester/build/generated/ios/Package.swift` -- making the published tarball depend on local build state. Anchor it like the other root-level entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The steps only existed in spm-application.yml, so trying SwiftPM meant reading a CI workflow. Covers the two things people hit first: every autolinked dependency needs a Package.swift, and the path is iOS only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
||
| ### Fixes | ||
|
|
||
| - Register the session replay mask components through the iOS codegen component provider ([#6784](https://github.com/getsentry/sentry-react-native/pull/6784)) |
There was a problem hiding this comment.
Q: My understanding is that this is a separate fix unrelated to the SPM support. Wdyt of splitting it on a separate PR to be tested separately? Since this is a PII sensitive change it would be nice to have an independent trail imo.
antonis
left a comment
There was a problem hiding this comment.
Thank you for your work on this Alex 🙇 The SPM changes LGMT 🎉
Added a comment suggestion on the masking change https://github.com/getsentry/sentry-react-native/pull/6784/changes#r4123462658

📢 Type of change
📜 Description
This is experimental support for the Swift Package Manager (SPM).
The SDK now has a
Package.swiftfile. React Native 0.87 and later versions canuse it. CocoaPods is still the default, and it does not change.
The PR also adds the iOS component provider for the two session replay mask
components. React Native then finds the classes directly. Before this change, it
used the legacy interop layer, which an app can disable.
💡 Motivation and Context
React Native 0.87 added SPM support. It is a preview.
The
npx react-native spmcommand stops if a library has noPackage.swiftfile. Thus you cannot build an app that uses this SDK with SPM.
Related to #5780.
💚 How did you test it?
npm install @sentry/react-nativecd ios && npx react-native spm add --deintegrateMore details can be found in README.md
A new CI job builds the app for each PR. It also makes sure that the app binary
contains the SDK and sentry-cocoa.
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps