From 92de534d54f509c4096f8c34e10133a0b7c32699 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Fri, 15 May 2026 13:59:30 +0100 Subject: [PATCH] Cache prebuilt iOS binaries in ~/Library/Caches/ReactNative Currently, Hermes, ReactNativeDependencies, and ReactNativeCore tarballs are cached only inside the Pods/ directory. This means every clean `pod install` (or deleting the Pods folder) triggers a full re-download from Maven, even if the same version was already downloaded before. This adds a shared cache layer at ~/Library/Caches/ReactNative/. On first download, tarballs are saved to the shared cache. On subsequent pod installs, if the local Pods cache is empty but the shared cache has the tarball, it is copied locally instead of re-downloaded. This benefits: - Clean installs after deleting Pods/ - Multiple projects using the same React Native version - CI environments with a persistent home directory Added descriptive log messages for each scenario (local hit, shared cache hit, cache miss + download) to aid debugging. --- .../react-native/scripts/cocoapods/rncore.rb | 34 ++++++++++++++----- .../scripts/cocoapods/rndependencies.rb | 31 ++++++++++++++--- .../sdks/hermes-engine/hermes-utils.rb | 32 ++++++++++++++--- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/packages/react-native/scripts/cocoapods/rncore.rb b/packages/react-native/scripts/cocoapods/rncore.rb index 09cd88363d75..7dee4370a0bf 100644 --- a/packages/react-native/scripts/cocoapods/rncore.rb +++ b/packages/react-native/scripts/cocoapods/rncore.rb @@ -403,18 +403,36 @@ def self.download_nightly_rncore(react_native_path, version, configuration, dsym download_rncore_tarball(react_native_path, tarball_url, version, configuration, dsyms) end + def self.shared_cache_dir() + return File.join(Dir.home, "Library", "Caches", "ReactNative") + end + def self.download_rncore_tarball(react_native_path, tarball_url, version, configuration, dsyms = false) - destination_path = configuration == nil ? - "#{artifacts_dir()}/reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}.tar.gz" : - "#{artifacts_dir()}/reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}-#{configuration}.tar.gz" + filename = configuration == nil ? + "reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}.tar.gz" : + "reactnative-core-#{version}#{dsyms ? "-dSYM" : ""}-#{configuration}.tar.gz" + destination_path = "#{artifacts_dir()}/#{filename}" + + if File.exist?(destination_path) + rncore_log("Tarball #{filename} already exists in Pods. Skipping download.") + return destination_path + end - unless File.exist?(destination_path) + `mkdir -p "#{artifacts_dir()}"` + + cached_path = File.join(shared_cache_dir(), filename) + if File.exist?(cached_path) + rncore_log("Cache hit: copying #{filename} from shared cache (#{shared_cache_dir()})") + FileUtils.cp(cached_path, destination_path) + else + rncore_log("Cache miss: downloading #{filename} from #{tarball_url}") # Download to a temporary file first so we don't cache incomplete downloads. - 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}") tmp_file = "#{artifacts_dir()}/reactnative-core.download" - `mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` - else - 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}") + `curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + # Save to shared cache for future use + `mkdir -p "#{shared_cache_dir()}"` + FileUtils.cp(destination_path, cached_path) + rncore_log("Saved #{filename} to shared cache (#{shared_cache_dir()})") end return destination_path diff --git a/packages/react-native/scripts/cocoapods/rndependencies.rb b/packages/react-native/scripts/cocoapods/rndependencies.rb index a90184034071..24ebaa5ad940 100644 --- a/packages/react-native/scripts/cocoapods/rndependencies.rb +++ b/packages/react-native/scripts/cocoapods/rndependencies.rb @@ -232,15 +232,36 @@ def self.podspec_source_download_prebuilt_nightly_tarball(version) return {:http => url} end + def self.shared_cache_dir() + return File.join(Dir.home, "Library", "Caches", "ReactNative") + end + def self.download_rndeps_tarball(react_native_path, tarball_url, version, configuration) - destination_path = configuration == nil ? - "#{artifacts_dir()}/reactnative-dependencies-#{version}.tar.gz" : - "#{artifacts_dir()}/reactnative-dependencies-#{version}-#{configuration}.tar.gz" + filename = configuration == nil ? + "reactnative-dependencies-#{version}.tar.gz" : + "reactnative-dependencies-#{version}-#{configuration}.tar.gz" + destination_path = "#{artifacts_dir()}/#{filename}" + + if File.exist?(destination_path) + rndeps_log("Tarball #{filename} already exists in Pods. Skipping download.") + return destination_path + end - unless File.exist?(destination_path) + `mkdir -p "#{artifacts_dir()}"` + + cached_path = File.join(shared_cache_dir(), filename) + if File.exist?(cached_path) + rndeps_log("Cache hit: copying #{filename} from shared cache (#{shared_cache_dir()})") + FileUtils.cp(cached_path, destination_path) + else + rndeps_log("Cache miss: downloading #{filename} from #{tarball_url}") # Download to a temporary file first so we don't cache incomplete downloads. tmp_file = "#{artifacts_dir()}/reactnative-dependencies.download" - `mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + `curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + # Save to shared cache for future use + `mkdir -p "#{shared_cache_dir()}"` + FileUtils.cp(destination_path, cached_path) + rndeps_log("Saved #{filename} to shared cache (#{shared_cache_dir()})") end return destination_path diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 65e7e450b28c..8d509911499e 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -236,16 +236,38 @@ def download_stable_hermes(react_native_path, version, configuration) download_hermes_tarball(react_native_path, tarball_url, version, configuration) end +def shared_cache_dir() + return File.join(Dir.home, "Library", "Caches", "ReactNative") +end + def download_hermes_tarball(react_native_path, tarball_url, version, configuration) - destination_path = configuration == nil ? - "#{artifacts_dir()}/hermes-ios-#{version}.tar.gz" : - "#{artifacts_dir()}/hermes-ios-#{version}-#{configuration}.tar.gz" + filename = configuration == nil ? + "hermes-ios-#{version}.tar.gz" : + "hermes-ios-#{version}-#{configuration}.tar.gz" + destination_path = "#{artifacts_dir()}/#{filename}" + + if File.exist?(destination_path) + hermes_log("Tarball #{filename} already exists in Pods. Skipping download.", :info) + return destination_path + end - unless File.exist?(destination_path) + `mkdir -p "#{artifacts_dir()}"` + + cached_path = File.join(shared_cache_dir(), filename) + if File.exist?(cached_path) + hermes_log("Cache hit: copying #{filename} from shared cache (#{shared_cache_dir()})", :info) + FileUtils.cp(cached_path, destination_path) + else + hermes_log("Cache miss: downloading #{filename} from #{tarball_url}", :info) # Download to a temporary file first so we don't cache incomplete downloads. tmp_file = "#{artifacts_dir()}/hermes-ios.download" - `mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + `curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` + # Save to shared cache for future use + `mkdir -p "#{shared_cache_dir()}"` + FileUtils.cp(destination_path, cached_path) + hermes_log("Saved #{filename} to shared cache (#{shared_cache_dir()})", :info) end + return destination_path end