Skip to content

Commit a18a0d8

Browse files
committed
Cache prebuilt iOS binaries with SHA1 validation
Adds a shared cache at ~/Library/Caches/ReactNative/ for Hermes, ReactNativeDependencies, and ReactNativeCore prebuilt tarballs. Validates file integrity via Maven SHA1 checksums. If a cached file fails verification, it is re-downloaded and replaced. Shared cache/validation utilities live in ReactNativePodsUtils (utils.rb). hermes-utils.rb has its own copy since it cannot require_relative utils.rb due to differing folder structures between the monorepo and the published npm package.
1 parent 5c49006 commit a18a0d8

4 files changed

Lines changed: 190 additions & 18 deletions

File tree

packages/react-native/scripts/cocoapods/rncore.rb

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,48 @@ def self.podspec_source_download_prebuilt_nightly_tarball(version)
174174
end
175175

176176
def self.download_rncore_tarball(react_native_path, tarball_url, version, configuration)
177-
destination_path = configuration == nil ?
178-
"#{artifacts_dir()}/reactnative-core-#{version}.tar.gz" :
179-
"#{artifacts_dir()}/reactnative-core-#{version}-#{configuration}.tar.gz"
177+
filename = configuration == nil ?
178+
"reactnative-core-#{version}.tar.gz" :
179+
"reactnative-core-#{version}-#{configuration}.tar.gz"
180+
destination_path = "#{artifacts_dir()}/#{filename}"
181+
182+
if File.exist?(destination_path)
183+
rncore_log("Tarball #{filename} already exists in Pods. Skipping download.")
184+
return destination_path
185+
end
180186

181-
unless File.exist?(destination_path)
182-
# Download to a temporary file first so we don't cache incomplete downloads.
187+
`mkdir -p "#{artifacts_dir()}"`
188+
189+
cached_path = File.join(ReactNativePodsUtils.shared_cache_dir(), filename)
190+
if File.exist?(cached_path)
191+
rncore_log("Verifying checksum for cached #{filename}...")
192+
if ReactNativePodsUtils.validate_tarball(cached_path, tarball_url)
193+
rncore_log("Cache hit: copying #{filename} from shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
194+
FileUtils.cp(cached_path, destination_path)
195+
else
196+
rncore_log("Shared cache file #{filename} failed SHA verification. Re-downloading.")
197+
tmp_file = "#{artifacts_dir()}/reactnative-core.download"
198+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
199+
rncore_log("Verifying checksum for downloaded #{filename}...")
200+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
201+
FileUtils.cp(destination_path, cached_path)
202+
rncore_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
203+
else
204+
rncore_log("Downloaded file #{filename} failed SHA verification!", :error)
205+
end
206+
end
207+
else
208+
rncore_log("Cache miss: downloading #{filename} from #{tarball_url}")
183209
tmp_file = "#{artifacts_dir()}/reactnative-core.download"
184-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
210+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
211+
rncore_log("Verifying checksum for downloaded #{filename}...")
212+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
213+
`mkdir -p "#{ReactNativePodsUtils.shared_cache_dir()}"`
214+
FileUtils.cp(destination_path, cached_path)
215+
rncore_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
216+
else
217+
rncore_log("Downloaded file #{filename} failed SHA verification!", :error)
218+
end
185219
end
186220

187221
return destination_path

packages/react-native/scripts/cocoapods/rndependencies.rb

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,48 @@ def self.podspec_source_download_prebuilt_nightly_tarball(version)
209209
end
210210

211211
def self.download_rndeps_tarball(react_native_path, tarball_url, version, configuration)
212-
destination_path = configuration == nil ?
213-
"#{artifacts_dir()}/reactnative-dependencies-#{version}.tar.gz" :
214-
"#{artifacts_dir()}/reactnative-dependencies-#{version}-#{configuration}.tar.gz"
212+
filename = configuration == nil ?
213+
"reactnative-dependencies-#{version}.tar.gz" :
214+
"reactnative-dependencies-#{version}-#{configuration}.tar.gz"
215+
destination_path = "#{artifacts_dir()}/#{filename}"
216+
217+
if File.exist?(destination_path)
218+
rndeps_log("Tarball #{filename} already exists in Pods. Skipping download.")
219+
return destination_path
220+
end
215221

216-
unless File.exist?(destination_path)
217-
# Download to a temporary file first so we don't cache incomplete downloads.
222+
`mkdir -p "#{artifacts_dir()}"`
223+
224+
cached_path = File.join(ReactNativePodsUtils.shared_cache_dir(), filename)
225+
if File.exist?(cached_path)
226+
rndeps_log("Verifying checksum for cached #{filename}...")
227+
if ReactNativePodsUtils.validate_tarball(cached_path, tarball_url)
228+
rndeps_log("Cache hit: copying #{filename} from shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
229+
FileUtils.cp(cached_path, destination_path)
230+
else
231+
rndeps_log("Shared cache file #{filename} failed SHA verification. Re-downloading.")
232+
tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download"
233+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
234+
rndeps_log("Verifying checksum for downloaded #{filename}...")
235+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
236+
FileUtils.cp(destination_path, cached_path)
237+
rndeps_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
238+
else
239+
rndeps_log("Downloaded file #{filename} failed SHA verification!", :error)
240+
end
241+
end
242+
else
243+
rndeps_log("Cache miss: downloading #{filename} from #{tarball_url}")
218244
tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download"
219-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
245+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
246+
rndeps_log("Verifying checksum for downloaded #{filename}...")
247+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
248+
`mkdir -p "#{ReactNativePodsUtils.shared_cache_dir()}"`
249+
FileUtils.cp(destination_path, cached_path)
250+
rndeps_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
251+
else
252+
rndeps_log("Downloaded file #{filename} failed SHA verification!", :error)
253+
end
220254
end
221255

222256
return destination_path

packages/react-native/scripts/cocoapods/utils.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# LICENSE file in the root directory of this source tree.
55

66
require 'shellwords'
7+
require 'digest'
78

89
require_relative "./helpers.rb"
910
require_relative "./jsengine.rb"
@@ -713,4 +714,45 @@ def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil
713714
spec.header_mappings_dir = header_mappings_dir
714715
end
715716
end
717+
718+
# ==================== #
719+
# Shared download cache #
720+
# ==================== #
721+
722+
def self.shared_cache_dir()
723+
return File.join(Dir.home, "Library", "Caches", "ReactNative")
724+
end
725+
726+
def self.fetch_maven_sha1(tarball_url)
727+
sha1 = `curl -sL "#{tarball_url}.sha1"`.strip
728+
return sha1.downcase if $?.success? && sha1.match?(/\A[a-fA-F0-9]{40}\z/)
729+
nil
730+
end
731+
732+
def self.validate_tarball(path, tarball_url)
733+
expected_sha1 = fetch_maven_sha1(tarball_url)
734+
basename = File.basename(path)
735+
if expected_sha1.nil?
736+
cache_log("SHA1 not available from Maven for #{basename}. Skipping validation.")
737+
return true
738+
end
739+
actual_sha1 = Digest::SHA1.file(path).hexdigest
740+
if actual_sha1 == expected_sha1
741+
cache_log("SHA1 verified for #{basename}")
742+
return true
743+
end
744+
cache_log("SHA1 mismatch for #{basename}: expected #{expected_sha1}, got #{actual_sha1}", :error)
745+
return false
746+
end
747+
748+
def self.cache_log(message, level = :info)
749+
return unless Object.const_defined?("Pod::UI")
750+
prefix = '[Cache] '
751+
case level
752+
when :error
753+
Pod::UI.puts prefix.red + message
754+
else
755+
Pod::UI.puts prefix.green + message
756+
end
757+
end
716758
end

packages/react-native/sdks/hermes-engine/hermes-utils.rb

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
require 'net/http'
77
require 'rexml/document'
8+
require 'digest'
89

910
HERMES_GITHUB_URL = "https://github.com/facebook/hermes.git"
1011
ENV_BUILD_FROM_SOURCE = "RCT_BUILD_HERMES_FROM_SOURCE"
@@ -221,16 +222,77 @@ def download_stable_hermes(react_native_path, version, configuration)
221222
download_hermes_tarball(react_native_path, tarball_url, version, configuration)
222223
end
223224

225+
def shared_cache_dir()
226+
return File.join(Dir.home, "Library", "Caches", "ReactNative")
227+
end
228+
229+
def fetch_maven_sha1(tarball_url)
230+
sha1 = `curl -sL "#{tarball_url}.sha1"`.strip
231+
return sha1.downcase if $?.success? && sha1.match?(/\A[a-fA-F0-9]{40}\z/)
232+
nil
233+
end
234+
235+
def validate_hermes_tarball(path, tarball_url)
236+
expected_sha1 = fetch_maven_sha1(tarball_url)
237+
basename = File.basename(path)
238+
if expected_sha1.nil?
239+
hermes_log("SHA1 not available from Maven for #{basename}. Skipping validation.", :info)
240+
return true
241+
end
242+
actual_sha1 = Digest::SHA1.file(path).hexdigest
243+
if actual_sha1 == expected_sha1
244+
hermes_log("SHA1 verified for #{basename}", :info)
245+
return true
246+
end
247+
hermes_log("SHA1 mismatch for #{basename}: expected #{expected_sha1}, got #{actual_sha1}", :error)
248+
return false
249+
end
250+
224251
def download_hermes_tarball(react_native_path, tarball_url, version, configuration)
225-
destination_path = configuration == nil ?
226-
"#{artifacts_dir()}/hermes-ios-#{version}.tar.gz" :
227-
"#{artifacts_dir()}/hermes-ios-#{version}-#{configuration}.tar.gz"
252+
filename = configuration == nil ?
253+
"hermes-ios-#{version}.tar.gz" :
254+
"hermes-ios-#{version}-#{configuration}.tar.gz"
255+
destination_path = "#{artifacts_dir()}/#{filename}"
256+
257+
if File.exist?(destination_path)
258+
hermes_log("Tarball #{filename} already exists in Pods. Skipping download.", :info)
259+
return destination_path
260+
end
228261

229-
unless File.exist?(destination_path)
230-
# Download to a temporary file first so we don't cache incomplete downloads.
262+
`mkdir -p "#{artifacts_dir()}"`
263+
264+
cached_path = File.join(shared_cache_dir(), filename)
265+
if File.exist?(cached_path)
266+
hermes_log("Verifying checksum for cached #{filename}...", :info)
267+
if validate_hermes_tarball(cached_path, tarball_url)
268+
hermes_log("Cache hit: copying #{filename} from shared cache (#{shared_cache_dir()})", :info)
269+
FileUtils.cp(cached_path, destination_path)
270+
else
271+
hermes_log("Shared cache file #{filename} failed SHA verification. Re-downloading.", :info)
272+
tmp_file = "#{artifacts_dir()}/hermes-ios.download"
273+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
274+
hermes_log("Verifying checksum for downloaded #{filename}...", :info)
275+
if validate_hermes_tarball(destination_path, tarball_url)
276+
FileUtils.cp(destination_path, cached_path)
277+
hermes_log("Saved #{filename} to shared cache (#{shared_cache_dir()})", :info)
278+
else
279+
hermes_log("Downloaded file #{filename} failed SHA verification!", :error)
280+
end
281+
end
282+
else
283+
hermes_log("Cache miss: downloading #{filename} from #{tarball_url}", :info)
231284
tmp_file = "#{artifacts_dir()}/hermes-ios.download"
232-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
285+
`curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
286+
hermes_log("Verifying checksum for downloaded #{filename}...", :info)
287+
if validate_hermes_tarball(destination_path, tarball_url)
288+
`mkdir -p "#{shared_cache_dir()}"`
289+
FileUtils.cp(destination_path, cached_path)
290+
hermes_log("Saved #{filename} to shared cache (#{shared_cache_dir()})", :info)
291+
else
292+
hermes_log("Downloaded file #{filename} failed SHA verification!", :error)
293+
end
233294
end
295+
234296
return destination_path
235297
end
236298

0 commit comments

Comments
 (0)