From c8143aa7221f6f166adc8a28ff978c02f6754262 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Tue, 30 Jun 2026 15:05:13 +0200 Subject: [PATCH 01/16] feat(ios): Default to consuming sentry-cocoa via Swift Package Manager 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 --- .github/workflows/sample-application.yml | 20 +++++++++++--- CHANGELOG.md | 3 +++ packages/core/RNSentry.podspec | 34 +++++++++++++++--------- packages/core/scripts/sentry_utils.rb | 9 +++++++ scripts/clang-format.sh | 1 + 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 5d5a687904..647719c9a9 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -43,7 +43,7 @@ jobs: caller_ref: ${{ github.ref }} build-ios: - name: Build ${{ matrix.rn-architecture }} ios ${{ matrix.build-type }} ${{ matrix.ios-use-frameworks }} + name: Build ${{ matrix.rn-architecture }} ios ${{ matrix.build-type }} ${{ matrix.ios-use-frameworks }} ${{ matrix.sentry-consumption }} runs-on: macos-26-xlarge needs: [diff_check, detect-changes] if: >- @@ -60,6 +60,15 @@ jobs: rn-architecture: ["new"] ios-use-frameworks: ["no-frameworks"] build-type: ["dev", "production"] + sentry-consumption: ["spm"] + # SPM is the default consumption path for sentry-cocoa on RN >= 0.75. + # Keep a single CocoaPods job to catch regressions in the fallback path + # used by older React Native versions. + include: + - rn-architecture: "new" + ios-use-frameworks: "no-frameworks" + build-type: "production" + sentry-consumption: "cocoapods" steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -95,6 +104,7 @@ jobs: [[ "${{ matrix.build-type }}" == "production" ]] && export ENABLE_PROD=1 || export ENABLE_PROD=0 [[ "${{ matrix.rn-architecture }}" == "new" ]] && export ENABLE_NEW_ARCH=1 || export ENABLE_NEW_ARCH=0 [[ "${{ matrix.ios-use-frameworks }}" == "dynamic-frameworks" ]] && export USE_FRAMEWORKS=dynamic + [[ "${{ matrix.sentry-consumption }}" == "cocoapods" ]] && export SENTRY_USE_SPM=0 ./scripts/pod-install.sh @@ -112,7 +122,9 @@ jobs: ./scripts/build-ios.sh - name: Archive iOS App - if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} + # Only upload from the SPM job (the default consumption path) to avoid + # duplicate artifact names when the CocoaPods regression job runs. + if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'spm' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: | zip -r \ @@ -120,7 +132,7 @@ jobs: sentryreactnativesample.app - name: Upload iOS APP - if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} + if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'spm' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-ios @@ -131,7 +143,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: build-sample-${{ matrix.rn-architecture }}-ios-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-logs + name: build-sample-${{ matrix.rn-architecture }}-ios-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-${{ matrix.sentry-consumption }}-logs path: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/ios/*.log build-android: diff --git a/CHANGELOG.md b/CHANGELOG.md index 394e2b5dbc..1d1771c134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ ### Changes - Default `mobileReplayIntegration({ networkCaptureBodies })` to `true`, matching the iOS and Android native SDK defaults ([#6372](https://github.com/getsentry/sentry-react-native/pull/6372)) +- Consume `sentry-cocoa` via Swift Package Manager by default on React Native >= 0.75 ([#5780](https://github.com/getsentry/sentry-react-native/issues/5780)) + + On RN 0.75+, `pod install` 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 React Native versions the SPM helper is unavailable and RNSentry transparently falls back to the CocoaPods source build. Override with `SENTRY_USE_SPM=0` to force CocoaPods, or `SENTRY_USE_SPM=1` to require SPM. ### Fixes diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index ea38efc95b..63763c1124 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -56,20 +56,30 @@ Pod::Spec.new do |s| sentry_cocoa_version = '9.19.0' - # Opt-in to consuming sentry-cocoa via Swift Package Manager. - # When `SENTRY_USE_SPM=1` is set, RNSentry pulls `Sentry` from the - # sentry-cocoa SPM package as a binary xcframework instead of from - # the Sentry CocoaPods source build. Defaults to CocoaPods consumption - # for backward compatibility with the full RN version range we support. + # Consume sentry-cocoa via Swift Package Manager by default. # - # Requires React Native >= 0.75 because the SPM helper - # (`react-native/scripts/cocoapods/spm.rb`) is loaded transitively from - # the Podfile via `react_native_pods.rb`. - if ENV['SENTRY_USE_SPM'] == '1' - unless defined?(SPM) && SPM.respond_to?(:dependency) + # On React Native >= 0.75, RNSentry pulls `Sentry` from the sentry-cocoa SPM + # package (a pre-built binary xcframework). On older RN, the SPM helper + # (`react-native/scripts/cocoapods/spm.rb`) is not available and we + # transparently fall back to the Sentry CocoaPods source build. + # + # Override the choice with the `SENTRY_USE_SPM` environment variable: + # * `SENTRY_USE_SPM=0` — force CocoaPods consumption even on RN >= 0.75. + # * `SENTRY_USE_SPM=1` — force SPM (errors if the helper is unavailable). + spm_helper_available = defined?(SPM) && SPM.respond_to?(:dependency) + env_use_spm = ENV['SENTRY_USE_SPM'] + use_spm = case env_use_spm + when '1' then true + when '0' then false + else supports_spm(rn_version) && spm_helper_available + end + + if use_spm + unless spm_helper_available raise 'SENTRY_USE_SPM=1 is set but the SPM helper is not loaded. ' \ - 'This requires React Native >= 0.75 and a Podfile that imports ' \ - 'react_native_pods.rb.' + 'This requires React Native >= 0.75 and a Podfile that loads ' \ + '`react_native_pods.rb`. Either upgrade React Native, or set ' \ + 'SENTRY_USE_SPM=0 to fall back to the Sentry CocoaPods build.' end SPM.dependency(s, url: 'https://github.com/getsentry/sentry-cocoa', diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 5dc57a3b52..36d1bc0da0 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -40,3 +40,12 @@ def should_use_folly_flags(rn_version) def is_new_hermes_runtime(rn_version) return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 81)) end + +# React Native >= 0.75 transitively loads `react-native/scripts/cocoapods/spm.rb` +# via `react_native_pods.rb`, which exposes the `SPM` helper that the RNSentry +# podspec uses to pull `sentry-cocoa` as a Swift Package binary xcframework. +# Below 0.75 the helper is unavailable and we fall back to consuming Sentry as +# a regular CocoaPods source build. +def supports_spm(rn_version) + return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 75)) +end diff --git a/scripts/clang-format.sh b/scripts/clang-format.sh index 6a642f4215..b9e94c77d1 100755 --- a/scripts/clang-format.sh +++ b/scripts/clang-format.sh @@ -64,6 +64,7 @@ cmd="find . -type f \( \ -path \"**android/build/**\" -or \ -path \"**.cxx/**\" -or \ -path \"**build/generated/**\" -or \ + -path \"**/DerivedData/**\" -or \ -path \"**/Carthage/Checkouts/*\" -or \ -path \"**/libs/**\" -or \ -path \"**/.yalc/**\" -or \ From 347198e6c81dd0e02b6b2a8a0a1f06d450b261f5 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Wed, 1 Jul 2026 17:09:32 +0200 Subject: [PATCH 02/16] docs(changelog): Link SPM entry to PR and flag breaking-change escape hatch Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed7878545..ca01437b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,9 +27,11 @@ ### Changes - Default `mobileReplayIntegration({ networkCaptureBodies })` to `true`, matching the iOS and Android native SDK defaults ([#6372](https://github.com/getsentry/sentry-react-native/pull/6372)) -- Consume `sentry-cocoa` via Swift Package Manager by default on React Native >= 0.75 ([#5780](https://github.com/getsentry/sentry-react-native/issues/5780)) +- Consume `sentry-cocoa` via Swift Package Manager by default on React Native >= 0.75 ([#6381](https://github.com/getsentry/sentry-react-native/pull/6381)) - On RN 0.75+, `pod install` 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 React Native versions the SPM helper is unavailable and RNSentry transparently falls back to the CocoaPods source build. Override with `SENTRY_USE_SPM=0` to force CocoaPods, or `SENTRY_USE_SPM=1` to require SPM. + **Warning** + + **This may be a breaking change for some setups.** On RN 0.75+, `pod install` now pulls Sentry as a pre-built binary xcframework, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), set `SENTRY_USE_SPM=0` before `pod install` to restore the previous behavior. On older React Native versions the SPM helper is unavailable and RNSentry transparently falls back to the CocoaPods source build. Use `SENTRY_USE_SPM=1` to require SPM. ### Fixes From e06dcf2a07c826289bc02fbf6cf9b2b3842b5f97 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 11:11:26 +0200 Subject: [PATCH 03/16] fix(ios): Use Sentry-Dynamic SPM product to avoid archive signature collision 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 --- packages/core/RNSentry.podspec | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 63763c1124..e30495ca5a 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -81,10 +81,16 @@ Pod::Spec.new do |s| '`react_native_pods.rb`. Either upgrade React Native, or set ' \ 'SENTRY_USE_SPM=0 to fall back to the Sentry CocoaPods build.' end + # Use the `Sentry-Dynamic` SPM product (dynamic xcframework). The static + # `Sentry` product ships a signed xcframework whose `Signatures/` entry + # collides during `xcodebuild archive` on Xcode 16/26, failing with + # `"Sentry.xcframework-ios.signature" couldn't be copied to "Signatures" + # because an item with the same name already exists`. The dynamic product + # avoids the copy path that triggers the collision. SPM.dependency(s, url: 'https://github.com/getsentry/sentry-cocoa', requirement: { kind: 'exactVersion', version: sentry_cocoa_version }, - products: ['Sentry'] + products: ['Sentry-Dynamic'] ) else s.dependency 'Sentry', sentry_cocoa_version From 48c5743107aab60d18ff4e6c5ba2652173165675 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 11:43:35 +0200 Subject: [PATCH 04/16] fix(ios): Use SentrySPM source product to avoid archive signature collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/core/RNSentry.podspec | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 2fbaa05cbe..38c51c915e 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -81,16 +81,17 @@ Pod::Spec.new do |s| '`react_native_pods.rb`. Either upgrade React Native, or set ' \ 'SENTRY_USE_SPM=0 to fall back to the Sentry CocoaPods build.' end - # Use the `Sentry-Dynamic` SPM product (dynamic xcframework). The static - # `Sentry` product ships a signed xcframework whose `Signatures/` entry - # collides during `xcodebuild archive` on Xcode 16/26, failing with - # `"Sentry.xcframework-ios.signature" couldn't be copied to "Signatures" - # because an item with the same name already exists`. The dynamic product - # avoids the copy path that triggers the collision. + # Use the `SentrySPM` source-compile product. sentry-cocoa's binary + # products (`Sentry`, `Sentry-Dynamic`, ...) ship signed xcframeworks whose + # `Signatures/*.signature` file collides during `xcodebuild archive` on + # Xcode 16/26, failing with `"…-ios.signature" couldn't be copied to + # "Signatures" because an item with the same name already exists`. + # `SentrySPM` is compiled from source (no xcframework, no `Signatures/`), + # which sidesteps the archive collision entirely. SPM.dependency(s, url: 'https://github.com/getsentry/sentry-cocoa', requirement: { kind: 'exactVersion', version: sentry_cocoa_version }, - products: ['Sentry-Dynamic'] + products: ['SentrySPM'] ) else s.dependency 'Sentry', sentry_cocoa_version From 9987701a98bbda3a3027051ed6928b8445ebde37 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 11:52:45 +0200 Subject: [PATCH 05/16] Revert "fix(ios): Use SentrySPM source product to avoid archive signature collision" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SentrySPM` doesn't expose the `Sentry` module the way the binary xcframework product does — RNSentry.mm fails to resolve `#import `, breaking every iOS build. Back to `Sentry-Dynamic` (archive-only failure) while we pick a real fix. Co-Authored-By: Claude Opus 4.7 --- packages/core/RNSentry.podspec | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 38c51c915e..2fbaa05cbe 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -81,17 +81,16 @@ Pod::Spec.new do |s| '`react_native_pods.rb`. Either upgrade React Native, or set ' \ 'SENTRY_USE_SPM=0 to fall back to the Sentry CocoaPods build.' end - # Use the `SentrySPM` source-compile product. sentry-cocoa's binary - # products (`Sentry`, `Sentry-Dynamic`, ...) ship signed xcframeworks whose - # `Signatures/*.signature` file collides during `xcodebuild archive` on - # Xcode 16/26, failing with `"…-ios.signature" couldn't be copied to - # "Signatures" because an item with the same name already exists`. - # `SentrySPM` is compiled from source (no xcframework, no `Signatures/`), - # which sidesteps the archive collision entirely. + # Use the `Sentry-Dynamic` SPM product (dynamic xcframework). The static + # `Sentry` product ships a signed xcframework whose `Signatures/` entry + # collides during `xcodebuild archive` on Xcode 16/26, failing with + # `"Sentry.xcframework-ios.signature" couldn't be copied to "Signatures" + # because an item with the same name already exists`. The dynamic product + # avoids the copy path that triggers the collision. SPM.dependency(s, url: 'https://github.com/getsentry/sentry-cocoa', requirement: { kind: 'exactVersion', version: sentry_cocoa_version }, - products: ['SentrySPM'] + products: ['Sentry-Dynamic'] ) else s.dependency 'Sentry', sentry_cocoa_version From eb8a766c4aadee8655639d2f5659e857074a3372 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 12:00:06 +0200 Subject: [PATCH 06/16] feat(ios): Vendor sentry-cocoa as prebuilt xcframework instead of SPM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/sample-application.yml | 20 ++++---- .gitignore | 3 ++ CHANGELOG.md | 4 +- packages/core/RNSentry.podspec | 53 +++++++------------ packages/core/scripts/sentry_utils.rb | 65 +++++++++++++++++++++--- 5 files changed, 93 insertions(+), 52 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 647719c9a9..afb6c9ac79 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -60,10 +60,11 @@ jobs: rn-architecture: ["new"] ios-use-frameworks: ["no-frameworks"] build-type: ["dev", "production"] - sentry-consumption: ["spm"] - # SPM is the default consumption path for sentry-cocoa on RN >= 0.75. - # Keep a single CocoaPods job to catch regressions in the fallback path - # used by older React Native versions. + sentry-consumption: ["xcframework"] + # `xcframework` is the default: RNSentry vendors a prebuilt + # `Sentry-Dynamic.xcframework` downloaded from sentry-cocoa's release. + # Keep a single CocoaPods job to catch regressions in the source-build + # fallback (`SENTRY_USE_XCFRAMEWORK=0`). include: - rn-architecture: "new" ios-use-frameworks: "no-frameworks" @@ -104,7 +105,7 @@ jobs: [[ "${{ matrix.build-type }}" == "production" ]] && export ENABLE_PROD=1 || export ENABLE_PROD=0 [[ "${{ matrix.rn-architecture }}" == "new" ]] && export ENABLE_NEW_ARCH=1 || export ENABLE_NEW_ARCH=0 [[ "${{ matrix.ios-use-frameworks }}" == "dynamic-frameworks" ]] && export USE_FRAMEWORKS=dynamic - [[ "${{ matrix.sentry-consumption }}" == "cocoapods" ]] && export SENTRY_USE_SPM=0 + [[ "${{ matrix.sentry-consumption }}" == "cocoapods" ]] && export SENTRY_USE_XCFRAMEWORK=0 ./scripts/pod-install.sh @@ -122,9 +123,10 @@ jobs: ./scripts/build-ios.sh - name: Archive iOS App - # Only upload from the SPM job (the default consumption path) to avoid - # duplicate artifact names when the CocoaPods regression job runs. - if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'spm' }} + # Only upload from the xcframework job (the default consumption path) + # to avoid duplicate artifact names when the CocoaPods regression job + # runs. + if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'xcframework' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: | zip -r \ @@ -132,7 +134,7 @@ jobs: sentryreactnativesample.app - name: Upload iOS APP - if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'spm' }} + if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' && matrix.sentry-consumption == 'xcframework' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-ios diff --git a/.gitignore b/.gitignore index b6dea85f87..30f069ab73 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,9 @@ node_modules.bak # API Extractor temp files /packages/core/temp/ +# sentry-cocoa xcframework downloaded at pod install time +/packages/core/ios/Vendor/ + # Sentry React Native Monorepo /packages/core/README.md .env.sentry-build-plugin diff --git a/CHANGELOG.md b/CHANGELOG.md index d2ddeffdde..18901e3bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,11 +28,11 @@ ### Changes - Default `mobileReplayIntegration({ networkCaptureBodies })` to `true`, matching the iOS and Android native SDK defaults ([#6372](https://github.com/getsentry/sentry-react-native/pull/6372)) -- Consume `sentry-cocoa` via Swift Package Manager by default on React Native >= 0.75 ([#6381](https://github.com/getsentry/sentry-react-native/pull/6381)) +- Consume `sentry-cocoa` as a prebuilt xcframework by default on iOS ([#6381](https://github.com/getsentry/sentry-react-native/pull/6381)) **Warning** - **This may be a breaking change for some setups.** On RN 0.75+, `pod install` now pulls Sentry as a pre-built binary xcframework, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), set `SENTRY_USE_SPM=0` before `pod install` to restore the previous behavior. On older React Native versions the SPM helper is unavailable and RNSentry transparently falls back to the CocoaPods source build. Use `SENTRY_USE_SPM=1` to require SPM. + **This may be a breaking change for some setups.** `pod install` now downloads `Sentry-Dynamic.xcframework` from sentry-cocoa's GitHub release (SHA256-verified) and vendors it, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), or if your `pod install` environment cannot reach `github.com`, set `SENTRY_USE_XCFRAMEWORK=0` before `pod install` to restore the previous source-build behavior. ### Fixes diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 2fbaa05cbe..75cf5f0d32 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -46,6 +46,7 @@ Pod::Spec.new do |s| # in `android/CMakeLists.txt`. The files are guarded with # `RCT_NEW_ARCH_ENABLED` so they compile to empty TUs on Old Arch. s.source_files = 'ios/**/*.{h,m,mm}', 'cpp/**/*.{h,cpp}' + s.exclude_files = 'ios/Vendor/**/*' s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h', 'ios/RNSentryStart.h', 'ios/RNSentryVersion.h', 'ios/RNSentryBreadcrumb.h', 'ios/RNSentryReplay.h', 'ios/RNSentryReplayBreadcrumbConverter.h', 'ios/Replay/RNSentryReplayMask.h', 'ios/Replay/RNSentryReplayUnmask.h', 'ios/RNSentryTimeToDisplay.h' s.compiler_flags = other_cflags @@ -56,42 +57,26 @@ Pod::Spec.new do |s| sentry_cocoa_version = '9.19.1' - # Consume sentry-cocoa via Swift Package Manager by default. + # Consume sentry-cocoa as a prebuilt `Sentry-Dynamic.xcframework` by default. # - # On React Native >= 0.75, RNSentry pulls `Sentry` from the sentry-cocoa SPM - # package (a pre-built binary xcframework). On older RN, the SPM helper - # (`react-native/scripts/cocoapods/spm.rb`) is not available and we - # transparently fall back to the Sentry CocoaPods source build. + # The xcframework is downloaded from sentry-cocoa's GitHub Release, + # SHA256-verified, and cached under `ios/Vendor/`. CocoaPods then embeds it + # via `s.vendored_frameworks`. This avoids compiling sentry-cocoa from + # source (fast install) and sidesteps the Xcode 16/26 archive bug that + # affects the same xcframework when it is consumed through Xcode's SPM + # integration (`Signatures/*.signature` collision during archive). # - # Override the choice with the `SENTRY_USE_SPM` environment variable: - # * `SENTRY_USE_SPM=0` — force CocoaPods consumption even on RN >= 0.75. - # * `SENTRY_USE_SPM=1` — force SPM (errors if the helper is unavailable). - spm_helper_available = defined?(SPM) && SPM.respond_to?(:dependency) - env_use_spm = ENV['SENTRY_USE_SPM'] - use_spm = case env_use_spm - when '1' then true - when '0' then false - else supports_spm(rn_version) && spm_helper_available - end - - if use_spm - unless spm_helper_available - raise 'SENTRY_USE_SPM=1 is set but the SPM helper is not loaded. ' \ - 'This requires React Native >= 0.75 and a Podfile that loads ' \ - '`react_native_pods.rb`. Either upgrade React Native, or set ' \ - 'SENTRY_USE_SPM=0 to fall back to the Sentry CocoaPods build.' - end - # Use the `Sentry-Dynamic` SPM product (dynamic xcframework). The static - # `Sentry` product ships a signed xcframework whose `Signatures/` entry - # collides during `xcodebuild archive` on Xcode 16/26, failing with - # `"Sentry.xcframework-ios.signature" couldn't be copied to "Signatures" - # because an item with the same name already exists`. The dynamic product - # avoids the copy path that triggers the collision. - SPM.dependency(s, - url: 'https://github.com/getsentry/sentry-cocoa', - requirement: { kind: 'exactVersion', version: sentry_cocoa_version }, - products: ['Sentry-Dynamic'] - ) + # Set `SENTRY_USE_XCFRAMEWORK=0` to fall back to the source-built + # `Sentry` CocoaPod (e.g. for offline builds behind a restrictive proxy). + env_use_xcframework = ENV['SENTRY_USE_XCFRAMEWORK'] + use_xcframework = case env_use_xcframework + when '0' then false + else true + end + + if use_xcframework + ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry-Dynamic') + s.vendored_frameworks = 'ios/Vendor/Sentry-Dynamic.xcframework' else s.dependency 'Sentry', sentry_cocoa_version end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 36d1bc0da0..ec165f2c56 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -41,11 +41,62 @@ def is_new_hermes_runtime(rn_version) return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 81)) end -# React Native >= 0.75 transitively loads `react-native/scripts/cocoapods/spm.rb` -# via `react_native_pods.rb`, which exposes the `SPM` helper that the RNSentry -# podspec uses to pull `sentry-cocoa` as a Swift Package binary xcframework. -# Below 0.75 the helper is unavailable and we fall back to consuming Sentry as -# a regular CocoaPods source build. -def supports_spm(rn_version) - return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 75)) +require 'digest' +require 'fileutils' + +# SHA256 checksums of `.xcframework.zip` assets published in +# sentry-cocoa GitHub releases (same value as the SPM binary target checksum +# in sentry-cocoa's `Package.swift`). Register the checksum for each +# sentry-cocoa version we ship a prebuilt xcframework for. +SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS = { + '9.19.1' => { + 'Sentry-Dynamic' => '7d0fb876a35b40ef942d36cd43dcab0ee16d2874d5cc7cc668e8e01e0c83db2a', + }, +}.freeze + +# Ensures `/ios/Vendor/.xcframework` exists. +# +# On first invocation, downloads the prebuilt xcframework zip from +# sentry-cocoa's GitHub release, verifies its SHA256 checksum against +# `SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS`, and extracts it. Subsequent +# invocations are no-ops. +# +# Consuming sentry-cocoa this way (vs. through Xcode's SPM integration) +# avoids the Xcode 16/26 archive bug where a signed SPM binary xcframework's +# `Signatures/*.signature` file collides during the archive step. +def ensure_sentry_xcframework(version, product = 'Sentry-Dynamic') + vendor_dir = File.expand_path('../ios/Vendor', __dir__) + target_dir = File.join(vendor_dir, "#{product}.xcframework") + return target_dir if File.directory?(target_dir) + + expected_checksum = SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS.dig(version, product) + unless expected_checksum + raise "sentry-cocoa xcframework checksum not registered for #{product} " \ + "#{version}. Add it to SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS in " \ + "packages/core/scripts/sentry_utils.rb after bumping the version." + end + + FileUtils.mkdir_p(vendor_dir) + zip_path = File.join(vendor_dir, "#{product}.xcframework.zip") + url = "https://github.com/getsentry/sentry-cocoa/releases/download/" \ + "#{version}/#{product}.xcframework.zip" + + Pod::UI.puts "[Sentry] Downloading #{product} #{version} from GitHub Releases…" if defined?(Pod::UI) + unless system('curl', '-sSfL', '-o', zip_path, url) + raise "Failed to download #{url}" + end + + actual_checksum = Digest::SHA256.file(zip_path).hexdigest + unless actual_checksum == expected_checksum + FileUtils.rm_f(zip_path) + raise "Checksum mismatch for #{product} #{version}: expected " \ + "#{expected_checksum}, got #{actual_checksum}" + end + + unless system('unzip', '-q', '-o', zip_path, '-d', vendor_dir) + raise "Failed to extract #{zip_path}" + end + FileUtils.rm_f(zip_path) + + target_dir end From 1ed61c62cdc883e721f69366f30f16fd5c24f039 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 12:08:05 +0200 Subject: [PATCH 07/16] fix(ios): Use Sentry (static) xcframework so CocoaPods resolves the module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 2 +- packages/core/RNSentry.podspec | 13 +++++++------ packages/core/scripts/sentry_utils.rb | 10 ++++++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18901e3bc3..6d1ef33fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,7 @@ **Warning** - **This may be a breaking change for some setups.** `pod install` now downloads `Sentry-Dynamic.xcframework` from sentry-cocoa's GitHub release (SHA256-verified) and vendors it, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), or if your `pod install` environment cannot reach `github.com`, set `SENTRY_USE_XCFRAMEWORK=0` before `pod install` to restore the previous source-build behavior. + **This may be a breaking change for some setups.** `pod install` now downloads `Sentry.xcframework` from sentry-cocoa's GitHub release (SHA256-verified) and vendors it, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), or if your `pod install` environment cannot reach `github.com`, set `SENTRY_USE_XCFRAMEWORK=0` before `pod install` to restore the previous source-build behavior. ### Fixes diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 75cf5f0d32..70f4ccea7c 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -57,14 +57,15 @@ Pod::Spec.new do |s| sentry_cocoa_version = '9.19.1' - # Consume sentry-cocoa as a prebuilt `Sentry-Dynamic.xcframework` by default. + # Consume sentry-cocoa as a prebuilt `Sentry.xcframework` by default. # # The xcframework is downloaded from sentry-cocoa's GitHub Release, - # SHA256-verified, and cached under `ios/Vendor/`. CocoaPods then embeds it + # SHA256-verified, and cached under `ios/Vendor/`. CocoaPods then links it # via `s.vendored_frameworks`. This avoids compiling sentry-cocoa from # source (fast install) and sidesteps the Xcode 16/26 archive bug that - # affects the same xcframework when it is consumed through Xcode's SPM - # integration (`Signatures/*.signature` collision during archive). + # affects the same xcframework when consumed through Xcode's SPM + # integration (`Signatures/*.signature` collision during archive) — the + # CocoaPods embed path is a different pipeline and is not affected. # # Set `SENTRY_USE_XCFRAMEWORK=0` to fall back to the source-built # `Sentry` CocoaPod (e.g. for offline builds behind a restrictive proxy). @@ -75,8 +76,8 @@ Pod::Spec.new do |s| end if use_xcframework - ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry-Dynamic') - s.vendored_frameworks = 'ios/Vendor/Sentry-Dynamic.xcframework' + ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry') + s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework' else s.dependency 'Sentry', sentry_cocoa_version end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index ec165f2c56..ffff963032 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -50,7 +50,13 @@ def is_new_hermes_runtime(rn_version) # sentry-cocoa version we ship a prebuilt xcframework for. SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS = { '9.19.1' => { - 'Sentry-Dynamic' => '7d0fb876a35b40ef942d36cd43dcab0ee16d2874d5cc7cc668e8e01e0c83db2a', + # `Sentry.xcframework.zip` — the static product. Its enclosing xcframework + # name matches the framework name inside (both `Sentry`), which CocoaPods + # requires to generate `-framework Sentry` correctly and to resolve the + # `Sentry` module. `Sentry-Dynamic.xcframework` would ship the same + # `Sentry.framework` inside but under a mismatched enclosing name, so + # CocoaPods generates `-framework Sentry-Dynamic` and fails at link. + 'Sentry' => 'd6d545af17e49851cda2747b0f45cde78ce08ea37709dde5a956c6b4671224e8', }, }.freeze @@ -64,7 +70,7 @@ def is_new_hermes_runtime(rn_version) # Consuming sentry-cocoa this way (vs. through Xcode's SPM integration) # avoids the Xcode 16/26 archive bug where a signed SPM binary xcframework's # `Signatures/*.signature` file collides during the archive step. -def ensure_sentry_xcframework(version, product = 'Sentry-Dynamic') +def ensure_sentry_xcframework(version, product = 'Sentry') vendor_dir = File.expand_path('../ios/Vendor', __dir__) target_dir = File.join(vendor_dir, "#{product}.xcframework") return target_dir if File.directory?(target_dir) From e92872e6766518f8adcae7c760896ffedef666c5 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 12:10:17 +0200 Subject: [PATCH 08/16] fix(ios): Address reviewer feedback on xcframework install - 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 --- packages/core/RNSentry.podspec | 8 ++++++++ packages/core/scripts/sentry_utils.rb | 20 +++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 70f4ccea7c..f088c25306 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -69,7 +69,15 @@ Pod::Spec.new do |s| # # Set `SENTRY_USE_XCFRAMEWORK=0` to fall back to the source-built # `Sentry` CocoaPod (e.g. for offline builds behind a restrictive proxy). + # + # `SENTRY_USE_SPM` was the name in earlier drafts of this PR; honor it as a + # deprecated alias so CI or local envs still exporting `SENTRY_USE_SPM=0` + # don't silently take the new xcframework path. env_use_xcframework = ENV['SENTRY_USE_XCFRAMEWORK'] + if env_use_xcframework.nil? && !ENV['SENTRY_USE_SPM'].nil? + Pod::UI.warn '[Sentry] SENTRY_USE_SPM is deprecated; use SENTRY_USE_XCFRAMEWORK instead.' if defined?(Pod::UI) + env_use_xcframework = ENV['SENTRY_USE_SPM'] + end use_xcframework = case env_use_xcframework when '0' then false else true diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index ffff963032..6f9d386a41 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -73,7 +73,12 @@ def is_new_hermes_runtime(rn_version) def ensure_sentry_xcframework(version, product = 'Sentry') vendor_dir = File.expand_path('../ios/Vendor', __dir__) target_dir = File.join(vendor_dir, "#{product}.xcframework") - return target_dir if File.directory?(target_dir) + # Treat the presence of `Info.plist` inside the xcframework as the "healthy" + # sentinel rather than just the directory existence. A directory without + # `Info.plist` most likely came from an interrupted `unzip` and would + # otherwise silently short-circuit re-download here. + target_manifest = File.join(target_dir, 'Info.plist') + return target_dir if File.file?(target_manifest) expected_checksum = SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS.dig(version, product) unless expected_checksum @@ -82,6 +87,9 @@ def ensure_sentry_xcframework(version, product = 'Sentry') "packages/core/scripts/sentry_utils.rb after bumping the version." end + # Wipe any stale partial extract from a previous interrupted run so we + # always start from a clean tree. + FileUtils.rm_rf(target_dir) FileUtils.mkdir_p(vendor_dir) zip_path = File.join(vendor_dir, "#{product}.xcframework.zip") url = "https://github.com/getsentry/sentry-cocoa/releases/download/" \ @@ -104,5 +112,15 @@ def ensure_sentry_xcframework(version, product = 'Sentry') end FileUtils.rm_f(zip_path) + # Guard against a release archive whose internal layout changed (e.g. a + # nested folder). Without this check, a wrong layout silently succeeds and + # then fails much later during `pod install` with a confusing "framework + # not found" error. + unless File.file?(target_manifest) + raise "Expected #{target_manifest} after extracting #{product}.xcframework.zip. " \ + "The sentry-cocoa release archive layout may have changed — update " \ + "the extraction logic in packages/core/scripts/sentry_utils.rb." + end + target_dir end From 99df7ebf204b5bd4542ba19f4fcaca0261bfcc1f Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 13:25:04 +0200 Subject: [PATCH 09/16] fix(ios): Add explicit FRAMEWORK_SEARCH_PATHS for vendored Sentry.xcframework 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 ` 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 --- packages/core/RNSentry.podspec | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index f088c25306..bdc5bd6e43 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -86,6 +86,14 @@ Pod::Spec.new do |s| if use_xcframework ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry') s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework' + # `s.vendored_frameworks` alone doesn't always propagate a framework + # search path to the pod's own compile phase for a vendored xcframework, + # so RNSentry.mm fails to resolve `#import `. Force the search + # path via pod_target_xcconfig — `${PODS_TARGET_SRCROOT}` resolves to + # the pod's source directory (i.e. `packages/core/`) at build time. + s.pod_target_xcconfig.merge!( + 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/ios/Vendor"' + ) else s.dependency 'Sentry', sentry_cocoa_version end From 4a13dc473722f6103bac8d84c042e6ac0b738c41 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 13:30:44 +0200 Subject: [PATCH 10/16] fix(ios): Build pod_target_xcconfig locally, assign once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 --- packages/core/RNSentry.podspec | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index bdc5bd6e43..5da78cd810 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |s| s.compiler_flags = other_cflags - s.pod_target_xcconfig = { + pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } @@ -88,12 +88,11 @@ Pod::Spec.new do |s| s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework' # `s.vendored_frameworks` alone doesn't always propagate a framework # search path to the pod's own compile phase for a vendored xcframework, - # so RNSentry.mm fails to resolve `#import `. Force the search - # path via pod_target_xcconfig — `${PODS_TARGET_SRCROOT}` resolves to - # the pod's source directory (i.e. `packages/core/`) at build time. - s.pod_target_xcconfig.merge!( - 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/ios/Vendor"' - ) + # so RNSentry.mm fails to resolve `#import `. Add the search + # path explicitly — `${PODS_TARGET_SRCROOT}` resolves to the pod's + # source directory (i.e. `packages/core/`) at build time. + pod_target_xcconfig['FRAMEWORK_SEARCH_PATHS'] = + '$(inherited) "${PODS_TARGET_SRCROOT}/ios/Vendor"' else s.dependency 'Sentry', sentry_cocoa_version end @@ -106,7 +105,7 @@ Pod::Spec.new do |s| if is_new_arch_enabled then # New Architecture on React Native 0.70 and older - s.pod_target_xcconfig.merge!({ + pod_target_xcconfig.merge!({ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" }) @@ -124,4 +123,6 @@ Pod::Spec.new do |s| s.dependency 'React-hermes' s.dependency 'hermes-engine' end + + s.pod_target_xcconfig = pod_target_xcconfig end From fec31a065a28fe0de1e22ce60fbb5a9866037629 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 13:41:02 +0200 Subject: [PATCH 11/16] fix(ios): Assign pod_target_xcconfig before install_modules_dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ` (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 --- packages/core/RNSentry.podspec | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 5da78cd810..86b407bdd3 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -97,6 +97,11 @@ Pod::Spec.new do |s| s.dependency 'Sentry', sentry_cocoa_version end + # Assign before `install_modules_dependencies` so it can merge its + # RN-specific settings on top. Assigning after would clobber those and + # break header resolution across the pod. + s.pod_target_xcconfig = pod_target_xcconfig + if defined? install_modules_dependencies # Default React Native dependencies for 0.71 and above (new and legacy architecture) install_modules_dependencies(s) @@ -109,6 +114,9 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" }) + # `install_modules_dependencies` is not defined on RN < 0.71 so re-assigning + # here is safe — nothing else has written to `s.pod_target_xcconfig` yet. + s.pod_target_xcconfig = pod_target_xcconfig s.dependency "React-RCTFabric" # Required for Fabric Components (like RCTViewComponentView) s.dependency "React-Codegen" @@ -123,6 +131,4 @@ Pod::Spec.new do |s| s.dependency 'React-hermes' s.dependency 'hermes-engine' end - - s.pod_target_xcconfig = pod_target_xcconfig end From bcaf4ba50f917a3877013935f5032c60952c57d3 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 14:06:12 +0200 Subject: [PATCH 12/16] fix(ios): Enumerate Sentry.xcframework slice dirs for FRAMEWORK_SEARCH_PATHS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed by local repro: Xcode's `-F ` 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 ` 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 ` 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 --- packages/core/RNSentry.podspec | 27 ++++++++++++++++++++------- packages/core/scripts/sentry_utils.rb | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 86b407bdd3..576f10c3db 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -84,15 +84,28 @@ Pod::Spec.new do |s| end if use_xcframework - ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry') + sentry_xcframework_dir = ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry') s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework' - # `s.vendored_frameworks` alone doesn't always propagate a framework - # search path to the pod's own compile phase for a vendored xcframework, - # so RNSentry.mm fails to resolve `#import `. Add the search - # path explicitly — `${PODS_TARGET_SRCROOT}` resolves to the pod's - # source directory (i.e. `packages/core/`) at build time. + + # Xcode's `-F ` doesn't descend into `.xcframework` bundles — it + # looks for `Sentry.framework` directly at the given path. Point search + # paths at every slice inside the xcframework so `#import ` + # resolves for whichever platform/arch Xcode ends up building. The + # slice list is enumerated at pod-install time from the extracted + # xcframework, so newly-added slices in future sentry-cocoa releases + # (e.g. new visionOS variants) come along for free. + # + # `pod_target_xcconfig` covers RNSentry's own compile phase; + # `user_target_xcconfig` covers the host app target (any `#import + # ` written directly in user code). + slice_paths = sentry_xcframework_slice_ids(sentry_xcframework_dir).map do |slice| + %("${PODS_TARGET_SRCROOT}/ios/Vendor/Sentry.xcframework/#{slice}") + end pod_target_xcconfig['FRAMEWORK_SEARCH_PATHS'] = - '$(inherited) "${PODS_TARGET_SRCROOT}/ios/Vendor"' + (['$(inherited)'] + slice_paths).join(' ') + s.user_target_xcconfig = { + 'FRAMEWORK_SEARCH_PATHS' => pod_target_xcconfig['FRAMEWORK_SEARCH_PATHS'], + } else s.dependency 'Sentry', sentry_cocoa_version end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 6f9d386a41..707ed21676 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -124,3 +124,18 @@ def ensure_sentry_xcframework(version, product = 'Sentry') target_dir end + +# Returns the list of slice directory names inside an xcframework bundle +# (e.g. `ios-arm64`, `ios-arm64_x86_64-simulator`). +# +# Xcode's `-F ` does not descend into an `.xcframework` bundle at +# search-path lookup time — it only sees `Sentry.xcframework` as a directory +# and doesn't find `Sentry.framework` inside. Callers use this list to +# enumerate every slice directory as a separate framework search path so +# `#import ` resolves for whichever platform/arch Xcode is +# building for. +def sentry_xcframework_slice_ids(xcframework_dir) + Dir.children(xcframework_dir).select do |name| + File.directory?(File.join(xcframework_dir, name)) && name != '_CodeSignature' + end.sort +end From 81d7a9ce12c30eb166b9af7846f94c5122cbc4dd Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 15:44:21 +0200 Subject: [PATCH 13/16] fix(ios): SDK-conditional framework search paths per xcframework slice 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 --- packages/core/RNSentry.podspec | 48 +++++++++++++++++--------- packages/core/scripts/sentry_utils.rb | 49 ++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 576f10c3db..e73d7c12e8 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -88,24 +88,40 @@ Pod::Spec.new do |s| s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework' # Xcode's `-F ` doesn't descend into `.xcframework` bundles — it - # looks for `Sentry.framework` directly at the given path. Point search - # paths at every slice inside the xcframework so `#import ` - # resolves for whichever platform/arch Xcode ends up building. The - # slice list is enumerated at pod-install time from the extracted - # xcframework, so newly-added slices in future sentry-cocoa releases - # (e.g. new visionOS variants) come along for free. + # looks for `Sentry.framework` directly at the given path. Point a + # separate framework search path at each slice, gated by the matching + # SDK selector so `#import ` resolves against exactly one + # slice per build. An unconditional search-path list would let Xcode's + # Swift module precompiler stumble into a slice for a different arch + # and fail with "unsupported Swift architecture". New slices in future + # sentry-cocoa releases are picked up automatically at pod-install. # - # `pod_target_xcconfig` covers RNSentry's own compile phase; - # `user_target_xcconfig` covers the host app target (any `#import - # ` written directly in user code). - slice_paths = sentry_xcframework_slice_ids(sentry_xcframework_dir).map do |slice| - %("${PODS_TARGET_SRCROOT}/ios/Vendor/Sentry.xcframework/#{slice}") + # `pod_target_xcconfig` covers RNSentry's own compile phase — inside + # a pod's xcconfig, `${PODS_TARGET_SRCROOT}` resolves to the pod + # source directory (`packages/core/` here). `user_target_xcconfig` + # covers the host app target so app code writing `#import ` + # (e.g. `SentryNativeInitializer.m`) also finds the framework. The + # aggregate/user xcconfig doesn't define `PODS_TARGET_SRCROOT`, so + # for that target we resolve the path via `${PODS_ROOT}/..` + + # `node_modules/@sentry/react-native`, which is where RN's + # auto-linking places the SDK. + pod_search_paths = {} + user_search_paths = {} + sentry_xcframework_slices_by_sdk(sentry_xcframework_dir).each do |sdk, slice_ids| + pod_paths = slice_ids.map do |slice| + %("${PODS_TARGET_SRCROOT}/ios/Vendor/Sentry.xcframework/#{slice}") + end + user_paths = slice_ids.map do |slice| + %("${PODS_ROOT}/../../node_modules/@sentry/react-native/ios/Vendor/Sentry.xcframework/#{slice}") + end + pod_search_paths["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = + (['$(inherited)'] + pod_paths).join(' ') + user_search_paths["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = + (['$(inherited)'] + user_paths).join(' ') end - pod_target_xcconfig['FRAMEWORK_SEARCH_PATHS'] = - (['$(inherited)'] + slice_paths).join(' ') - s.user_target_xcconfig = { - 'FRAMEWORK_SEARCH_PATHS' => pod_target_xcconfig['FRAMEWORK_SEARCH_PATHS'], - } + + pod_target_xcconfig.merge!(pod_search_paths) + s.user_target_xcconfig = user_search_paths else s.dependency 'Sentry', sentry_cocoa_version end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 707ed21676..fb2e3cbe4c 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -125,17 +125,50 @@ def ensure_sentry_xcframework(version, product = 'Sentry') target_dir end -# Returns the list of slice directory names inside an xcframework bundle -# (e.g. `ios-arm64`, `ios-arm64_x86_64-simulator`). +# Returns a hash of ` => [slice_id, ...]` for the slice +# directories inside an xcframework bundle. # # Xcode's `-F ` does not descend into an `.xcframework` bundle at # search-path lookup time — it only sees `Sentry.xcframework` as a directory -# and doesn't find `Sentry.framework` inside. Callers use this list to -# enumerate every slice directory as a separate framework search path so -# `#import ` resolves for whichever platform/arch Xcode is -# building for. -def sentry_xcframework_slice_ids(xcframework_dir) - Dir.children(xcframework_dir).select do |name| +# and doesn't find `Sentry.framework` inside. Callers use these groupings +# to emit SDK-conditional `FRAMEWORK_SEARCH_PATHS[sdk=…*]` xcconfig entries +# so each SDK build only sees its own slice — putting all slices under a +# single unconditional search path lets Xcode's Swift module precompiler +# stumble into an incompatible slice and fail with +# "unsupported Swift architecture". +# +# Slice-name convention is stable across the xcframeworks Apple has ever +# published: +# ios-*-simulator -> iphonesimulator +# ios-*-maccatalyst -> maccatalyst +# ios-… -> iphoneos +# tvos-*-simulator -> appletvsimulator +# tvos-… -> appletvos +# watchos-*-simulator -> watchsimulator +# watchos-… -> watchos +# xros-*-simulator -> xrsimulator +# xros-… -> xros +# macos-… -> macosx +def sentry_xcframework_slices_by_sdk(xcframework_dir) + slice_ids = Dir.children(xcframework_dir).select do |name| File.directory?(File.join(xcframework_dir, name)) && name != '_CodeSignature' end.sort + + slice_ids.each_with_object({}) do |slice, groups| + sdk = _sentry_sdk_for_slice(slice) + next unless sdk # unknown platform prefix — skip rather than mis-attach + (groups[sdk] ||= []) << slice + end +end + +def _sentry_sdk_for_slice(slice_id) + return 'maccatalyst' if slice_id.include?('-maccatalyst') + simulator = slice_id.end_with?('-simulator') + case + when slice_id.start_with?('ios-') then simulator ? 'iphonesimulator' : 'iphoneos' + when slice_id.start_with?('tvos-') then simulator ? 'appletvsimulator' : 'appletvos' + when slice_id.start_with?('watchos-') then simulator ? 'watchsimulator' : 'watchos' + when slice_id.start_with?('xros-') then simulator ? 'xrsimulator' : 'xros' + when slice_id.start_with?('macos-') then 'macosx' + end end From 69d9dbb067ba91a79f76ba34007b3b6d4e93f10a Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 16:04:46 +0200 Subject: [PATCH 14/16] fix(ios): Use absolute xcframework path + Swift link stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/core/RNSentry.podspec | 43 +++++++++---------- packages/core/ios/RNSentrySwiftLinkStub.swift | 7 +++ 2 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 packages/core/ios/RNSentrySwiftLinkStub.swift diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index e73d7c12e8..b597c8c7c8 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -45,7 +45,11 @@ Pod::Spec.new do |s| # is pulled in here; on Android it is compiled by the dedicated CMake target # in `android/CMakeLists.txt`. The files are guarded with # `RCT_NEW_ARCH_ENABLED` so they compile to empty TUs on Old Arch. - s.source_files = 'ios/**/*.{h,m,mm}', 'cpp/**/*.{h,cpp}' + # `.swift` is here for `RNSentrySwiftLinkStub.swift`, which forces Xcode + # to link the Swift runtime compatibility libs required by our vendored + # Sentry.xcframework static Swift library. + s.source_files = 'ios/**/*.{h,m,mm,swift}', 'cpp/**/*.{h,cpp}' + s.swift_versions = ['5.5'] s.exclude_files = 'ios/Vendor/**/*' s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h', 'ios/RNSentryStart.h', 'ios/RNSentryVersion.h', 'ios/RNSentryBreadcrumb.h', 'ios/RNSentryReplay.h', 'ios/RNSentryReplayBreadcrumbConverter.h', 'ios/Replay/RNSentryReplayMask.h', 'ios/Replay/RNSentryReplayUnmask.h', 'ios/RNSentryTimeToDisplay.h' @@ -96,32 +100,25 @@ Pod::Spec.new do |s| # and fail with "unsupported Swift architecture". New slices in future # sentry-cocoa releases are picked up automatically at pod-install. # - # `pod_target_xcconfig` covers RNSentry's own compile phase — inside - # a pod's xcconfig, `${PODS_TARGET_SRCROOT}` resolves to the pod - # source directory (`packages/core/` here). `user_target_xcconfig` - # covers the host app target so app code writing `#import ` - # (e.g. `SentryNativeInitializer.m`) also finds the framework. The - # aggregate/user xcconfig doesn't define `PODS_TARGET_SRCROOT`, so - # for that target we resolve the path via `${PODS_ROOT}/..` + - # `node_modules/@sentry/react-native`, which is where RN's - # auto-linking places the SDK. - pod_search_paths = {} - user_search_paths = {} + # Point the search paths at the pod-install-time absolute path to the + # xcframework. `${PODS_TARGET_SRCROOT}` is only defined in per-pod + # xcconfigs, not in aggregate/user-target xcconfigs, and a + # `${PODS_ROOT}`-relative fallback works for one Podfile layout but + # breaks for another (e.g. the RN sample apps put node_modules at a + # different depth from RNSentryCocoaTester). Using the absolute path + # avoids the layout-detection dance — the path is regenerated on + # every `pod install`, so it's not something anyone commits. + xcframework_search_paths = {} sentry_xcframework_slices_by_sdk(sentry_xcframework_dir).each do |sdk, slice_ids| - pod_paths = slice_ids.map do |slice| - %("${PODS_TARGET_SRCROOT}/ios/Vendor/Sentry.xcframework/#{slice}") + paths = slice_ids.map do |slice| + %("#{File.join(sentry_xcframework_dir, slice)}") end - user_paths = slice_ids.map do |slice| - %("${PODS_ROOT}/../../node_modules/@sentry/react-native/ios/Vendor/Sentry.xcframework/#{slice}") - end - pod_search_paths["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = - (['$(inherited)'] + pod_paths).join(' ') - user_search_paths["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = - (['$(inherited)'] + user_paths).join(' ') + xcframework_search_paths["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = + (['$(inherited)'] + paths).join(' ') end - pod_target_xcconfig.merge!(pod_search_paths) - s.user_target_xcconfig = user_search_paths + pod_target_xcconfig.merge!(xcframework_search_paths) + s.user_target_xcconfig = xcframework_search_paths else s.dependency 'Sentry', sentry_cocoa_version end diff --git a/packages/core/ios/RNSentrySwiftLinkStub.swift b/packages/core/ios/RNSentrySwiftLinkStub.swift new file mode 100644 index 0000000000..185eb574f5 --- /dev/null +++ b/packages/core/ios/RNSentrySwiftLinkStub.swift @@ -0,0 +1,7 @@ +// This file exists to force Xcode to link the Swift runtime compatibility +// libraries (e.g. libswiftCompatibility56, libswiftCompatibilityConcurrency) +// into RNSentry. Those libs are required by our vendored `Sentry.xcframework` +// static Swift library and Xcode only auto-links them when the consuming +// target itself contains Swift code — without this stub, linking a dynamic +// RNSentry framework fails with: +// Undefined symbols: "__swift_FORCE_LOAD_$_swiftCompatibility56" From f702c1b838413ea9a06b03bd5e12313d611cf8d9 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 16:16:39 +0200 Subject: [PATCH 15/16] fix(ios): Gate Swift link stub inclusion on RN >= 0.75 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 --- packages/core/RNSentry.podspec | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index b597c8c7c8..c0d5a8b4da 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -45,11 +45,24 @@ Pod::Spec.new do |s| # is pulled in here; on Android it is compiled by the dedicated CMake target # in `android/CMakeLists.txt`. The files are guarded with # `RCT_NEW_ARCH_ENABLED` so they compile to empty TUs on Old Arch. - # `.swift` is here for `RNSentrySwiftLinkStub.swift`, which forces Xcode - # to link the Swift runtime compatibility libs required by our vendored - # Sentry.xcframework static Swift library. - s.source_files = 'ios/**/*.{h,m,mm,swift}', 'cpp/**/*.{h,cpp}' - s.swift_versions = ['5.5'] + # + # We include `.swift` (for `RNSentrySwiftLinkStub.swift`) only on RN >= + # 0.75. Adding a Swift file makes CocoaPods treat RNSentry as a Swift + # pod, which then requires modular headers from its ObjC dependencies + # (React-Core, React-hermes) — RN < 0.75 doesn't emit those, so + # `pod install` fails with: + # "The Swift pod `RNSentry` depends upon `React-hermes`, which does + # not define modules." + # The stub is only needed when linking Sentry.xcframework's Swift + # symbols into a dynamic framework anyway (RN 0.86+ `use_frameworks! + # :dynamic`), so gating on RN 0.75 is safe. + supports_swift_stub = rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 75) + if supports_swift_stub + s.source_files = 'ios/**/*.{h,m,mm,swift}', 'cpp/**/*.{h,cpp}' + s.swift_versions = ['5.5'] + else + s.source_files = 'ios/**/*.{h,m,mm}', 'cpp/**/*.{h,cpp}' + end s.exclude_files = 'ios/Vendor/**/*' s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h', 'ios/RNSentryStart.h', 'ios/RNSentryVersion.h', 'ios/RNSentryBreadcrumb.h', 'ios/RNSentryReplay.h', 'ios/RNSentryReplayBreadcrumbConverter.h', 'ios/Replay/RNSentryReplayMask.h', 'ios/Replay/RNSentryReplayUnmask.h', 'ios/RNSentryTimeToDisplay.h' From 9171a88789222b34f91c6a4d1eaea17298c20d65 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 2 Jul 2026 17:05:19 +0200 Subject: [PATCH 16/16] fix: Address reviewer feedback on CHANGELOG placement and Swift stub - 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 --- CHANGELOG.md | 15 ++++++++++----- packages/core/ios/RNSentrySwiftLinkStub.swift | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819663ba41..17fcd8884d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Changes + +- Consume `sentry-cocoa` as a prebuilt xcframework by default on iOS ([#6381](https://github.com/getsentry/sentry-react-native/pull/6381)) + + **Warning** + + **This may be a breaking change for some setups.** `pod install` now downloads `Sentry.xcframework` from sentry-cocoa's GitHub release (SHA256-verified) and vendors it, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), or if your `pod install` environment cannot reach `github.com`, set `SENTRY_USE_XCFRAMEWORK=0` before `pod install` to restore the previous source-build behavior. + ## 8.17.0 ### Features @@ -29,11 +39,6 @@ ### Changes - Default `mobileReplayIntegration({ networkCaptureBodies })` to `true`, matching the iOS and Android native SDK defaults ([#6372](https://github.com/getsentry/sentry-react-native/pull/6372)) -- Consume `sentry-cocoa` as a prebuilt xcframework by default on iOS ([#6381](https://github.com/getsentry/sentry-react-native/pull/6381)) - - **Warning** - - **This may be a breaking change for some setups.** `pod install` now downloads `Sentry.xcframework` from sentry-cocoa's GitHub release (SHA256-verified) and vendors it, instead of building Sentry from source as a CocoaPod. If your iOS build breaks after upgrading (e.g. when another pod also depends on the `Sentry` CocoaPod), or if your `pod install` environment cannot reach `github.com`, set `SENTRY_USE_XCFRAMEWORK=0` before `pod install` to restore the previous source-build behavior. ### Fixes diff --git a/packages/core/ios/RNSentrySwiftLinkStub.swift b/packages/core/ios/RNSentrySwiftLinkStub.swift index 185eb574f5..7e456ef6c1 100644 --- a/packages/core/ios/RNSentrySwiftLinkStub.swift +++ b/packages/core/ios/RNSentrySwiftLinkStub.swift @@ -5,3 +5,8 @@ // target itself contains Swift code — without this stub, linking a dynamic // RNSentry framework fails with: // Undefined symbols: "__swift_FORCE_LOAD_$_swiftCompatibility56" + +// A private, unused constant so the compiler emits a real object file. A +// pure-comment file compiles to nothing and would defeat the purpose of +// the stub. +private let _rnSentrySwiftLinkStub: Void = ()