diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea29b7f08..26ddec1bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ When a root span ends (idle nav spans from `reactNavigationIntegration` / `expoRouterIntegration`, or a user's own `Sentry.startSpan(...)`), the integration writes `turbo_module...{call_count,duration_ms,error_count}` attributes plus summary keys (`turbo_module.total_call_count`, `turbo_module.total_duration_ms`, `turbo_module.top_module`). Async calls above `slowCallThresholdMs` (default 500ms) additionally record a `native.turbo_module` breadcrumb. Both surfaces enabled by default; new knobs `enableSpanAttribution`, `slowCallThresholdMs`, `maxTopModulesPerSpan` on `turboModuleContextIntegration`. +### Fixes + +- Make the `RNSentry` SPEC CHECKSUM in `Podfile.lock` machine-independent ([#6474](https://github.com/getsentry/sentry-react-native/pull/6474)) + + The prebuilt `Sentry.xcframework` is now referenced through a `$(PODS_ROOT)/sentry-xcframeworks/…` symlink instead of the absolute per-user cache path, so `Podfile.lock` no longer churns between developers and CI. Expect a one-time `RNSentry` checksum change on the next `pod install`; the `SENTRY_XCFRAMEWORK_CACHE_DIR=/tmp/…` workaround is no longer needed. + ### Changes - Expose `instrumentStateGraph` for manual LangGraph instrumentation ([#6520](https://github.com/getsentry/sentry-react-native/pull/6520)) diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 4162b140dd..f2a8ac0601 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -130,16 +130,25 @@ Pod::Spec.new do |s| # sentry-cocoa's `Sentry.xcframework` layout is stable across releases. # Add a slice there if a future release ships one. # - # 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. + # Reference the xcframework through a `Pods/sentry-xcframeworks/…` + # symlink so the search paths are machine-independent (see + # `stage_sentry_xcframework_in_pods`) — the values below feed the + # `RNSentry` SPEC CHECKSUM in `Podfile.lock`, so an absolute + # `~/Library/Caches/…` path here made the lockfile churn between + # machines (#6467). `${PODS_TARGET_SRCROOT}` can't be used instead: it + # is only defined in per-pod xcconfigs, not in aggregate/user-target + # xcconfigs, and a `${PODS_ROOT}`-relative path to node_modules works + # for one Podfile layout but breaks for another (e.g. the RN sample + # apps put node_modules at a different depth from RNSentryCocoaTester). + # When the link can't be staged — or the podspec is evaluated outside a + # `pod install`, where `$(PODS_ROOT)` would point at an unrelated + # sandbox — fall back to the absolute pod-install-time path: functional, + # but with a machine-specific checksum. + sentry_xcframework_ref = + stage_sentry_xcframework_in_pods(sentry_xcframework_dir, sentry_cocoa_version, 'Sentry') || + sentry_xcframework_dir xcframework_search_paths = SENTRY_XCFRAMEWORK_SLICES_BY_SDK.each_with_object({}) do |(sdk, slice_ids), acc| - paths = slice_ids.map { |slice| %("#{File.join(sentry_xcframework_dir, slice)}") } + paths = slice_ids.map { |slice| %("#{File.join(sentry_xcframework_ref, slice)}") } acc["FRAMEWORK_SEARCH_PATHS[sdk=#{sdk}*]"] = (['$(inherited)'] + paths).join(' ') end diff --git a/packages/core/scripts/sentry_utils.rb b/packages/core/scripts/sentry_utils.rb index 43a778c019..82cb751995 100644 --- a/packages/core/scripts/sentry_utils.rb +++ b/packages/core/scripts/sentry_utils.rb @@ -165,3 +165,71 @@ def ensure_sentry_xcframework(version, product = 'Sentry') target_dir end +# Directory inside the CocoaPods sandbox (`Pods/`) that holds the symlinks to +# the cached xcframeworks staged by `stage_sentry_xcframework_in_pods`. +SENTRY_XCFRAMEWORK_STAGING_DIR = 'sentry-xcframeworks'.freeze + +# Stage the cached xcframework behind a machine-independent `$(PODS_ROOT)` +# reference. +# +# Everything assigned to `pod_target_xcconfig`/`user_target_xcconfig` is part +# of the evaluated spec, and CocoaPods derives the pod's `SPEC CHECKSUM` in +# `Podfile.lock` from exactly that evaluated spec. Pointing +# `FRAMEWORK_SEARCH_PATHS` at the per-user cache directory +# (`~/Library/Caches/…`) therefore leaked `$HOME` into the checksum: two +# machines installing the same SDK version produced different `RNSentry` +# checksums and `Podfile.lock` churned on every `pod install` (#6467). +# +# Instead, symlink `Pods/sentry-xcframeworks//.xcframework` +# to the cached bundle and reference it as +# `$(PODS_ROOT)/sentry-xcframeworks/…` — a string that is identical on every +# machine (including ones overriding `SENTRY_XCFRAMEWORK_CACHE_DIR`). +# `$(PODS_ROOT)` is defined by CocoaPods in both the per-pod and the +# user/aggregate xcconfigs, unlike `$(PODS_TARGET_SRCROOT)`, and the link +# lives inside `Pods/` so no Podfile-layout detection is needed. +# +# Returns the `$(PODS_ROOT)`-based path, or nil when the link can't be staged +# — callers must then fall back to the absolute cache path (previous +# behaviour, functional but with a machine-specific checksum). +def stage_sentry_xcframework_in_pods(xcframework_dir, version, product = 'Sentry') + # Only stage when we're inside a real `pod install`, i.e. CocoaPods has + # located a Podfile and `sandbox_root` therefore points at the `Pods/` + # directory of the project that will consume the xcconfig. Anywhere else + # (`pod ipc spec`, `pod lib lint`, plain `Specification.from_file`) the + # sandbox root is unrelated to that project — CocoaPods evaluates a podspec + # with the CWD set to the podspec's own directory, so a guessed + # `/Pods` would stage the link inside `node_modules/@sentry/ + # react-native/` and `$(PODS_ROOT)/…` would dangle at build time. Bail out + # and let the caller use the absolute path, which is always correct. + return nil unless defined?(Pod::Config) && + Pod::Config.instance.respond_to?(:podfile_path) && + Pod::Config.instance.podfile_path + + staging_root = File.join(Pod::Config.instance.sandbox_root.to_s, SENTRY_XCFRAMEWORK_STAGING_DIR) + staging_dir = File.join(staging_root, version) + link_path = File.join(staging_dir, "#{product}.xcframework") + + FileUtils.mkdir_p(staging_dir) + # Recreate the link when it's missing or points at a stale location (e.g. + # `SENTRY_XCFRAMEWORK_CACHE_DIR` changed between installs). `rm_rf` unlinks + # the symlink itself, it does not follow it into the shared cache. + unless File.symlink?(link_path) && File.readlink(link_path) == xcframework_dir + FileUtils.rm_rf(link_path) + File.symlink(xcframework_dir, link_path) + end + + # Drop links staged for other sentry-cocoa versions (left behind by SDK + # upgrades) so the directory doesn't collect dangling symlinks. + Dir.glob(File.join(staging_root, '*')).each do |stale| + FileUtils.rm_rf(stale) unless File.basename(stale) == version + end + + "$(PODS_ROOT)/#{SENTRY_XCFRAMEWORK_STAGING_DIR}/#{version}/#{product}.xcframework" +rescue StandardError, NotImplementedError => e + if defined?(Pod::UI) + Pod::UI.warn "[Sentry] Could not link the #{product} xcframework into Pods/ " \ + "(#{e.class}: #{e.message}). Falling back to the absolute cache path; " \ + "the RNSentry checksum in Podfile.lock will be machine-specific." + end + nil +end