Skip to content

Commit bc90cda

Browse files
russellwheatleyjsnavarroc
authored andcommitted
fix(ios): embed Firebase SPM package frameworks
Add an RNFB SPM post-install patch that injects an app-target build phase for Firebase SPM dynamic frameworks. The phase copies frameworks built under Xcode's PackageFrameworks directory into the app Frameworks folder when they were not already embedded by CocoaPods. This fixes release launches where RNFBStorage links FirebaseStorage, FirebaseStorage links FirebaseAppCheckInterop, but the app bundle does not contain FirebaseAppCheckInterop.framework. The fix keeps Storage declaring only the public FirebaseStorage product and avoids requiring consumers to install App Check or add a Podfile workaround. Also regenerates the iOS test project so the new embed phase is present.
1 parent eaa09ea commit bc90cda

3 files changed

Lines changed: 312 additions & 460 deletions

File tree

packages/app/firebase_spm.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
require 'json'
2020

21+
RNFIREBASE_SPM_EMBED_PHASE_NAME = '[RNFB] Embed Firebase SPM Frameworks'
22+
2123
# Read Firebase SPM URL from app package.json (single source of truth).
2224
# __dir__ resolves to the directory of this file (packages/app/).
2325
# In monorepos with hoisted dependencies or pnpm, the path from other packages
@@ -46,6 +48,88 @@
4648
# https://github.com/firebase/firebase-ios-sdk/blob/main/Package.swift
4749
# (all products use .library(type: .dynamic))
4850
#
51+
# Returns true only when `$RNFirebaseDisableSPM` has been explicitly set to `true`.
52+
#
53+
# We deliberately check the value (not just `defined?`), so that config generators,
54+
# Expo plugins, or env-templated Podfiles that emit `$RNFirebaseDisableSPM = false`
55+
# don't silently switch to CocoaPods.
56+
def rnfirebase_spm_disabled?
57+
defined?($RNFirebaseDisableSPM) && $RNFirebaseDisableSPM == true
58+
end
59+
60+
def rnfirebase_spm_embed_script
61+
<<~'SCRIPT'
62+
set -euo pipefail
63+
64+
package_frameworks_dir="${BUILT_PRODUCTS_DIR}/PackageFrameworks"
65+
app_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
66+
67+
if [ ! -d "${package_frameworks_dir}" ]; then
68+
exit 0
69+
fi
70+
71+
mkdir -p "${app_frameworks_dir}"
72+
73+
find "${package_frameworks_dir}" -maxdepth 1 -type d -name "*.framework" -print0 | while IFS= read -r -d '' framework; do
74+
framework_name="$(basename "${framework}")"
75+
destination="${app_frameworks_dir}/${framework_name}"
76+
77+
if [ -e "${destination}" ]; then
78+
continue
79+
fi
80+
81+
echo "Embedding Firebase SPM framework ${framework_name}"
82+
rsync -av --delete \
83+
--filter "- Headers" \
84+
--filter "- PrivateHeaders" \
85+
--filter "- Modules" \
86+
"${framework}" \
87+
"${app_frameworks_dir}"
88+
89+
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ] && [ "${CODE_SIGNING_ALLOWED:-}" != "NO" ]; then
90+
/usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements "${destination}"
91+
fi
92+
done
93+
SCRIPT
94+
end
95+
96+
def rnfirebase_add_spm_embed_phase(installer)
97+
return unless defined?(SPM)
98+
99+
dependencies_by_pod = SPM.instance_variable_get(:@dependencies_by_pod) || {}
100+
firebase_spm_enabled = dependencies_by_pod.values.flatten.any? do |dependency|
101+
dependency[:url] == $firebase_spm_url
102+
end
103+
return unless firebase_spm_enabled
104+
105+
installer.aggregate_targets.each do |aggregate_target|
106+
aggregate_target.user_project.native_targets.each do |target|
107+
next unless target.respond_to?(:shell_script_build_phases)
108+
next unless target.shell_script_build_phases.any? { |phase| phase.name == '[CP] Embed Pods Frameworks' }
109+
110+
phase = target.shell_script_build_phases.find { |candidate| candidate.name == RNFIREBASE_SPM_EMBED_PHASE_NAME }
111+
phase ||= target.new_shell_script_build_phase(RNFIREBASE_SPM_EMBED_PHASE_NAME)
112+
113+
phase.shell_script = rnfirebase_spm_embed_script
114+
phase.shell_path = '/bin/bash'
115+
phase.always_out_of_date = '1'
116+
phase.input_paths = ['${BUILT_PRODUCTS_DIR}/PackageFrameworks']
117+
phase.output_paths = ['${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}']
118+
end
119+
120+
aggregate_target.user_project.save
121+
end
122+
end
123+
124+
if defined?(SPM) && SPM.respond_to?(:apply_on_post_install) && !SPM.singleton_class.method_defined?(:rnfirebase_original_apply_on_post_install)
125+
SPM.singleton_class.alias_method :rnfirebase_original_apply_on_post_install, :apply_on_post_install
126+
127+
def SPM.apply_on_post_install(installer)
128+
rnfirebase_original_apply_on_post_install(installer)
129+
rnfirebase_add_spm_embed_phase(installer)
130+
end
131+
end
132+
49133
# @param spec [Pod::Specification] The podspec object (the `s` in podspec DSL)
50134
# @param version [String] Firebase SDK version (e.g., '12.10.0')
51135
# @param spm_products [Array<String>] SPM product names (e.g., ['FirebaseAuth'])

0 commit comments

Comments
 (0)