Skip to content

Commit f2880fd

Browse files
cipolleschifabriziocucci
authored andcommitted
Cache prebuilt iOS binaries in ~/Library/Caches/ReactNative (#56847)
Summary: Adds a shared cache layer at `~/Library/Caches/ReactNative/` for prebuilt iOS tarballs (Hermes, ReactNativeDependencies, ReactNativeCore). - On first download, tarballs are saved to both the local Pods artifacts dir **and** the shared cache. - On subsequent `pod install` runs, if the local Pods cache is empty but the shared cache has the tarball for that version, it is copied locally instead of re-downloaded from Maven. - Adds descriptive log messages for each scenario: local Pods hit, shared cache hit, and cache miss (download). This avoids redundant downloads when: - The Pods directory is deleted between installs - Multiple projects use the same React Native version - CI environments have a persistent home directory ## Changelog: [IOS] [CHANGED] - Cache prebuilt iOS binaries (Hermes, ReactNativeDependencies, ReactNativeCore) in ~/Library/Caches/ReactNative to avoid redundant downloads across pod installs Pull Request resolved: #56847 Test Plan: ``` [ReactNativeDependencies] Setting up ReactNativeDependencies... [ReactNativeDependencies] Building from source: false [ReactNativeDependencies] Using release tarball [ReactNativeDependencies] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz [ReactNativeDependencies] Cache miss: downloading reactnative-dependencies-0.81.6-debug.tar.gz from https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:- 0 0 0 0 0 0 0 0 --:--:-- --:- 35 18.3M 35 6580k 0 0 6386k 0 0:00:02 0:0 73 18.3M 73 13.5M 0 0 6827k 0 0:00:02 0:0100 18.3M 100 18.3M 0 0 6915k 0 0:00:02 0:00:02 --:--:-- 6914k [ReactNativeDependencies] Saved reactnative-dependencies-0.81.6-debug.tar.gz to shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeDependencies] Cache miss: downloading reactnative-dependencies-0.81.6-release.tar.gz from https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-release.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:- 0 0 0 0 0 0 0 0 --:--:-- --:- 67 9.9M 67 6948k 0 0 6332k 0 0:00:01 0:0100 9.9M 100 9.9M 0 0 6607k 0 0:00:01 0:00:01 --:--:-- 6603k [ReactNativeDependencies] Saved reactnative-dependencies-0.81.6-release.tar.gz to shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeDependencies] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz"} [ReactNativeCore] Setting up ReactNativeCore... [ReactNativeCore] Building from source: false [ReactNativeCore] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz [ReactNativeCore] Cache miss: downloading reactnative-core-0.81.6-debug.tar.gz from https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:- 1 78.5M 1 1557k 0 0 5035k 0 0:00:15 --:- 10 78.5M 10 8537k 0 0 6536k 0 0:00:12 0:0 19 78.5M 19 15.4M 0 0 6866k 0 0:00:11 0:0 28 78.5M 28 22.5M 0 0 6990k 0 0:00:11 0:0 37 78.5M 37 29.7M 0 0 7062k 0 0:00:11 0:0 46 78.5M 46 36.4M 0 0 7037k 0 0:00:11 0:0 55 78.5M 55 43.5M 0 0 7065k 0 0:00:11 0:0 64 78.5M 64 50.5M 0 0 7077k 0 0:00:11 0:0100 78.5M 100 78.5M 0 0 6852k 0 0:00:11 0:00:11 --:--:-- 6482k [ReactNativeCore] Saved reactnative-core-0.81.6-debug.tar.gz to shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeCore] Cache miss: downloading reactnative-core-0.81.6-release.tar.gz from https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-release.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 26.3M 100 26.3M 0 0 5683k 0 0:00:04 0:00:04 --:--:-- 5793k [ReactNativeCore] Saved reactnative-core-0.81.6-release.tar.gz to shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeCore] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz"} Configuring the target with the New Architecture [ReactNativeCore] Using React Native Core and React Native Dependencies prebuilt versions. [Codegen] Analyzing /Users/cipolleschi/Tests/My0_81App/package.json ``` ``` [ReactNativeDependencies] Setting up ReactNativeDependencies... [ReactNativeDependencies] Building from source: false [ReactNativeDependencies] Using release tarball [ReactNativeDependencies] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz [ReactNativeDependencies] Cache hit: copying reactnative-dependencies-0.81.6-debug.tar.gz from shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeDependencies] Cache hit: copying reactnative-dependencies-0.81.6-release.tar.gz from shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeDependencies] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz"} [ReactNativeCore] Setting up ReactNativeCore... [ReactNativeCore] Building from source: false [ReactNativeCore] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz [ReactNativeCore] Cache hit: copying reactnative-core-0.81.6-debug.tar.gz from shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeCore] Cache hit: copying reactnative-core-0.81.6-release.tar.gz from shared cache (/Users/cipolleschi/Library/Caches/ReactNative) [ReactNativeCore] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz"} Configuring the target with the New Architecture [ReactNativeCore] Using React Native Core and React Native Dependencies prebuilt versions. [Codegen] Analyzing /Users/cipolleschi/Tests/My0_81App/package.json ... [ReactNativeDependencies] Using release tarball [ReactNativeDependencies] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-debug.tar.gz already exists in Pods. Skipping download. [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-release.tar.gz already exists in Pods. Skipping download. ``` ``` [ReactNativeDependencies] Setting up ReactNativeDependencies... [ReactNativeDependencies] Building from source: false [ReactNativeDependencies] Using release tarball [ReactNativeDependencies] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-debug.tar.gz already exists in Pods. Skipping download. [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-release.tar.gz already exists in Pods. Skipping download. [ReactNativeDependencies] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz"} [ReactNativeCore] Setting up ReactNativeCore... [ReactNativeCore] Building from source: false [ReactNativeCore] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz [ReactNativeCore] Tarball reactnative-core-0.81.6-debug.tar.gz already exists in Pods. Skipping download. [ReactNativeCore] Tarball reactnative-core-0.81.6-release.tar.gz already exists in Pods. Skipping download. [ReactNativeCore] Source: {http: "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-core-debug.tar.gz"} Configuring the target with the New Architecture [ReactNativeCore] Using React Native Core and React Native Dependencies prebuilt versions. [Codegen] Analyzing /Users/cipolleschi/Tests/My0_81App/package.json [Codegen] Searching for codegen-enabled libraries in the app. [Codegen] The "codegenConfig" field is not defined in package.json. Assuming there is nothing to generate at the app level. [Codegen] Searching for codegen-enabled libraries in react-native.config.js [Codegen] Found react-native-safe-area-context [Codegen] Processing safeareacontext [Codegen] Searching for podspec in the project dependencies. [Codegen] Supported Apple platforms: ios, macos, tvos, visionos for safeareacontext [Codegen] Generating Native Code for safeareacontext - ios [Codegen] Generated artifacts: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios [Codegen] Generating RCTThirdPartyComponentsProvider.h [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTThirdPartyComponentsProvider.h [Codegen] Generating RCTThirdPartyComponentsProvider.mm [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTThirdPartyComponentsProvider.mm [Codegen] Generating RCTModulesProvider.h [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTModuleProviders.h [Codegen] Generating RCTModuleProviders.mm [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTModuleProviders.mm [Codegen] Generating RCTAppDependencyProvider [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTAppDependencyProvider.h [Codegen] Generated artifact: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/RCTAppDependencyProvider.mm [Codegen] Generated podspec: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/ReactAppDependencyProvider.podspec [Codegen] Generated podspec: /Users/cipolleschi/Tests/My0_81App/ios/build/generated/ios/ReactCodegen.podspec [Codegen] Done. [ReactNativeDependencies] Using release tarball [ReactNativeDependencies] Using tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.6/react-native-artifacts-0.81.6-reactnative-dependencies-debug.tar.gz [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-debug.tar.gz already exists in Pods. Skipping download. [ReactNativeDependencies] Tarball reactnative-dependencies-0.81.6-release.tar.gz already exists in Pods. Skipping download. Analyzing dependencies ``` Reviewed By: cortinico Differential Revision: D105315202 Pulled By: cipolleschi fbshipit-source-id: 3196d589f59b8edc75e62752d0c61c3618c1be90
1 parent 443ac40 commit f2880fd

5 files changed

Lines changed: 259 additions & 18 deletions

File tree

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -406,17 +406,69 @@ def self.download_nightly_rncore(react_native_path, version, configuration, dsym
406406
end
407407

408408
def self.download_rncore_tarball(react_native_path, tarball_url, version, configuration, dsyms = false)
409-
destination_path = configuration == nil ?
410-
"#{artifacts_dir()}/reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}.tar.gz" :
411-
"#{artifacts_dir()}/reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}-#{configuration}.tar.gz"
409+
filename = configuration == nil ?
410+
"reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}.tar.gz" :
411+
"reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}-#{configuration}.tar.gz"
412+
destination_path = "#{artifacts_dir()}/#{filename}"
413+
414+
if File.exist?(destination_path)
415+
rncore_log("Tarball #{filename} already exists in Pods. Skipping download.")
416+
return destination_path
417+
end
412418

413-
unless File.exist?(destination_path)
414-
# Download to a temporary file first so we don't cache incomplete downloads.
415-
rncore_log("Downloading ReactNativeCore-prebuilt #{dsyms ? "dSYMs " : ""}#{configuration ? configuration.to_s : ""} tarball from #{tarball_url} to #{Pathname.new(destination_path).relative_path_from(Pathname.pwd).to_s}")
419+
`mkdir -p "#{artifacts_dir()}"`
420+
421+
if ReactNativePodsUtils.skip_caches?
422+
rncore_log("RCT_SKIP_CACHES is set. Downloading #{filename} directly (bypassing shared cache).")
416423
tmp_file = "#{artifacts_dir()}/reactnative-core.download"
417-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
424+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
425+
unless File.exist?(destination_path)
426+
abort("[ReactNativeCore] Failed to download #{filename} from #{tarball_url}. Aborting.")
427+
end
428+
return destination_path
429+
end
430+
431+
cached_path = File.join(ReactNativePodsUtils.shared_cache_dir(), filename)
432+
if File.exist?(cached_path)
433+
rncore_log("Verifying checksum for cached #{filename}...")
434+
if ReactNativePodsUtils.validate_tarball(cached_path, tarball_url)
435+
rncore_log("Cache hit: copying #{filename} from shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
436+
FileUtils.cp(cached_path, destination_path)
437+
else
438+
rncore_log("Shared cache file #{filename} failed SHA verification. Re-downloading.")
439+
File.delete(cached_path)
440+
tmp_file = "#{artifacts_dir()}/reactnative-core.download"
441+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
442+
unless File.exist?(destination_path)
443+
abort("[ReactNativeCore] Failed to download #{filename} from #{tarball_url}. Aborting.")
444+
end
445+
rncore_log("Verifying checksum for downloaded #{filename}...")
446+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
447+
FileUtils.cp(destination_path, cached_path)
448+
rncore_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
449+
else
450+
File.delete(destination_path) if File.exist?(destination_path)
451+
abort("[ReactNativeCore] Downloaded file #{filename} failed SHA verification. Aborting.")
452+
end
453+
end
418454
else
419-
rncore_log("Using downloaded ReactNativeCore-prebuilt #{dsyms ? "dSYMs " : ""}#{configuration ? configuration.to_s : ""} tarball at #{Pathname.new(destination_path).relative_path_from(Pathname.pwd).to_s}")
455+
rncore_log("Cache miss: downloading #{filename} from #{tarball_url}")
456+
# Download to a temporary file first so we don't cache incomplete downloads.
457+
tmp_file = "#{artifacts_dir()}/reactnative-core.download"
458+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
459+
unless File.exist?(destination_path)
460+
abort("[ReactNativeCore] Failed to download #{filename} from #{tarball_url}. Aborting.")
461+
end
462+
rncore_log("Verifying checksum for downloaded #{filename}...")
463+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
464+
# Save to shared cache for future use
465+
`mkdir -p "#{ReactNativePodsUtils.shared_cache_dir()}"`
466+
FileUtils.cp(destination_path, cached_path)
467+
rncore_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
468+
else
469+
File.delete(destination_path) if File.exist?(destination_path)
470+
abort("[ReactNativeCore] Downloaded file #{filename} failed SHA verification. Aborting.")
471+
end
420472
end
421473

422474
return destination_path

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

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,69 @@ def self.podspec_source_download_prebuilt_nightly_tarball(version)
232232
end
233233

234234
def self.download_rndeps_tarball(react_native_path, tarball_url, version, configuration)
235-
destination_path = configuration == nil ?
236-
"#{artifacts_dir()}/reactnative-dependencies-#{version}.tar.gz" :
237-
"#{artifacts_dir()}/reactnative-dependencies-#{version}-#{configuration}.tar.gz"
235+
filename = configuration == nil ?
236+
"reactnative-dependencies-#{version}.tar.gz" :
237+
"reactnative-dependencies-#{version}-#{configuration}.tar.gz"
238+
destination_path = "#{artifacts_dir()}/#{filename}"
239+
240+
if File.exist?(destination_path)
241+
rndeps_log("Tarball #{filename} already exists in Pods. Skipping download.")
242+
return destination_path
243+
end
244+
245+
`mkdir -p "#{artifacts_dir()}"`
246+
247+
if ReactNativePodsUtils.skip_caches?
248+
rndeps_log("RCT_SKIP_CACHES is set. Downloading #{filename} directly (bypassing shared cache).")
249+
tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download"
250+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
251+
unless File.exist?(destination_path)
252+
abort("[ReactNativeDependencies] Failed to download #{filename} from #{tarball_url}. Aborting.")
253+
end
254+
return destination_path
255+
end
238256

239-
unless File.exist?(destination_path)
257+
cached_path = File.join(ReactNativePodsUtils.shared_cache_dir(), filename)
258+
if File.exist?(cached_path)
259+
rndeps_log("Verifying checksum for cached #{filename}...")
260+
if ReactNativePodsUtils.validate_tarball(cached_path, tarball_url)
261+
rndeps_log("Cache hit: copying #{filename} from shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
262+
FileUtils.cp(cached_path, destination_path)
263+
else
264+
rndeps_log("Shared cache file #{filename} failed SHA verification. Re-downloading.")
265+
File.delete(cached_path)
266+
tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download"
267+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
268+
unless File.exist?(destination_path)
269+
abort("[ReactNativeDependencies] Failed to download #{filename} from #{tarball_url}. Aborting.")
270+
end
271+
rndeps_log("Verifying checksum for downloaded #{filename}...")
272+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
273+
FileUtils.cp(destination_path, cached_path)
274+
rndeps_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
275+
else
276+
File.delete(destination_path) if File.exist?(destination_path)
277+
abort("[ReactNativeDependencies] Downloaded file #{filename} failed SHA verification. Aborting.")
278+
end
279+
end
280+
else
281+
rndeps_log("Cache miss: downloading #{filename} from #{tarball_url}")
240282
# Download to a temporary file first so we don't cache incomplete downloads.
241283
tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download"
242-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
284+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
285+
unless File.exist?(destination_path)
286+
abort("[ReactNativeDependencies] Failed to download #{filename} from #{tarball_url}. Aborting.")
287+
end
288+
rndeps_log("Verifying checksum for downloaded #{filename}...")
289+
if ReactNativePodsUtils.validate_tarball(destination_path, tarball_url)
290+
# Save to shared cache for future use
291+
`mkdir -p "#{ReactNativePodsUtils.shared_cache_dir()}"`
292+
FileUtils.cp(destination_path, cached_path)
293+
rndeps_log("Saved #{filename} to shared cache (#{ReactNativePodsUtils.shared_cache_dir()})")
294+
else
295+
File.delete(destination_path) if File.exist?(destination_path)
296+
abort("[ReactNativeDependencies] Downloaded file #{filename} failed SHA verification. Aborting.")
297+
end
243298
end
244299

245300
return destination_path

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

Lines changed: 46 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"
@@ -727,4 +728,49 @@ def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil
727728
spec.header_mappings_dir = header_mappings_dir
728729
end
729730
end
731+
732+
# ==================== #
733+
# Shared download cache #
734+
# ==================== #
735+
736+
def self.skip_caches?
737+
ENV['RCT_SKIP_CACHES'] == '1'
738+
end
739+
740+
def self.shared_cache_dir()
741+
return File.join(Dir.home, "Library", "Caches", "ReactNative")
742+
end
743+
744+
def self.fetch_maven_sha1(tarball_url)
745+
sha1 = `curl -sL "#{tarball_url}.sha1"`.strip
746+
return sha1.downcase if $?.success? && sha1.match?(/\A[a-fA-F0-9]{40}\z/)
747+
nil
748+
end
749+
750+
def self.validate_tarball(path, tarball_url)
751+
expected_sha1 = fetch_maven_sha1(tarball_url)
752+
basename = File.basename(path)
753+
if expected_sha1.nil?
754+
cache_log("SHA1 not available from Maven for #{basename}. Skipping validation.")
755+
return true
756+
end
757+
actual_sha1 = Digest::SHA1.file(path).hexdigest
758+
if actual_sha1 == expected_sha1
759+
cache_log("SHA1 verified for #{basename}")
760+
return true
761+
end
762+
cache_log("SHA1 mismatch for #{basename}: expected #{expected_sha1}, got #{actual_sha1}", :error)
763+
return false
764+
end
765+
766+
def self.cache_log(message, level = :info)
767+
return unless Object.const_defined?("Pod::UI")
768+
prefix = '[Cache] '
769+
case level
770+
when :error
771+
Pod::UI.puts prefix.red + message
772+
else
773+
Pod::UI.puts prefix.green + message
774+
end
775+
end
730776
end

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

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# This source code is licensed under the MIT license found in the
44
# LICENSE file in the root directory of this source tree.
55

6+
require 'digest'
67
require 'net/http'
78
require 'rexml/document'
89

@@ -236,16 +237,102 @@ def download_stable_hermes(react_native_path, version, configuration)
236237
download_hermes_tarball(react_native_path, tarball_url, version, configuration)
237238
end
238239

240+
def shared_cache_dir()
241+
return File.join(Dir.home, "Library", "Caches", "ReactNative")
242+
end
243+
244+
def fetch_maven_sha1(tarball_url)
245+
sha1 = `curl -sL "#{tarball_url}.sha1"`.strip
246+
return sha1.downcase if $?.success? && sha1.match?(/\A[a-fA-F0-9]{40}\z/)
247+
nil
248+
end
249+
250+
def skip_caches?
251+
ENV['RCT_SKIP_CACHES'] == '1'
252+
end
253+
254+
def validate_hermes_tarball(path, tarball_url)
255+
expected_sha1 = fetch_maven_sha1(tarball_url)
256+
basename = File.basename(path)
257+
if expected_sha1.nil?
258+
hermes_log("SHA1 not available from Maven for #{basename}. Skipping validation.", :info)
259+
return true
260+
end
261+
actual_sha1 = Digest::SHA1.file(path).hexdigest
262+
if actual_sha1 == expected_sha1
263+
hermes_log("SHA1 verified for #{basename}", :info)
264+
return true
265+
end
266+
hermes_log("SHA1 mismatch for #{basename}: expected #{expected_sha1}, got #{actual_sha1}", :error)
267+
return false
268+
end
269+
239270
def download_hermes_tarball(react_native_path, tarball_url, version, configuration)
240-
destination_path = configuration == nil ?
241-
"#{artifacts_dir()}/hermes-ios-#{version}.tar.gz" :
242-
"#{artifacts_dir()}/hermes-ios-#{version}-#{configuration}.tar.gz"
271+
filename = configuration == nil ?
272+
"hermes-ios-#{version}.tar.gz" :
273+
"hermes-ios-#{version}-#{configuration}.tar.gz"
274+
destination_path = "#{artifacts_dir()}/#{filename}"
275+
276+
if File.exist?(destination_path)
277+
hermes_log("Tarball #{filename} already exists in Pods. Skipping download.", :info)
278+
return destination_path
279+
end
280+
281+
`mkdir -p "#{artifacts_dir()}"`
282+
283+
if skip_caches?
284+
hermes_log("RCT_SKIP_CACHES is set. Downloading #{filename} directly (bypassing shared cache).", :info)
285+
tmp_file = "#{artifacts_dir()}/hermes-ios.download"
286+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
287+
unless File.exist?(destination_path)
288+
abort("[Hermes] Failed to download #{filename} from #{tarball_url}. Aborting.")
289+
end
290+
return destination_path
291+
end
243292

244-
unless File.exist?(destination_path)
293+
cached_path = File.join(shared_cache_dir(), filename)
294+
if File.exist?(cached_path)
295+
hermes_log("Verifying checksum for cached #{filename}...", :info)
296+
if validate_hermes_tarball(cached_path, tarball_url)
297+
hermes_log("Cache hit: copying #{filename} from shared cache (#{shared_cache_dir()})", :info)
298+
FileUtils.cp(cached_path, destination_path)
299+
else
300+
hermes_log("Shared cache file #{filename} failed SHA verification. Re-downloading.", :info)
301+
File.delete(cached_path)
302+
tmp_file = "#{artifacts_dir()}/hermes-ios.download"
303+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
304+
unless File.exist?(destination_path)
305+
abort("[Hermes] Failed to download #{filename} from #{tarball_url}. Aborting.")
306+
end
307+
hermes_log("Verifying checksum for downloaded #{filename}...", :info)
308+
if validate_hermes_tarball(destination_path, tarball_url)
309+
FileUtils.cp(destination_path, cached_path)
310+
hermes_log("Saved #{filename} to shared cache (#{shared_cache_dir()})", :info)
311+
else
312+
File.delete(destination_path) if File.exist?(destination_path)
313+
abort("[Hermes] Downloaded file #{filename} failed SHA verification. Aborting.")
314+
end
315+
end
316+
else
317+
hermes_log("Cache miss: downloading #{filename} from #{tarball_url}", :info)
245318
# Download to a temporary file first so we don't cache incomplete downloads.
246319
tmp_file = "#{artifacts_dir()}/hermes-ios.download"
247-
`mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
320+
`curl -A "react-native-#{version}" "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
321+
unless File.exist?(destination_path)
322+
abort("[Hermes] Failed to download #{filename} from #{tarball_url}. Aborting.")
323+
end
324+
hermes_log("Verifying checksum for downloaded #{filename}...", :info)
325+
if validate_hermes_tarball(destination_path, tarball_url)
326+
# Save to shared cache for future use
327+
`mkdir -p "#{shared_cache_dir()}"`
328+
FileUtils.cp(destination_path, cached_path)
329+
hermes_log("Saved #{filename} to shared cache (#{shared_cache_dir()})", :info)
330+
else
331+
File.delete(destination_path) if File.exist?(destination_path)
332+
abort("[Hermes] Downloaded file #{filename} failed SHA verification. Aborting.")
333+
end
248334
end
335+
249336
return destination_path
250337
end
251338

packages/rn-tester/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"clean-android": "rm -rf android/app/build",
2020
"prepare-ios": "node ./cli.js bootstrap ios",
2121
"clean-ios": "rm -rf build/generated/ios Pods Podfile.lock RNTesterPods.xcworkspace/ ../react-native-codegen/lib/",
22+
"ios:clear-prebuild-caches": "rm -rf $HOME/Library/Caches/ReactNative",
2223
"e2e-build-android": "../../gradlew :packages:rn-tester:android:app:installRelease -PreactNativeArchitectures=arm64-v8a",
2324
"e2e-build-ios": "./scripts/maestro-build-ios.sh",
2425
"e2e-test-android": "maestro test .maestro/ -e APP_ID=com.facebook.react.uiapp",

0 commit comments

Comments
 (0)