diff --git a/scripts/artifacts-utils/ios/patched_ios_artifacts.rb b/scripts/artifacts-utils/ios/patched_ios_artifacts.rb index d2ec5a6bae80..cd7eed3b8652 100644 --- a/scripts/artifacts-utils/ios/patched_ios_artifacts.rb +++ b/scripts/artifacts-utils/ios/patched_ios_artifacts.rb @@ -48,6 +48,43 @@ def self.setup ReactNativeCoreUtils.class_variable_set(:@@patched_artifact_url_prefix, resolution['artifactUrlPrefix']) ReactNativeCoreUtils.class_variable_set(:@@patched_github_token, resolution['githubToken']) ReactNativeCoreUtils.class_variable_set(:@@patched_build_from_source, resolution['buildFromSource']) + + # Content identity of this install's artifacts; '+dsym' so flipping the flag counts as a change. + @artifacts_stamp = @using_prebuilt ? + "#{resolution['version']}#{ENV['RCT_SYMBOLICATE_PREBUILT_FRAMEWORKS'] == '1' ? '+dsym' : ''}" : nil + + force_rncore_podspec_reevaluation if @using_prebuilt + end + + def self.artifacts_stamp_path + File.join(Pod::Config.instance.project_pods_root, 'ReactNativeCore-artifacts', '.artifacts-version') + end + + # CocoaPods memoizes external :podspec sources and may skip re-reading ours, whose source is + # resolved dynamically. When the tarballs in Pods don't match this install's resolution, drop + # the memoized copy so CocoaPods re-evaluates the podspec, re-running our download (and dSYM + # merge). The re-read podspec is byte-identical, so Podfile.lock stays put. + def self.force_rncore_podspec_reevaluation + return if File.exist?(artifacts_stamp_path) && File.read(artifacts_stamp_path) == @artifacts_stamp + + Pod::Config.instance.sandbox.remove_local_podspec('React-Core-prebuilt') + log("Artifacts changed to #{@artifacts_stamp}; the React-Core-prebuilt podspec will be re-evaluated.") + end + + # Prepends sync-prebuilt-rncore.sh to react-native's '[RNCore] Replace ...' build phase, so a + # build re-extracts the prebuilt React Core when the artifact version changed — CocoaPods won't, + # as its caches key on our never-changing source URL. Prepended into that phase (not added as + # its own) because CocoaPods sorts phases by name on save, which would push ours after [RNCore]. + def self.add_sync_prebuilt_script_phase(installer) + return unless @using_prebuilt + + target = installer.pods_project.targets.find { |t| t.name == 'React-Core-prebuilt' } + phase = target&.shell_script_build_phases&.find { |p| p.name.to_s.include?('[RNCore] Replace') } + raise "#{LOG_PREFIX} The [RNCore] Replace build phase was not found on the React-Core-prebuilt target, " \ + 'so the extracted prebuilt React Core would keep following a stale artifact version.' unless phase + + prelude = %(bash "#{File.join(NEW_DOT_ROOT, 'scripts/artifacts-utils/ios/sync-prebuilt-rncore.sh')}" || exit 1\n) + phase.shell_script = prelude + phase.shell_script unless phase.shell_script.start_with?(prelude) end # True only when a matching prebuilt artifact resolved and prebuilds are enabled. @@ -225,6 +262,10 @@ def self.podspec_source_download_prebuild_stable_tarball process_dsyms(release, download_stable_rncore(@@react_native_path, @@react_native_version, :release, true)) end + # Content version of the flat tarballs — their names can't carry it, replace-rncore-version.js hardcodes them. + File.write(File.join(File.dirname(debug), '.artifacts-version'), + "#{@@patched_version}#{@@download_dsyms ? '+dsym' : ''}") + # URI::File.build validates path components as ASCII, so escape the filesystem path first — # matches RN 0.86's own ReactNativePodsUtils.local_file_uri, which this replaces. {:http => URI::File.build(path: URI::DEFAULT_PARSER.escape(debug)).to_s} diff --git a/scripts/artifacts-utils/ios/sync-prebuilt-rncore.sh b/scripts/artifacts-utils/ios/sync-prebuilt-rncore.sh new file mode 100755 index 000000000000..7b1002fb8f2b --- /dev/null +++ b/scripts/artifacts-utils/ios/sync-prebuilt-rncore.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Prelude of react-native's '[RNCore] Replace ...' build phase (prepended by +# PatchedIOSArtifacts.add_sync_prebuilt_script_phase). When the extracted prebuilt React Core +# is a different patched artifact version than the tarballs in Pods, invalidates the +# .last_build_configuration marker so replace-rncore-version.js, running right after this, +# re-extracts from the tarballs. Needed because our tarball path carries no patches version, +# so a version change is invisible to CocoaPods and it can keep a stale extraction. +# +# Expected Xcode environment variables: +# PODS_ROOT +set -euo pipefail + +TARBALLS_STAMP="$PODS_ROOT/ReactNativeCore-artifacts/.artifacts-version" +PREBUILT_DIR="$PODS_ROOT/React-Core-prebuilt" +readonly TARBALLS_STAMP PREBUILT_DIR + +# No stamp: the last install built react-native from source — nothing to sync. +[ -f "$TARBALLS_STAMP" ] || exit 0 + +TARBALLS=$(cat "$TARBALLS_STAMP") +EXTRACTED=$(cat "$PREBUILT_DIR/.patched-version" 2>/dev/null || true) +# Fast path, taken on every build: what is extracted matches the tarballs. +[ "$TARBALLS" = "$EXTRACTED" ] && exit 0 + +echo "[PatchedArtifacts] Extracted prebuilt React Core is '${EXTRACTED:-}', tarballs are '$TARBALLS' — marking for re-extraction." +mkdir -p "$PREBUILT_DIR" +# 'stale' matches no configuration, so replace-rncore-version.js re-extracts; it stays until +# an extraction succeeds and writes a real configuration back, so a failed build retries. +# No trailing newlines: replace-rncore-version.js compares its marker verbatim. +printf 'stale' > "$PREBUILT_DIR/.last_build_configuration" +printf '%s' "$TARBALLS" > "$PREBUILT_DIR/.patched-version"