-
-
Notifications
You must be signed in to change notification settings - Fork 365
Expand file tree
/
Copy pathsentry_utils.rb
More file actions
235 lines (211 loc) · 10.9 KB
/
Copy pathsentry_utils.rb
File metadata and controls
235 lines (211 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def parse_rn_package_json()
rn_path = File.dirname(`node --print "require.resolve('react-native/package.json')"`)
env_rn_path = ENV['REACT_NATIVE_NODE_MODULES_DIR']
if env_rn_path != nil
rn_path = env_rn_path
end
rn_package_json_path = File.join(rn_path, 'package.json')
if !File.exist?(rn_package_json_path)
raise "React Native `package.json` not found, it doesn't exist in #{rn_package_json_path}, use `REACT_NATIVE_NODE_MODULES_DIR` env variable to specify a custom location"
end
return JSON.parse(File.read(rn_package_json_path))
end
def get_rn_version(package)
version = package['version'].split('.')
result = {
:major => version[0].to_i,
:minor => version[1].to_i,
}
return result
end
def is_hermes_default(rn_version)
return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 70))
end
# before RN 0.69 Hermes was not shipped with RN
# users could use unexpected version of Hermes
def is_profiling_supported(rn_version)
return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 69))
end
# Check if we need the old Folly flags (for RN < 0.80.0)
def should_use_folly_flags(rn_version)
return (rn_version[:major] == 0 && rn_version[:minor] < 80)
end
def is_new_hermes_runtime(rn_version)
return (rn_version[:major] >= 1 || (rn_version[:major] == 0 && rn_version[:minor] >= 81))
end
require 'digest'
require 'fileutils'
require 'open-uri'
# SHA256 checksums of `<product>.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.
#
# Kept in sync with `sentry_cocoa_version` in `RNSentry.podspec` by
# `scripts/update-cocoa.sh set-version <new>`, which downloads the new
# release archive, computes its SHA256, and rewrites the entry below.
SENTRY_COCOA_XCFRAMEWORK_CHECKSUMS = {
# `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.
'9.24.0' => {
'Sentry' => 'c530edd27b20f7c151e73d84a34ee03474e3d5ddab65ffe9d30366f80149668a',
},
}.freeze
# Static map from xcframework slice directory name to the Xcode SDK selector
# it should be attached to. `sentry-cocoa`'s `Sentry.xcframework` layout is
# stable across releases — same slice names come out of every build — so we
# hardcode the mapping rather than scanning the extracted bundle. Add a
# new entry here if `sentry-cocoa` ever ships a new platform slice.
SENTRY_XCFRAMEWORK_SLICES_BY_SDK = {
'iphoneos' => %w[ios-arm64_arm64e],
'iphonesimulator' => %w[ios-arm64_x86_64-simulator],
'maccatalyst' => %w[ios-arm64_arm64e_x86_64-maccatalyst],
'macosx' => %w[macos-arm64_arm64e_x86_64],
'appletvos' => %w[tvos-arm64_arm64e],
'appletvsimulator' => %w[tvos-arm64_x86_64-simulator],
'watchos' => %w[watchos-arm64_arm64_32_arm64e_armv7k],
'watchsimulator' => %w[watchos-arm64_x86_64-simulator],
'xros' => %w[xros-arm64_arm64e],
'xrsimulator' => %w[xros-arm64_x86_64-simulator],
}.freeze
# Ensures `<cache>/<version>/<product>.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. Returns the absolute path to the extracted
# xcframework, which callers pass to `FRAMEWORK_SEARCH_PATHS`.
#
# The cache lives under a user-writable directory (`~/Library/Caches/
# sentry-react-native/xcframeworks/` on macOS by default; override with
# `SENTRY_XCFRAMEWORK_CACHE_DIR`). Cannot live under the pod's own source
# tree because pnpm's isolated store makes `node_modules/@sentry/
# react-native/ios/` read-only, and `Yarn PnP` doesn't materialize the
# package directory at all — writing there fails with `EACCES`.
# Deduplicating cache across multiple RN projects on the same machine
# is a nice side effect.
#
# 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')
cache_root = ENV['SENTRY_XCFRAMEWORK_CACHE_DIR'] ||
File.expand_path('~/Library/Caches/sentry-react-native/xcframeworks')
vendor_dir = File.join(cache_root, version)
target_dir = File.join(vendor_dir, "#{product}.xcframework")
# 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
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
# 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/" \
"#{version}/#{product}.xcframework.zip"
Pod::UI.puts "[Sentry] Downloading #{product} #{version} from GitHub Releases…" if defined?(Pod::UI)
begin
URI.open(url, redirect: true) do |remote|
File.open(zip_path, 'wb') { |f| IO.copy_stream(remote, f) }
end
rescue OpenURI::HTTPError, SocketError, StandardError => e
FileUtils.rm_f(zip_path)
raise "Failed to download #{url}: #{e.class}: #{e.message}"
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)
# 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
# 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/<version>/<product>.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
# `<cwd>/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