Skip to content

Commit 3eed101

Browse files
alwxclaude
andcommitted
fix(ios): Cache xcframework in ~/Library/Caches, not inside node_modules
Repro on a bare RN app installed via `pnpm install --config.node-linker=isolated` (pnpm's strict store makes package directories read-only): pod install failed with Permission denied @ dir_s_mkdir - .../node_modules/.pnpm/ @sentry+react-native.../ios/Vendor Under pnpm's isolated store (and Yarn PnP), the SDK package directory is either a read-only symlink into a shared store or not materialized on disk at all — writing our xcframework cache there is not portable. Move the cache to `~/Library/Caches/sentry-react-native/xcframeworks/ <version>/` (macOS user cache, always writable, deduped across projects on the same machine). Override with `SENTRY_XCFRAMEWORK_CACHE_DIR` for CI or enterprise setups. Also drop `s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework'`. It was decorative — CocoaPods doesn't stage vendored xcframeworks for `path:` (development) pods, which is why we already had to wire FRAMEWORK_SEARCH_PATHS manually. Linking still happens: clang's `-fmodules-autolink` (default) emits the `-framework Sentry` directive as soon as `@import Sentry;` in RNSentry.mm resolves the module through the search paths. With the vendored_frameworks entry gone, the podspec no longer points at a path that only existed inside node_modules. Verified locally against a bare RN 0.86 sample installed via pnpm strict + chmod a-w on the package's ios/ directory: pod install succeeds, build succeeds, app boots, native crash → dSYM upload path still works. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1d96e69 commit 3eed101

3 files changed

Lines changed: 36 additions & 14 deletions

File tree

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,6 @@ node_modules.bak
9191
# API Extractor temp files
9292
/packages/core/temp/
9393

94-
# sentry-cocoa xcframework downloaded at pod install time
95-
/packages/core/ios/Vendor/
96-
9794
# Sentry React Native Monorepo
9895
/packages/core/README.md
9996
.env.sentry-build-plugin

packages/core/RNSentry.podspec

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ Pod::Spec.new do |s|
6363
else
6464
s.source_files = 'ios/**/*.{h,m,mm}', 'cpp/**/*.{h,cpp}'
6565
end
66-
s.exclude_files = 'ios/Vendor/**/*'
6766
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'
6867

6968
s.compiler_flags = other_cflags
@@ -77,12 +76,14 @@ Pod::Spec.new do |s|
7776
# Consume sentry-cocoa as a prebuilt `Sentry.xcframework` by default.
7877
#
7978
# The xcframework is downloaded from sentry-cocoa's GitHub Release,
80-
# SHA256-verified, and cached under `ios/Vendor/`. CocoaPods then links it
81-
# via `s.vendored_frameworks`. This avoids compiling sentry-cocoa from
82-
# source (fast install) and sidesteps the Xcode 16/26 archive bug that
83-
# affects the same xcframework when consumed through Xcode's SPM
84-
# integration (`Signatures/*.signature` collision during archive) — the
85-
# CocoaPods embed path is a different pipeline and is not affected.
79+
# SHA256-verified, and cached under `~/Library/Caches/sentry-react-native/
80+
# xcframeworks/<version>/` (override with `SENTRY_XCFRAMEWORK_CACHE_DIR`).
81+
# Slice paths are then wired into the pod and app target via
82+
# `FRAMEWORK_SEARCH_PATHS` below. Consuming the xcframework this way
83+
# avoids compiling sentry-cocoa from source (fast install) and
84+
# sidesteps the Xcode 16/26 archive bug that affects the same
85+
# xcframework when consumed through Xcode's SPM integration
86+
# (`Signatures/*.signature` collision during archive).
8687
#
8788
# Set `SENTRY_USE_XCFRAMEWORK=0` to fall back to the source-built
8889
# `Sentry` CocoaPod (e.g. for offline builds behind a restrictive proxy).
@@ -102,7 +103,19 @@ Pod::Spec.new do |s|
102103

103104
if use_xcframework
104105
sentry_xcframework_dir = ensure_sentry_xcframework(sentry_cocoa_version, 'Sentry')
105-
s.vendored_frameworks = 'ios/Vendor/Sentry.xcframework'
106+
107+
# We deliberately do NOT set `s.vendored_frameworks` even though the
108+
# xcframework is downloaded and referenced below. CocoaPods doesn't
109+
# stage vendored xcframeworks for `path:` (development) pods anyway
110+
# — it did nothing for us functionally — and pointing it at a path
111+
# inside `node_modules/@sentry/react-native/ios/Vendor/` used to be
112+
# actively harmful, because that path only existed when the SDK was
113+
# cached into the package directory (broken under pnpm's isolated
114+
# store / Yarn PnP where node_modules is read-only). Framework
115+
# linking still happens: `@import Sentry;` in RNSentry.mm + user
116+
# code triggers clang's `-fmodules-autolink`, which emits the
117+
# `-framework Sentry` linker directive automatically once the
118+
# module is discovered via `FRAMEWORK_SEARCH_PATHS` below.
106119

107120
# Xcode's `-F <dir>` doesn't descend into `.xcframework` bundles — it
108121
# looks for `Sentry.framework` directly at the given path. Point a

packages/core/scripts/sentry_utils.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,30 @@ def is_new_hermes_runtime(rn_version)
8383
'xrsimulator' => %w[xros-arm64_x86_64-simulator],
8484
}.freeze
8585

86-
# Ensures `<sdk_root>/ios/Vendor/<product>.xcframework` exists.
86+
# Ensures `<cache>/<version>/<product>.xcframework` exists.
8787
#
8888
# On first invocation, downloads the prebuilt xcframework zip from
8989
# sentry-cocoa's GitHub release, verifies its SHA256 checksum against
9090
# `SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS`, and extracts it. Subsequent
91-
# invocations are no-ops.
91+
# invocations are no-ops. Returns the absolute path to the extracted
92+
# xcframework, which callers pass to `FRAMEWORK_SEARCH_PATHS`.
93+
#
94+
# The cache lives under a user-writable directory (`~/Library/Caches/
95+
# sentry-react-native/xcframeworks/` on macOS by default; override with
96+
# `SENTRY_XCFRAMEWORK_CACHE_DIR`). Cannot live under the pod's own source
97+
# tree because pnpm's isolated store makes `node_modules/@sentry/
98+
# react-native/ios/` read-only, and `Yarn PnP` doesn't materialize the
99+
# package directory at all — writing there fails with `EACCES`.
100+
# Deduplicating cache across multiple RN projects on the same machine
101+
# is a nice side effect.
92102
#
93103
# Consuming sentry-cocoa this way (vs. through Xcode's SPM integration)
94104
# avoids the Xcode 16/26 archive bug where a signed SPM binary xcframework's
95105
# `Signatures/*.signature` file collides during the archive step.
96106
def ensure_sentry_xcframework(version, product = 'Sentry')
97-
vendor_dir = File.expand_path('../ios/Vendor', __dir__)
107+
cache_root = ENV['SENTRY_XCFRAMEWORK_CACHE_DIR'] ||
108+
File.expand_path('~/Library/Caches/sentry-react-native/xcframeworks')
109+
vendor_dir = File.join(cache_root, version)
98110
target_dir = File.join(vendor_dir, "#{product}.xcframework")
99111
# Treat the presence of `Info.plist` inside the xcframework as the "healthy"
100112
# sentinel rather than just the directory existence. A directory without

0 commit comments

Comments
 (0)