Skip to content

feat(ios): Support SPM autolinking - #6784

Open
alwx wants to merge 17 commits into
mainfrom
alwx/feature/full-spm-support
Open

alwx wants to merge 17 commits into
mainfrom
alwx/feature/full-spm-support

Conversation

@alwx

@alwx alwx commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

This is experimental support for the Swift Package Manager (SPM).

The SDK now has a Package.swift file. React Native 0.87 and later versions can
use 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 spm command stops if a library has no Package.swift
file. Thus you cannot build an app that uses this SDK with SPM.

Related to #5780.

💚 How did you test it?

  • Create a new React Native app (with RN 0.87+)
  • npm install @sentry/react-native
  • cd ios && npx react-native spm add --deintegrate

More 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

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

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.
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 726e6c1

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

⚪ None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • feat(ios): Support SPM autolinking by alwx in #6784
  • chore(deps): bump getsentry/craft from 2.31.0 to 2.31.2 by dependabot in #6793
  • chore(deps): bump the codeql-action group with 3 updates by dependabot in #6789
  • chore(deps): bump ruby/setup-ruby from 1.324.0 to 1.327.0 by dependabot in #6790
  • chore(deps): bump getsentry/github-workflows/updater from 3.4.0 to 3.4.1 by dependabot in #6792
  • chore(deps): bump getsentry/github-workflows/danger from 4013fc6e1aeb1be1f9d3b4d232624f0ec1afa613 to 36c729264d2edc29ebae61950c50e1e9f043ad7e by dependabot in #6791
  • chore(deps): update Cocoa SDK to v9.29.2 by github-actions in #6787
  • feat(replay): Populate trace_ids in mobile replay events by antonis in #6786
  • chore(deps): update Cocoa SDK to v9.29.1 by github-actions in #6785
  • chore(deps): update Android SDK to v8.58.0 by github-actions in #6778
  • fix(build): Prevent update-android.sh from failing on SIGPIPE by antonis in #6777
  • chore(deps): update Wizard to v8.0.0 by github-actions in #6775

🤖 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.
@alwx alwx added the ready-to-merge Triggers the full CI test suite label Sep 24, 2026
@alwx alwx changed the title feat(ios): Support SwiftPM autolinking feat(ios): Support SPM autolinking Sep 24, 2026
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.
@sentry

sentry Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.28.0 (108) Release

⚙️ sentry-react-native Build Distribution Settings

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3878.28 ms 1232.33 ms -2645.95 ms
Size 5.15 MiB 6.93 MiB 1.78 MiB

Previous results on branch: alwx/feature/full-spm-support

Startup times

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

Comment thread .github/workflows/spm-application.yml Outdated
`@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.
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 430.57 ms 475.94 ms 45.37 ms
Size 50.56 MiB 56.51 MiB 5.95 MiB

Baseline results on branch: main

Startup times

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

Comment thread .github/workflows/spm-application.yml Outdated
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 430.77 ms 450.75 ms 19.98 ms
Size 50.56 MiB 56.51 MiB 5.95 MiB

Baseline results on branch: main

Startup times

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

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3845.38 ms 1222.63 ms -2622.76 ms
Size 5.15 MiB 6.93 MiB 1.78 MiB

Baseline results on branch: main

Startup times

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.
Comment thread packages/core/Package.swift Outdated
Comment thread packages/core/ios/AGENTS.md
Comment thread scripts/update-cocoa.sh
alwx and others added 5 commits September 25, 2026 11:17
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.
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>
Comment thread packages/core/Package.swift
Comment thread packages/core/ios/RNSentry.mm
Comment thread packages/core/ios/RNSentry.mm
Comment thread packages/core/ios/RNSentryInternalWrapper.h
Comment thread packages/core/ios/RNSentryInternalWrapper.m
Comment thread packages/core/Package.swift
Comment thread packages/core/ios/RNSentry.mm
Comment thread packages/core/ios/RNSentry.mm
Comment thread packages/core/ios/RNSentryInternalWrapper.h
Comment thread packages/core/ios/RNSentryInternalWrapper.m
Comment thread packages/core/ios/RNSentrySwiftBridge.h
alwx and others added 2 commits September 28, 2026 11:35
#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>
@alwx
alwx marked this pull request as ready for review September 28, 2026 09:35
@alwx
alwx requested a review from antonis as a code owner September 28, 2026 09:35

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/core/Package.swift
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>
Comment thread .github/workflows/spm-application.yml Outdated
Comment thread packages/core/ios/RNSentryInternalWrapper.h
alwx and others added 2 commits September 28, 2026 13:40
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>
Comment thread .github/workflows/spm-application.yml
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>
Comment thread CHANGELOG.md

### Fixes

- Register the session replay mask components through the iOS codegen component provider ([#6784](https://github.com/getsentry/sentry-react-native/pull/6784))

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.

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 antonis 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.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants