@react-native-firebase/app 26.0.0 → 26.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +21 -0
- package/RNFBApp.podspec +13 -2
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
- package/dist/module/internal/web/RNFBAppModule.js +16 -3
- package/dist/module/internal/web/RNFBAppModule.js.map +1 -1
- package/dist/module/internal/web/utils.js +6 -1
- package/dist/module/internal/web/utils.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts.map +1 -1
- package/dist/typescript/lib/internal/web/utils.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/firebase_spm.rb +1016 -0
- package/ios/RNFBApp/RCTConvert+FIRApp.h +4 -0
- package/ios/RNFBApp/RCTConvert+FIROptions.h +4 -0
- package/ios/RNFBApp/RNFBAppModule.mm +6 -0
- package/ios/RNFBApp/RNFBSharedUtils.h +4 -0
- package/ios/RNFBApp/RNFBVersion.m +1 -1
- package/lib/internal/web/RNFBAppModule.ts +16 -3
- package/lib/internal/web/utils.ts +6 -1
- package/lib/version.ts +1 -1
- package/package.json +6 -5
package/firebase_spm.rb
ADDED
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this library except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
require 'json'
|
|
20
|
+
|
|
21
|
+
RNFIREBASE_SPM_EMBED_PHASE_NAME = '[RNFB] Embed Firebase SPM Frameworks'
|
|
22
|
+
RNFIREBASE_SPM_SIGNATURE_FIX_PHASE_NAME = '[RNFB] Remove duplicate Firebase/Google SPM binary xcframework signature files'
|
|
23
|
+
|
|
24
|
+
# Every `.binaryTarget` xcframework name reachable in the resolved SPM package
|
|
25
|
+
# graph for the RNFB test app (firebase-ios-sdk 12.16.0, full module set --
|
|
26
|
+
# Analytics with ad support, Firestore, etc). Enumerated from
|
|
27
|
+
# `SourcePackages/workspace-state.json`'s `artifacts` list after a clean
|
|
28
|
+
# `-resolvePackageDependencies` run rather than guessed, since none of these
|
|
29
|
+
# show up as a reference in our own podspecs or pbxprojs (see comment on
|
|
30
|
+
# `rnfirebase_fix_spm_archive_signature_collision` below for why). Any of
|
|
31
|
+
# these can be staged into more than one target's build directory and hit the
|
|
32
|
+
# Archive signature-copy collision below, not just the Analytics-related
|
|
33
|
+
# ones we hit first.
|
|
34
|
+
RNFIREBASE_SPM_SIGNATURE_FIX_ARTIFACT_NAMES = %w[
|
|
35
|
+
GoogleAppMeasurement
|
|
36
|
+
GoogleAppMeasurementIdentitySupport
|
|
37
|
+
GoogleAdsOnDeviceConversion
|
|
38
|
+
FirebaseAnalytics
|
|
39
|
+
FirebaseFirestoreInternal
|
|
40
|
+
absl
|
|
41
|
+
grpc
|
|
42
|
+
grpcpp
|
|
43
|
+
openssl_grpc
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
# Encapsulates the SPM-related state that has to survive across CocoaPods'
|
|
47
|
+
# per-podspec evaluation (where `firebase_dependency` runs -- see `activate!`
|
|
48
|
+
# below) and the later, single `post_install` phase (where every
|
|
49
|
+
# `rnfirebase_*` helper in this file reads it back via `active?`/`version`/
|
|
50
|
+
# `url`). This works today because CocoaPods always finishes evaluating every
|
|
51
|
+
# podspec before running `post_install`, so a single, process-wide place to
|
|
52
|
+
# stash this is safe -- but wrapping the three pieces of state in a module
|
|
53
|
+
# (rather than three bare globals) keeps every read/write site in this file
|
|
54
|
+
# explicit about what it's touching, and gives `active?` a place to
|
|
55
|
+
# self-check for a state that should never happen instead of every
|
|
56
|
+
# downstream caller trusting a bare boolean blindly.
|
|
57
|
+
module RNFirebaseSPM
|
|
58
|
+
class << self
|
|
59
|
+
# Firebase SPM package URL, read from the app's own package.json (single
|
|
60
|
+
# source of truth) the first time it's needed, then cached for the rest
|
|
61
|
+
# of this `pod install` process.
|
|
62
|
+
#
|
|
63
|
+
# __dir__ resolves to the directory of this file (packages/app/).
|
|
64
|
+
# Every other podspec loads this file via `require_relative` (e.g.
|
|
65
|
+
# `require_relative '../app/firebase_spm'`), which resolves the path
|
|
66
|
+
# relative to the requiring file's own location rather than the
|
|
67
|
+
# process's current working directory -- so this always resolves
|
|
68
|
+
# correctly regardless of monorepo hoisting layout (hoisted
|
|
69
|
+
# dependencies, pnpm, etc.), with no adjustment needed.
|
|
70
|
+
def url
|
|
71
|
+
@url ||= begin
|
|
72
|
+
app_package_path = File.join(__dir__, 'package.json')
|
|
73
|
+
app_package = JSON.parse(File.read(app_package_path))
|
|
74
|
+
app_package['sdkVersions']['ios']['firebaseSpmUrl']
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Records that `firebase_dependency` (below) took the SPM path for at
|
|
79
|
+
# least one podspec in this install, and which Firebase SDK `version` it
|
|
80
|
+
# resolved with. Called once, from `firebase_dependency` itself, the
|
|
81
|
+
# first time it successfully takes the SPM path. `version` is stored so
|
|
82
|
+
# `rnfirebase_add_spm_core_to_app_target` can declare the same minimum
|
|
83
|
+
# version requirement on the app target's own FirebaseCore product
|
|
84
|
+
# dependency, without needing its own separate copy of it.
|
|
85
|
+
def activate!(version)
|
|
86
|
+
@active = true
|
|
87
|
+
@version = version
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Whether SPM is active for this install -- read by every `rnfirebase_*`
|
|
91
|
+
# post-install helper in this file to decide whether to act at all.
|
|
92
|
+
#
|
|
93
|
+
# Self-checks internal consistency before returning: if `@active` is
|
|
94
|
+
# `true` but no real `version` was ever recorded, something set the flag
|
|
95
|
+
# without going through `activate!` above (e.g. a future refactor that
|
|
96
|
+
# assigns the flag directly instead of calling it), and every downstream
|
|
97
|
+
# helper that trusts this return value -- including one that links
|
|
98
|
+
# FirebaseCore into the app target at a specific minimum version -- would
|
|
99
|
+
# silently operate on incomplete state instead. Raising `Pod::Informative`
|
|
100
|
+
# here (this file's own user-facing `pod install`-time error class, same
|
|
101
|
+
# as `rnfirebase_fail_if_spm_static_linkage!` below) turns that into a
|
|
102
|
+
# loud, immediate failure instead of a confusing downstream symptom.
|
|
103
|
+
def active?
|
|
104
|
+
return false unless @active
|
|
105
|
+
|
|
106
|
+
if @version.nil? || @version.to_s.strip.empty?
|
|
107
|
+
raise Pod::Informative, <<~MESSAGE
|
|
108
|
+
[react-native-firebase] Internal error: Firebase SPM was marked active without a recorded version -- `RNFirebaseSPM.activate!` was either never called, or was called with a nil/empty version. This indicates a bug in react-native-firebase's own Podfile integration, not a problem with your project.
|
|
109
|
+
|
|
110
|
+
Please open an issue at https://github.com/invertase/react-native-firebase/issues, including your full `pod install` output.
|
|
111
|
+
MESSAGE
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The Firebase SDK version `firebase_dependency` resolved with, recorded
|
|
118
|
+
# by `activate!` above -- used by `rnfirebase_add_spm_core_to_app_target`
|
|
119
|
+
# to declare the same minimum version requirement on the app target's own
|
|
120
|
+
# FirebaseCore product dependency.
|
|
121
|
+
def version
|
|
122
|
+
@version
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Clears active/version/url back to their unset defaults. Not needed in
|
|
126
|
+
# production Podfile code paths -- each `pod install` process is
|
|
127
|
+
# short-lived, so there's nothing to reset between installs -- but the
|
|
128
|
+
# test suite `load`s this file fresh for every test and needs a
|
|
129
|
+
# deliberate way to reset this module's state in between, rather than
|
|
130
|
+
# relying on Ruby's one-way `defined?` (a global variable, once assigned,
|
|
131
|
+
# can't become "undefined" again) the way `$RNFirebaseDisableSPM` still
|
|
132
|
+
# does in the test suite's `setup`.
|
|
133
|
+
def reset!
|
|
134
|
+
@active = nil
|
|
135
|
+
@version = nil
|
|
136
|
+
@url = nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Helper to declare Firebase dependencies with SPM support and CocoaPods fallback.
|
|
142
|
+
#
|
|
143
|
+
# When `spm_dependency` is available (React Native >= 0.75), it declares the
|
|
144
|
+
# Firebase iOS SDK as a Swift Package dependency. Otherwise, it falls back to
|
|
145
|
+
# the traditional CocoaPods `s.dependency` declaration.
|
|
146
|
+
#
|
|
147
|
+
# Set `$RNFirebaseDisableSPM = true` in your Podfile to force CocoaPods-only
|
|
148
|
+
# dependency resolution. You must disable SPM when using `use_frameworks! :linkage => :static`
|
|
149
|
+
# because static frameworks cause each pod to embed Firebase SPM products,
|
|
150
|
+
# resulting in duplicate symbol linker errors.
|
|
151
|
+
#
|
|
152
|
+
# firebase-ios-sdk SPM requires dynamic linkage. There is no upstream statement
|
|
153
|
+
# from Google that SPM+static is supported. See:
|
|
154
|
+
# https://github.com/firebase/firebase-ios-sdk/blob/main/Package.swift
|
|
155
|
+
# (all products use .library(type: .dynamic))
|
|
156
|
+
#
|
|
157
|
+
# Returns true only when `$RNFirebaseDisableSPM` has been explicitly set to `true`.
|
|
158
|
+
#
|
|
159
|
+
# We deliberately check the value (not just `defined?`), so that config generators,
|
|
160
|
+
# Expo plugins, or env-templated Podfiles that emit `$RNFirebaseDisableSPM = false`
|
|
161
|
+
# don't silently switch to CocoaPods.
|
|
162
|
+
def rnfirebase_spm_disabled?
|
|
163
|
+
defined?($RNFirebaseDisableSPM) && $RNFirebaseDisableSPM == true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Normalizes a build setting value that Xcode/Xcodeproj may represent as
|
|
167
|
+
# `nil`, a whitespace-separated `String`, or an `Array` into a plain `Array`
|
|
168
|
+
# safe to call `include?`/`push`/`<<` on. Settings like `OTHER_LDFLAGS` or
|
|
169
|
+
# `SWIFT_INCLUDE_PATHS` can legitimately arrive as any of the three depending
|
|
170
|
+
# on how a consumer's project/build settings were authored -- treating them
|
|
171
|
+
# as always-already-an-Array (e.g. a bare `||= []` default, which only
|
|
172
|
+
# covers the `nil` case) raises `NoMethodError` the moment a consumer's
|
|
173
|
+
# target already has one of these set as a `String`.
|
|
174
|
+
def rnfirebase_build_setting_list(current)
|
|
175
|
+
if current.nil? || (current.is_a?(String) && current.strip.empty?) || (current.is_a?(Array) && current.empty?)
|
|
176
|
+
['$(inherited)']
|
|
177
|
+
elsif current.is_a?(String)
|
|
178
|
+
current.split(' ')
|
|
179
|
+
else
|
|
180
|
+
current.dup
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def rnfirebase_spm_embed_script
|
|
185
|
+
<<~'SCRIPT'
|
|
186
|
+
set -euo pipefail
|
|
187
|
+
|
|
188
|
+
app_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
|
189
|
+
mkdir -p "${app_frameworks_dir}"
|
|
190
|
+
|
|
191
|
+
embed_frameworks_from() {
|
|
192
|
+
local source_dir="$1"
|
|
193
|
+
[ -d "${source_dir}" ] || return 0
|
|
194
|
+
|
|
195
|
+
find "${source_dir}" -maxdepth 1 -type d -name "*.framework" -print0 | while IFS= read -r -d '' framework; do
|
|
196
|
+
framework_name="$(basename "${framework}")"
|
|
197
|
+
destination="${app_frameworks_dir}/${framework_name}"
|
|
198
|
+
|
|
199
|
+
if [ -e "${destination}" ]; then
|
|
200
|
+
continue
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
echo "Embedding Firebase SPM framework ${framework_name} (from ${source_dir})"
|
|
204
|
+
rsync -av --delete \
|
|
205
|
+
--filter "- Headers" \
|
|
206
|
+
--filter "- PrivateHeaders" \
|
|
207
|
+
--filter "- Modules" \
|
|
208
|
+
"${framework}" \
|
|
209
|
+
"${app_frameworks_dir}"
|
|
210
|
+
|
|
211
|
+
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ] && [ "${CODE_SIGNING_ALLOWED:-}" != "NO" ]; then
|
|
212
|
+
/usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements "${destination}"
|
|
213
|
+
fi
|
|
214
|
+
done
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Regular (simulator/device) builds put every Swift Package product for
|
|
218
|
+
# the whole scheme's dependency graph into one shared folder.
|
|
219
|
+
embed_frameworks_from "${BUILT_PRODUCTS_DIR}/PackageFrameworks"
|
|
220
|
+
|
|
221
|
+
# Xcode's Archive action (ONLY_ACTIVE_ARCH=NO, DEPLOYMENT_POSTPROCESSING=YES)
|
|
222
|
+
# never populates any target's PackageFrameworks folder at all -- it builds
|
|
223
|
+
# Swift Package products into a separate shared "uninstalled products"
|
|
224
|
+
# folder instead. Without also checking here, a real `xcodebuild archive`
|
|
225
|
+
# (i.e. every TestFlight/App Store build) silently embeds zero Firebase SPM
|
|
226
|
+
# frameworks and the resulting app crashes at launch with a missing-library
|
|
227
|
+
# dyld error.
|
|
228
|
+
embed_frameworks_from "${OBJROOT}/UninstalledProducts/${PLATFORM_NAME}"
|
|
229
|
+
SCRIPT
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Creates (or updates in place) a shell-script build phase named `name` on
|
|
233
|
+
# `target`, only reporting a change when something about it actually
|
|
234
|
+
# differs from what's already there.
|
|
235
|
+
#
|
|
236
|
+
# Without this, callers that unconditionally assign every property and then
|
|
237
|
+
# unconditionally set their own "did I touch anything" flag end up rewriting
|
|
238
|
+
# -- and re-saving -- the consumer's `.pbxproj` on every single `pod install`,
|
|
239
|
+
# even when the phase's script/paths are byte-for-byte identical to the last
|
|
240
|
+
# install. That's needless diff churn on a file consumers commit to source
|
|
241
|
+
# control.
|
|
242
|
+
#
|
|
243
|
+
# Returns `true` if the phase was newly created or any of its properties
|
|
244
|
+
# changed; `false` if it already matched and nothing was touched.
|
|
245
|
+
def rnfirebase_upsert_shell_script_phase!(target, name, shell_script:, shell_path:, input_paths: nil, output_paths: nil)
|
|
246
|
+
existing = target.shell_script_build_phases.find { |candidate| candidate.name == name }
|
|
247
|
+
phase = existing || target.new_shell_script_build_phase(name)
|
|
248
|
+
|
|
249
|
+
changed = existing.nil?
|
|
250
|
+
changed ||= phase.shell_script != shell_script
|
|
251
|
+
changed ||= phase.shell_path != shell_path
|
|
252
|
+
changed ||= phase.always_out_of_date != '1'
|
|
253
|
+
changed ||= (!input_paths.nil? && phase.input_paths != input_paths)
|
|
254
|
+
changed ||= (!output_paths.nil? && phase.output_paths != output_paths)
|
|
255
|
+
|
|
256
|
+
phase.shell_script = shell_script
|
|
257
|
+
phase.shell_path = shell_path
|
|
258
|
+
phase.always_out_of_date = '1'
|
|
259
|
+
phase.input_paths = input_paths if input_paths
|
|
260
|
+
phase.output_paths = output_paths if output_paths
|
|
261
|
+
|
|
262
|
+
changed
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Adds a build phase that copies Firebase's SPM-built dynamic frameworks
|
|
266
|
+
# into the app bundle. Runs automatically on every `pod install`/`pod update`
|
|
267
|
+
# -- see `rnfirebase_hook_cocoapods_post_install!` below -- so you normally
|
|
268
|
+
# never need to call this yourself.
|
|
269
|
+
#
|
|
270
|
+
# Only needed when Firebase is resolved via SPM (the RN >= 0.75 default) with
|
|
271
|
+
# dynamic linkage. It's a no-op (returns immediately) when Firebase used
|
|
272
|
+
# CocoaPods instead, so it's always safe to leave in your Podfile if you're
|
|
273
|
+
# calling it manually as a fallback (see below).
|
|
274
|
+
#
|
|
275
|
+
# Why this needs to exist at all: React Native's SPM integration
|
|
276
|
+
# (`spm_dependency` -> `SPM.apply_on_post_install`, in RN's own bundled
|
|
277
|
+
# `react_native_pods.rb`) adds Swift package product dependencies to pod
|
|
278
|
+
# targets, but never teaches the app target's CocoaPods embed script about
|
|
279
|
+
# the dynamic frameworks Xcode's SPM build produces -- so without this, apps
|
|
280
|
+
# crash at launch with a missing-library dyld error.
|
|
281
|
+
#
|
|
282
|
+
# If the automatic hook below ever fails to install (e.g. a future CocoaPods
|
|
283
|
+
# release restructures `Pod::Installer`), it prints a `pod install`-time
|
|
284
|
+
# warning telling you to call this explicitly instead:
|
|
285
|
+
#
|
|
286
|
+
# post_install do |installer|
|
|
287
|
+
# react_native_post_install(installer, ...)
|
|
288
|
+
# rnfirebase_add_spm_embed_phase(installer)
|
|
289
|
+
# end
|
|
290
|
+
def rnfirebase_add_spm_embed_phase(installer)
|
|
291
|
+
return unless RNFirebaseSPM.active?
|
|
292
|
+
|
|
293
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
294
|
+
project_modified = false
|
|
295
|
+
|
|
296
|
+
aggregate_target.user_project.native_targets.each do |target|
|
|
297
|
+
next unless target.respond_to?(:shell_script_build_phases)
|
|
298
|
+
next unless target.shell_script_build_phases.any? { |phase| phase.name == '[CP] Embed Pods Frameworks' }
|
|
299
|
+
|
|
300
|
+
changed = rnfirebase_upsert_shell_script_phase!(
|
|
301
|
+
target,
|
|
302
|
+
RNFIREBASE_SPM_EMBED_PHASE_NAME,
|
|
303
|
+
shell_script: rnfirebase_spm_embed_script,
|
|
304
|
+
shell_path: '/bin/bash',
|
|
305
|
+
input_paths: ['${BUILT_PRODUCTS_DIR}/PackageFrameworks'],
|
|
306
|
+
output_paths: ['${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}']
|
|
307
|
+
)
|
|
308
|
+
project_modified ||= changed
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Only rewrite the pbxproj when we actually created or changed a phase --
|
|
312
|
+
# avoids an unconditional save (and consumer-visible pbxproj diff) on
|
|
313
|
+
# every single `pod install`, even when nothing about the phase differs
|
|
314
|
+
# from the last install.
|
|
315
|
+
aggregate_target.user_project.save if project_modified
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Safety net for `rnfirebase_add_spm_embed_phase`: confirms its embed build
|
|
320
|
+
# phase actually landed on every target that needs it, immediately after it
|
|
321
|
+
# runs. "Needs it" uses the exact same target-selection criterion
|
|
322
|
+
# `rnfirebase_add_spm_embed_phase` itself uses (SPM active, target has a
|
|
323
|
+
# `'[CP] Embed Pods Frameworks'` phase) -- so this is only ever checking
|
|
324
|
+
# targets that function was actually supposed to have touched.
|
|
325
|
+
#
|
|
326
|
+
# Without this, a future Xcodeproj/Xcode-project shape that
|
|
327
|
+
# `rnfirebase_add_spm_embed_phase` doesn't anticipate could leave it
|
|
328
|
+
# silently doing nothing for a target, with no `pod install`-time signal --
|
|
329
|
+
# the first sign of trouble would be that app crashing at launch with a
|
|
330
|
+
# missing-library dyld error, with nothing pointing back at this file.
|
|
331
|
+
#
|
|
332
|
+
# Raises `Pod::Informative` -- CocoaPods' own user-facing error class,
|
|
333
|
+
# same as `rnfirebase_fail_if_spm_static_linkage!` -- rather than warning
|
|
334
|
+
# and continuing like the softer checks in this file: there's no safe
|
|
335
|
+
# fallback for "the app you're about to build will crash at launch," so
|
|
336
|
+
# `pod install` itself must fail loudly here instead.
|
|
337
|
+
def rnfirebase_verify_spm_embed_phase_applied!(installer)
|
|
338
|
+
return unless RNFirebaseSPM.active?
|
|
339
|
+
|
|
340
|
+
missing_target_names = []
|
|
341
|
+
|
|
342
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
343
|
+
aggregate_target.user_project.native_targets.each do |target|
|
|
344
|
+
next unless target.respond_to?(:shell_script_build_phases)
|
|
345
|
+
next unless target.shell_script_build_phases.any? { |phase| phase.name == '[CP] Embed Pods Frameworks' }
|
|
346
|
+
next if target.shell_script_build_phases.any? { |phase| phase.name == RNFIREBASE_SPM_EMBED_PHASE_NAME }
|
|
347
|
+
|
|
348
|
+
missing_target_names << target.name
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
return if missing_target_names.empty?
|
|
353
|
+
|
|
354
|
+
raise Pod::Informative, <<~MESSAGE
|
|
355
|
+
[react-native-firebase] Failed to add the Firebase SPM embed build phase to target(s): #{missing_target_names.join(', ')}.
|
|
356
|
+
|
|
357
|
+
Without it, this app will crash at launch with a missing-library dyld error, because Firebase's Swift Package frameworks never get copied into the app bundle.
|
|
358
|
+
|
|
359
|
+
Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile's post_install block as a fallback, then run `pod install` again.
|
|
360
|
+
MESSAGE
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Fails `pod install` fast, with a clear explanation, when RNFB's SPM mode is
|
|
364
|
+
# combined with static linkage (`use_frameworks! :linkage => :static`) --
|
|
365
|
+
# instead of letting consumers hit a confusing "duplicate symbols for
|
|
366
|
+
# architecture ..." linker error later, at Xcode build time, with no obvious
|
|
367
|
+
# link back to the Podfile setting that caused it.
|
|
368
|
+
#
|
|
369
|
+
# firebase-ios-sdk's SPM package only ships dynamic library products -- see
|
|
370
|
+
# https://github.com/firebase/firebase-ios-sdk/blob/main/Package.swift (every
|
|
371
|
+
# product uses `.library(type: .dynamic)`) -- so under static linkage, every
|
|
372
|
+
# RNFB pod that resolves Firebase via SPM ends up statically embedding its
|
|
373
|
+
# own private copy of the same Firebase SPM products, and the app target
|
|
374
|
+
# links duplicate symbols for each one. There is no supported combination of
|
|
375
|
+
# RNFB's SPM mode with static linkage to fall back to; the fix is always to
|
|
376
|
+
# either switch to dynamic linkage or opt out of SPM entirely.
|
|
377
|
+
#
|
|
378
|
+
# Raises `Pod::Informative` -- CocoaPods' own user-facing error class, which
|
|
379
|
+
# `pod install` prints as a plain, readable message with no Ruby backtrace --
|
|
380
|
+
# rather than warning-and-continuing like the other checks in this file.
|
|
381
|
+
def rnfirebase_fail_if_spm_static_linkage!(installer)
|
|
382
|
+
return unless RNFirebaseSPM.active?
|
|
383
|
+
|
|
384
|
+
# `AggregateTarget#build_as_static?` (and `#build_type` it delegates to) is
|
|
385
|
+
# NOT what it sounds like: CocoaPods always constructs the aggregate
|
|
386
|
+
# "Pods-<target>" umbrella target itself as `static_framework`/
|
|
387
|
+
# `static_library` -- see `Installer::Analyzer#generate_aggregate_target`,
|
|
388
|
+
# which hardcodes `target_definition.uses_frameworks? ? BuildType.static_framework
|
|
389
|
+
# : BuildType.static_library` regardless of `use_frameworks!`'s `:linkage`.
|
|
390
|
+
# Checking it here made this fail unconditionally for every SPM install,
|
|
391
|
+
# dynamic linkage included, because it's true either way.
|
|
392
|
+
#
|
|
393
|
+
# The actual `:linkage => :dynamic` vs `:static` choice from the Podfile is
|
|
394
|
+
# recorded on each aggregate target's own `target_definition.build_type`
|
|
395
|
+
# instead (`TargetDefinition#build_type`, which individual `PodTarget`s
|
|
396
|
+
# also inherit from via `Analyzer#determine_build_type`) -- check that.
|
|
397
|
+
static_targets = installer.aggregate_targets.select do |target|
|
|
398
|
+
target.target_definition.build_type.static?
|
|
399
|
+
end
|
|
400
|
+
return if static_targets.empty?
|
|
401
|
+
|
|
402
|
+
target_names = static_targets.map(&:name).join(', ')
|
|
403
|
+
|
|
404
|
+
raise Pod::Informative, <<~MESSAGE
|
|
405
|
+
[react-native-firebase] SPM + static linkage is not supported (target(s): #{target_names}).
|
|
406
|
+
|
|
407
|
+
firebase-ios-sdk's Swift Package only ships dynamic library products, so `use_frameworks! :linkage => :static` causes every react-native-firebase pod that resolves Firebase via SPM to embed its own copy of the same Firebase frameworks -- this produces duplicate-symbol linker errors at build time instead of a clear error here.
|
|
408
|
+
|
|
409
|
+
Fix one of the following in your Podfile, then run `pod install` again:
|
|
410
|
+
- Use dynamic linkage: `use_frameworks! :linkage => :dynamic`
|
|
411
|
+
- Opt out of SPM: set `$RNFirebaseDisableSPM = true` before any target block, then use the static or dynamic linkage your project requires.
|
|
412
|
+
MESSAGE
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# CocoaPods `TargetUUIDGenerator` replaces `@generated_uuids` with leftover
|
|
416
|
+
# `@available_uuids` before Podfile `post_install`. RN's SPM integration then
|
|
417
|
+
# calls `project.new` for Firebase package product deps; with an empty/short
|
|
418
|
+
# counter that restarts near index 0 and **overwrites** `rootObject`
|
|
419
|
+
# (`PBXProject` at `PREFIX0000000`). Xcode 26 then refuses to open Pods
|
|
420
|
+
# (`-[XCSwiftPackageProductDependency _setSavedArchiveVersion:]`) and the
|
|
421
|
+
# workspace builds only SPM app targets — bridging headers can't see RNFB
|
|
422
|
+
# frameworks (reproduced on RN 0.85.3 × prebuilt RNCore). Raise the sequential
|
|
423
|
+
# UUID high-water mark to past every existing CocoaPods-format object before
|
|
424
|
+
# any `post_install` body (including `react_native_post_install` → SPM) runs.
|
|
425
|
+
def rnfirebase_ensure_pods_uuid_counter_safe!(installer)
|
|
426
|
+
project = installer.pods_project
|
|
427
|
+
return unless project
|
|
428
|
+
|
|
429
|
+
prefix = project.instance_variable_get(:@uuid_prefix)
|
|
430
|
+
return unless prefix.is_a?(String) && prefix.length >= 6
|
|
431
|
+
|
|
432
|
+
pfx = prefix[0, 6]
|
|
433
|
+
max_idx = -1
|
|
434
|
+
project.objects_by_uuid.each_key do |uuid|
|
|
435
|
+
next unless uuid.is_a?(String) && uuid.length == 14 && uuid.start_with?(pfx) && uuid.end_with?('0')
|
|
436
|
+
|
|
437
|
+
idx = uuid[6, 7].to_i(16)
|
|
438
|
+
max_idx = idx if idx > max_idx
|
|
439
|
+
end
|
|
440
|
+
return if max_idx < 0
|
|
441
|
+
|
|
442
|
+
generated = project.instance_variable_get(:@generated_uuids)
|
|
443
|
+
generated = [] unless generated.is_a?(Array)
|
|
444
|
+
already_high = generated.size > max_idx
|
|
445
|
+
while generated.size <= max_idx
|
|
446
|
+
generated << format('%.6s%07X0', prefix, generated.size)
|
|
447
|
+
end
|
|
448
|
+
project.instance_variable_set(:@generated_uuids, generated)
|
|
449
|
+
project.instance_variable_set(:@available_uuids, [])
|
|
450
|
+
|
|
451
|
+
# Only log when SPM is active and we actually padded -- non-SPM installs
|
|
452
|
+
# still get the counter raise (cheap insurance) but must not spam every
|
|
453
|
+
# `pod install` with a success line.
|
|
454
|
+
if !already_high && defined?(Pod::UI) && RNFirebaseSPM.active?
|
|
455
|
+
Pod::UI.puts '[react-native-firebase] Raised CocoaPods Pods UUID counter ' \
|
|
456
|
+
"past index #{max_idx} before RN SPM mutates Pods.xcodeproj."
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Hard integrity check for the UUID-collision failure class above -- mirrors
|
|
461
|
+
# `rnfirebase_verify_spm_embed_phase_applied!` (soft attempt, then fail closed).
|
|
462
|
+
# After RN's SPM `post_install` mutates Pods.xcodeproj, the Pods project's
|
|
463
|
+
# `rootObject` UUID must still resolve to the same `PBXProject` instance. If a
|
|
464
|
+
# later `project.new` reused `PREFIX0000000`, `objects_by_uuid` holds a
|
|
465
|
+
# different object at that UUID while `@root_object` still points at the
|
|
466
|
+
# original `PBXProject`; saving then writing that UUID as `rootObject` leaves
|
|
467
|
+
# Xcode unable to open Pods.
|
|
468
|
+
#
|
|
469
|
+
# Raises `Pod::Informative` rather than warning-and-continuing: there is no
|
|
470
|
+
# safe fallback once the project graph is corrupted.
|
|
471
|
+
def rnfirebase_verify_pods_project_uuid_integrity!(installer)
|
|
472
|
+
return unless RNFirebaseSPM.active?
|
|
473
|
+
return unless installer.respond_to?(:pods_project)
|
|
474
|
+
|
|
475
|
+
project = installer.pods_project
|
|
476
|
+
return unless project
|
|
477
|
+
return unless project.respond_to?(:root_object) && project.respond_to?(:objects_by_uuid)
|
|
478
|
+
|
|
479
|
+
root = project.root_object
|
|
480
|
+
resolved = root && project.objects_by_uuid[root.uuid]
|
|
481
|
+
return if root &&
|
|
482
|
+
resolved &&
|
|
483
|
+
resolved.equal?(root) &&
|
|
484
|
+
resolved.respond_to?(:isa) &&
|
|
485
|
+
resolved.isa == 'PBXProject'
|
|
486
|
+
|
|
487
|
+
raise Pod::Informative, <<~MESSAGE
|
|
488
|
+
[react-native-firebase] Pods.xcodeproj rootObject / PBXProject UUID integrity check failed after post_install.
|
|
489
|
+
|
|
490
|
+
CocoaPods' sequential UUID counter was likely reset before React Native's SPM integration called `project.new`, overwriting the Pods `PBXProject` (`rootObject`). Xcode then refuses to open Pods (e.g. `-[XCSwiftPackageProductDependency _setSavedArchiveVersion:]`), and bridging headers cannot see React Native Firebase frameworks.
|
|
491
|
+
|
|
492
|
+
Delete `ios/Pods` and `ios/Podfile.lock`, upgrade `@react-native-firebase/app`, then run `pod install` again. If this persists, report it with your React Native and CocoaPods versions.
|
|
493
|
+
MESSAGE
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Hooks CocoaPods itself (not React Native) so `rnfirebase_add_spm_embed_phase`
|
|
497
|
+
# runs automatically on every `pod install`/`pod update`, without requiring
|
|
498
|
+
# any Podfile change from consumers.
|
|
499
|
+
#
|
|
500
|
+
# We wrap `Pod::Installer#run_podfile_post_install_hooks` -- the method
|
|
501
|
+
# CocoaPods calls, unconditionally, on every install (it's what runs the
|
|
502
|
+
# Podfile's own `post_install do |installer| ... end` block, if any, but the
|
|
503
|
+
# *wrapper* method itself always runs even when the Podfile defines no
|
|
504
|
+
# `post_install` at all). This is the same point in the install lifecycle
|
|
505
|
+
# where consumers previously called `rnfirebase_add_spm_embed_phase`
|
|
506
|
+
# manually, so behavior is unchanged -- only *how* it gets invoked differs.
|
|
507
|
+
#
|
|
508
|
+
# This file is `require`d from each RNFB podspec, which CocoaPods evaluates
|
|
509
|
+
# early, during dependency resolution (itself one of the first steps inside
|
|
510
|
+
# `Installer#install!`). That's early enough for the patch installed here to
|
|
511
|
+
# affect the *later*, fresh call to `run_podfile_post_install_hooks` made
|
|
512
|
+
# further down in that same `install!` run.
|
|
513
|
+
#
|
|
514
|
+
# Why hook CocoaPods instead of React Native: `Pod::Installer` is a stable,
|
|
515
|
+
# semantically-versioned public class that the wider CocoaPods plugin
|
|
516
|
+
# ecosystem already depends on directly, and its shape hasn't materially
|
|
517
|
+
# changed in years. That makes it a meaningfully safer patch target than
|
|
518
|
+
# RN's private, unversioned `react-native/scripts/cocoapods/spm.rb` helper,
|
|
519
|
+
# which isn't part of any documented RN contract. If CocoaPods ever
|
|
520
|
+
# renames/removes this method, the guards below no-op instead of raising,
|
|
521
|
+
# and print a `pod install`-time warning (a visible integration error,
|
|
522
|
+
# rather than a silent runtime dyld crash) telling you to call
|
|
523
|
+
# `rnfirebase_add_spm_embed_phase(installer)` from your own Podfile as a
|
|
524
|
+
# fallback.
|
|
525
|
+
#
|
|
526
|
+
# `installer_class` is only ever overridden by tests -- there's no real
|
|
527
|
+
# `Pod::Installer` outside of a full CocoaPods environment.
|
|
528
|
+
def rnfirebase_hook_cocoapods_post_install!(installer_class = (Pod::Installer if defined?(Pod::Installer)))
|
|
529
|
+
hook_method = :run_podfile_post_install_hooks
|
|
530
|
+
original_method = :rnfirebase_original_run_podfile_post_install_hooks
|
|
531
|
+
|
|
532
|
+
unless installer_class
|
|
533
|
+
if defined?(Pod::UI)
|
|
534
|
+
Pod::UI.warn '[react-native-firebase] `Pod::Installer` isn\'t defined -- automatic Firebase SPM setup ' \
|
|
535
|
+
'(dynamic framework embedding, etc.) was not hooked into `pod install`. Add ' \
|
|
536
|
+
'`rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_install block as a fallback.'
|
|
537
|
+
end
|
|
538
|
+
return
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
was_private = installer_class.private_method_defined?(hook_method)
|
|
542
|
+
unless was_private || installer_class.method_defined?(hook_method)
|
|
543
|
+
if defined?(Pod::UI)
|
|
544
|
+
Pod::UI.warn "[react-native-firebase] `Pod::Installer##{hook_method}` doesn't exist (a CocoaPods " \
|
|
545
|
+
'release may have renamed or removed it) -- automatic Firebase SPM setup was not hooked into ' \
|
|
546
|
+
'`pod install`. Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_install ' \
|
|
547
|
+
'block as a fallback.'
|
|
548
|
+
end
|
|
549
|
+
return
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Already hooked -- e.g. a second RNFB podspec also `require`d this same
|
|
553
|
+
# file within one `pod install` process (this file is required by path,
|
|
554
|
+
# and a hoisted/symlinked dependency layout can resolve to the "same"
|
|
555
|
+
# file more than once). This is expected and idempotent, not a failure --
|
|
556
|
+
# every multi-podspec RNFB install hits this exact path -- so it's
|
|
557
|
+
# deliberately silent rather than warning on every normal install.
|
|
558
|
+
return if installer_class.method_defined?(original_method) || installer_class.private_method_defined?(original_method)
|
|
559
|
+
|
|
560
|
+
installer_class.class_eval do
|
|
561
|
+
alias_method original_method, hook_method
|
|
562
|
+
|
|
563
|
+
define_method(hook_method) do
|
|
564
|
+
# Deliberately not wrapped in a rescue-and-warn like the checks below:
|
|
565
|
+
# there's no working fallback for this combination, so letting `pod
|
|
566
|
+
# install` continue would only delay the same failure to Xcode's
|
|
567
|
+
# build/link step, with a far more confusing error and no pointer back
|
|
568
|
+
# to the actual misconfiguration.
|
|
569
|
+
rnfirebase_fail_if_spm_static_linkage!(self)
|
|
570
|
+
# Soft ensure (warn on unexpected errors) -- paired with the hard
|
|
571
|
+
# `rnfirebase_verify_pods_project_uuid_integrity!` after original
|
|
572
|
+
# post_install, same pattern as embed-phase add + verify below.
|
|
573
|
+
begin
|
|
574
|
+
rnfirebase_ensure_pods_uuid_counter_safe!(self)
|
|
575
|
+
rescue => e
|
|
576
|
+
if defined?(Pod::UI)
|
|
577
|
+
Pod::UI.warn '[react-native-firebase] Couldn\'t raise Pods UUID counter before ' \
|
|
578
|
+
"RN SPM (#{e.class}: #{e.message}). If `pod install` leaves Pods.xcodeproj " \
|
|
579
|
+
'damaged (missing PBXProject / Xcode `_setSavedArchiveVersion`), upgrade ' \
|
|
580
|
+
'react-native-firebase or patch CocoaPods UUID generation.'
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
result = send(original_method)
|
|
584
|
+
# Deliberately not rescued: if RN SPM overwrote `rootObject`, continuing
|
|
585
|
+
# would only delay the failure to Xcode with a worse diagnostic.
|
|
586
|
+
rnfirebase_verify_pods_project_uuid_integrity!(self)
|
|
587
|
+
begin
|
|
588
|
+
rnfirebase_add_spm_embed_phase(self)
|
|
589
|
+
rescue => e
|
|
590
|
+
if defined?(Pod::UI)
|
|
591
|
+
Pod::UI.warn "[react-native-firebase] Couldn't embed Firebase SPM frameworks " \
|
|
592
|
+
"automatically (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_embed_phase(installer)` " \
|
|
593
|
+
'to your Podfile\'s post_install block as a fallback.'
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
# Deliberately outside the `rescue` above, and not itself wrapped in a
|
|
597
|
+
# rescue-and-warn like the softer checks in this method: dynamic
|
|
598
|
+
# framework embedding is load-bearing (without it, the app crashes at
|
|
599
|
+
# launch with a missing-library dyld error), so if the phase still
|
|
600
|
+
# isn't actually on a target that needs it after the call above --
|
|
601
|
+
# whether that call raised, silently no-opped, or only partially
|
|
602
|
+
# applied -- this must abort `pod install` with a clear message
|
|
603
|
+
# rather than let a broken install continue, the same way
|
|
604
|
+
# `rnfirebase_fail_if_spm_static_linkage!` above is deliberately not
|
|
605
|
+
# rescued either.
|
|
606
|
+
rnfirebase_verify_spm_embed_phase_applied!(self)
|
|
607
|
+
begin
|
|
608
|
+
rnfirebase_add_spm_core_to_app_target(self)
|
|
609
|
+
rescue => e
|
|
610
|
+
if defined?(Pod::UI)
|
|
611
|
+
Pod::UI.warn "[react-native-firebase] Couldn't link FirebaseCore into the app target " \
|
|
612
|
+
"automatically (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_core_to_app_target(installer)` " \
|
|
613
|
+
'to your Podfile\'s post_install block as a fallback if your own native code calls ' \
|
|
614
|
+
'FIRApp/FIROptions APIs directly.'
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
begin
|
|
618
|
+
rnfirebase_remove_spm_core_from_app_target(self)
|
|
619
|
+
rescue => e
|
|
620
|
+
if defined?(Pod::UI)
|
|
621
|
+
Pod::UI.warn "[react-native-firebase] Couldn't remove a stale FirebaseCore SPM link from the " \
|
|
622
|
+
"app target automatically (#{e.class}: #{e.message}). If you previously used SPM and have " \
|
|
623
|
+
'since set `$RNFirebaseDisableSPM = true`, remove the "firebase-ios-sdk" Swift Package ' \
|
|
624
|
+
'dependency from your app target manually in Xcode.'
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
begin
|
|
628
|
+
rnfirebase_fix_spm_archive_signature_collision(self)
|
|
629
|
+
rescue => e
|
|
630
|
+
if defined?(Pod::UI)
|
|
631
|
+
Pod::UI.warn '[react-native-firebase] Couldn\'t add the Firebase/Google SPM binary ' \
|
|
632
|
+
"xcframework signature workaround automatically (#{e.class}: #{e.message}). If your " \
|
|
633
|
+
'Release archive fails with `"...xcframework-ios.signature" couldn\'t be copied to ' \
|
|
634
|
+
'"Signatures" because an item with the same name already exists`, add a Run Script ' \
|
|
635
|
+
'build phase to your app target that runs `rm -f ' \
|
|
636
|
+
"\"\\${CONFIGURATION_BUILD_DIR}\"/<TheNameFromTheErrorMessage>.xcframework-ios.signature`."
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
begin
|
|
640
|
+
rnfirebase_apply_spm_build_settings(self)
|
|
641
|
+
rescue => e
|
|
642
|
+
if defined?(Pod::UI)
|
|
643
|
+
Pod::UI.warn "[react-native-firebase] Couldn't apply Firebase SPM build settings " \
|
|
644
|
+
"automatically (#{e.class}: #{e.message}). Add `rnfirebase_apply_spm_build_settings(installer)` " \
|
|
645
|
+
'to your Podfile\'s post_install block as a fallback if Release builds crash at launch with ' \
|
|
646
|
+
'missing FIRComponent registrations, or Xcode reports that a Firebase module such as ' \
|
|
647
|
+
'`FirebaseCoreInternal`/`FirebaseSharedSwift` cannot be resolved.'
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
result
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
installer_class.send(:private, hook_method) if was_private
|
|
654
|
+
rescue => e
|
|
655
|
+
if defined?(Pod::UI)
|
|
656
|
+
Pod::UI.warn "[react-native-firebase] Couldn't hook CocoaPods to auto-embed Firebase SPM " \
|
|
657
|
+
"frameworks (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_embed_phase(installer)` " \
|
|
658
|
+
'to your Podfile\'s post_install block as a fallback.'
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Adds a direct SPM product dependency on `FirebaseCore` to the *app's own*
|
|
663
|
+
# native target(s) -- not just RNFB's pod targets. Runs automatically on every
|
|
664
|
+
# `pod install`/`pod update` alongside `rnfirebase_add_spm_embed_phase` -- see
|
|
665
|
+
# `rnfirebase_hook_cocoapods_post_install!` below -- so you normally never
|
|
666
|
+
# need to call this yourself.
|
|
667
|
+
#
|
|
668
|
+
# Why this needs to exist: every react-native-firebase app is required to
|
|
669
|
+
# `import Firebase` and call `FirebaseApp.configure()` (Swift) /
|
|
670
|
+
# `[FIRApp configure]` (Objective-C) itself -- this isn't an optional pattern
|
|
671
|
+
# for a secondary app instance, it's a strict requirement for all RNFB apps.
|
|
672
|
+
# With CocoaPods-only Firebase dependency resolution, every RNFB pod declares
|
|
673
|
+
# a regular `s.dependency 'Firebase/CoreOnly'`, and CocoaPods automatically
|
|
674
|
+
# propagates the resulting framework/header search paths all the way up to
|
|
675
|
+
# the app's own target -- so that required `FIRApp configure` call has always
|
|
676
|
+
# been able to link against FirebaseCore for free, without the app declaring
|
|
677
|
+
# anything itself. Xcode's own SPM package product dependencies don't
|
|
678
|
+
# propagate the same way: each target needs its own *explicit* product
|
|
679
|
+
# dependency in order to link a package product. Without this, apps using
|
|
680
|
+
# SPM+dynamic linkage fail at Archive time with "Undefined symbols ...
|
|
681
|
+
# _OBJC_CLASS_$_FIRApp", even though the same code links fine under
|
|
682
|
+
# CocoaPods-only resolution.
|
|
683
|
+
def rnfirebase_add_spm_core_to_app_target(installer)
|
|
684
|
+
return unless RNFirebaseSPM.active?
|
|
685
|
+
|
|
686
|
+
pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
|
|
687
|
+
ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
|
|
688
|
+
|
|
689
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
690
|
+
project = aggregate_target.user_project
|
|
691
|
+
project_modified = false
|
|
692
|
+
|
|
693
|
+
project.native_targets.each do |target|
|
|
694
|
+
next unless target.respond_to?(:package_product_dependencies)
|
|
695
|
+
next unless target.respond_to?(:shell_script_build_phases)
|
|
696
|
+
next unless target.shell_script_build_phases.any? { |phase| phase.name == '[CP] Embed Pods Frameworks' }
|
|
697
|
+
next if target.package_product_dependencies.any? { |dep| dep.product_name == 'FirebaseCore' }
|
|
698
|
+
|
|
699
|
+
pkg = project.root_object.package_references.find do |candidate|
|
|
700
|
+
candidate.class == pkg_class && candidate.repositoryURL == RNFirebaseSPM.url
|
|
701
|
+
end
|
|
702
|
+
if !pkg
|
|
703
|
+
pkg = project.new(pkg_class)
|
|
704
|
+
pkg.repositoryURL = RNFirebaseSPM.url
|
|
705
|
+
pkg.requirement = { kind: 'upToNextMajorVersion', minimumVersion: RNFirebaseSPM.version }
|
|
706
|
+
project.root_object.package_references << pkg
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
if defined?(Pod) && defined?(Pod::UI)
|
|
710
|
+
Pod::UI.puts "[react-native-firebase] #{target.name}: ".yellow +
|
|
711
|
+
'Linking FirebaseCore directly into the app target (SPM) so native code that calls ' \
|
|
712
|
+
'FIRApp/FIROptions APIs directly can resolve those symbols.'
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
ref = project.new(ref_class)
|
|
716
|
+
ref.package = pkg
|
|
717
|
+
ref.product_name = 'FirebaseCore'
|
|
718
|
+
target.package_product_dependencies << ref
|
|
719
|
+
|
|
720
|
+
target.build_configurations.each do |config|
|
|
721
|
+
build_settings = target.build_settings(config.name)
|
|
722
|
+
# Normalize first: Xcode/Xcodeproj may already represent
|
|
723
|
+
# SWIFT_INCLUDE_PATHS as a whitespace-separated String rather than an
|
|
724
|
+
# Array, depending on how the consumer's project was authored. A bare
|
|
725
|
+
# `||= ['$(inherited)']` only covers the nil case -- calling `.push`
|
|
726
|
+
# on an existing String value raises NoMethodError and crashes `pod
|
|
727
|
+
# install` for that target.
|
|
728
|
+
paths = rnfirebase_build_setting_list(build_settings['SWIFT_INCLUDE_PATHS'])
|
|
729
|
+
search_path = '${SYMROOT}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/'
|
|
730
|
+
paths << search_path unless paths.include?(search_path)
|
|
731
|
+
build_settings['SWIFT_INCLUDE_PATHS'] = paths
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
project_modified = true
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
project.save if project_modified
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Undoes `rnfirebase_add_spm_core_to_app_target` -- removes the direct SPM
|
|
742
|
+
# `FirebaseCore` product dependency (and, once nothing else references it,
|
|
743
|
+
# the "firebase-ios-sdk" package reference itself) from the app's own native
|
|
744
|
+
# target(s). Runs automatically on every `pod install`/`pod update` alongside
|
|
745
|
+
# `rnfirebase_add_spm_core_to_app_target` -- see
|
|
746
|
+
# `rnfirebase_hook_cocoapods_post_install!` above -- so you normally never
|
|
747
|
+
# need to call this yourself.
|
|
748
|
+
#
|
|
749
|
+
# Why this needs to exist: `rnfirebase_add_spm_core_to_app_target` writes into
|
|
750
|
+
# the *app's own* Xcode project (`aggregate_target.user_project`, e.g.
|
|
751
|
+
# `testing.xcodeproj`) -- a different project than the one React Native's own
|
|
752
|
+
# SPM integration manages (`installer.pods_project`, i.e. `Pods.xcodeproj`).
|
|
753
|
+
# RN's `SPMManager#clean_spm_dependencies_from_target` (in
|
|
754
|
+
# `react-native/scripts/cocoapods/spm.rb`) only ever clears package
|
|
755
|
+
# references from `pods_project` on every `pod install` -- it has no
|
|
756
|
+
# knowledge of, and never touches, the app-project-level reference added
|
|
757
|
+
# above. So once SPM has been active at least once and the resulting
|
|
758
|
+
# `FirebaseCore` product dependency has been committed into the app's
|
|
759
|
+
# `.pbxproj` (as it normally would be), switching to
|
|
760
|
+
# `$RNFirebaseDisableSPM = true` and reinstalling left that stale SPM wiring
|
|
761
|
+
# in place forever: the app target ended up simultaneously linked against
|
|
762
|
+
# Xcode's SPM-resolved `firebase-ios-sdk` package graph *and* the freshly
|
|
763
|
+
# CocoaPods-resolved `Firebase/CoreOnly` pod, and the two copies of
|
|
764
|
+
# Firebase's module graph collided -- surfacing as `redefinition of module
|
|
765
|
+
# 'Firebase'` at compile time, and as duplicate App-Intents-metadata build
|
|
766
|
+
# commands at Archive time.
|
|
767
|
+
def rnfirebase_remove_spm_core_from_app_target(installer)
|
|
768
|
+
return if RNFirebaseSPM.active?
|
|
769
|
+
|
|
770
|
+
pkg_class = Xcodeproj::Project::Object::XCRemoteSwiftPackageReference
|
|
771
|
+
ref_class = Xcodeproj::Project::Object::XCSwiftPackageProductDependency
|
|
772
|
+
|
|
773
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
774
|
+
project = aggregate_target.user_project
|
|
775
|
+
project_modified = false
|
|
776
|
+
|
|
777
|
+
project.native_targets.each do |target|
|
|
778
|
+
next unless target.respond_to?(:package_product_dependencies)
|
|
779
|
+
|
|
780
|
+
stale_refs = target.package_product_dependencies.select do |dep|
|
|
781
|
+
dep.class == ref_class && dep.product_name == 'FirebaseCore' && dep.package&.repositoryURL == RNFirebaseSPM.url
|
|
782
|
+
end
|
|
783
|
+
next if stale_refs.empty?
|
|
784
|
+
|
|
785
|
+
if defined?(Pod) && defined?(Pod::UI)
|
|
786
|
+
Pod::UI.puts "[react-native-firebase] #{target.name}: ".yellow +
|
|
787
|
+
'SPM disabled -- removing the stale FirebaseCore Swift Package link left on the app target.'
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
stale_refs.each do |ref|
|
|
791
|
+
target.package_product_dependencies.delete(ref)
|
|
792
|
+
ref.remove_from_project
|
|
793
|
+
end
|
|
794
|
+
project_modified = true
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
project.root_object.package_references
|
|
798
|
+
.select { |pkg| pkg.class == pkg_class && pkg.repositoryURL == RNFirebaseSPM.url }
|
|
799
|
+
.each do |pkg|
|
|
800
|
+
next if pkg.referrers.any? { |referrer| referrer.class == ref_class }
|
|
801
|
+
|
|
802
|
+
project.root_object.package_references.delete(pkg)
|
|
803
|
+
pkg.remove_from_project
|
|
804
|
+
project_modified = true
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
project.save if project_modified
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
# Works around a long-standing Xcode Archive bug (present since Xcode 15,
|
|
812
|
+
# still reproducing on Xcode 26) where a Swift Package binary target's
|
|
813
|
+
# `.signature` provenance file gets staged into more than one target's build
|
|
814
|
+
# directory when multiple targets in the workspace transitively depend on the
|
|
815
|
+
# same binary artifact. Xcode's Archive action then tries to copy every
|
|
816
|
+
# staged copy into the shared `<Archive>.xcarchive/Signatures/` directory,
|
|
817
|
+
# and the second copy collides with the first:
|
|
818
|
+
#
|
|
819
|
+
# "GoogleAppMeasurementIdentitySupport.xcframework-ios.signature" couldn't
|
|
820
|
+
# be copied to "Signatures" because an item with the same name already
|
|
821
|
+
# exists.
|
|
822
|
+
#
|
|
823
|
+
# This isn't specific to react-native-firebase -- the same class of bug, with
|
|
824
|
+
# the same fix, has been reported for other CocoaPods+SPM binary xcframeworks
|
|
825
|
+
# (Mapbox: CocoaPods/CocoaPods#12022; MapLibre: maplibre-react-native#1489;
|
|
826
|
+
# Lottie).
|
|
827
|
+
#
|
|
828
|
+
# It can hit *any* binary xcframework in the resolved graph, not just
|
|
829
|
+
# Analytics-related ones -- e.g. Google's own `google/GoogleAppMeasurement.git`
|
|
830
|
+
# SPM package unconditionally links `GoogleAdsOnDeviceConversion` (from the
|
|
831
|
+
# *separate* `googleads/google-ads-on-device-conversion-ios-sdk` package) as a
|
|
832
|
+
# dependency of `GoogleAppMeasurementTarget`, completely independent of
|
|
833
|
+
# RNFBAnalytics's own *optional* `spm_dependency` call for it (gated behind
|
|
834
|
+
# `$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion`, which turns
|
|
835
|
+
# out to only matter for CocoaPods-only resolution) -- none of that showed up
|
|
836
|
+
# as a reference in our own podspecs or pbxprojs; it only turned up by
|
|
837
|
+
# inspecting the actual checked-out Package.swift manifests under
|
|
838
|
+
# DerivedData/.../SourcePackages/checkouts. Confirmed locally: fixing one
|
|
839
|
+
# binary artifact just surfaces the collision on the next one on a subsequent
|
|
840
|
+
# archive run, so `RNFIREBASE_SPM_SIGNATURE_FIX_ARTIFACT_NAMES` above lists
|
|
841
|
+
# every `.binaryTarget` xcframework in the resolved graph (enumerated from
|
|
842
|
+
# `SourcePackages/workspace-state.json`, not guessed) so they're all covered
|
|
843
|
+
# in one pass.
|
|
844
|
+
#
|
|
845
|
+
# Deliberately scoped to this known artifact-name list rather than a bare
|
|
846
|
+
# `*.signature` glob -- broad enough to cover this whole binary family without
|
|
847
|
+
# also silently masking an unrelated, legitimate "file already exists"
|
|
848
|
+
# failure from some other SPM package in a consumer's own app.
|
|
849
|
+
def rnfirebase_fix_spm_archive_signature_collision(installer)
|
|
850
|
+
return unless RNFirebaseSPM.active?
|
|
851
|
+
|
|
852
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
853
|
+
project = aggregate_target.user_project
|
|
854
|
+
project_modified = false
|
|
855
|
+
|
|
856
|
+
project.native_targets.each do |target|
|
|
857
|
+
next unless target.respond_to?(:shell_script_build_phases)
|
|
858
|
+
next unless target.shell_script_build_phases.any? { |phase| phase.name == '[CP] Embed Pods Frameworks' }
|
|
859
|
+
|
|
860
|
+
shell_script = RNFIREBASE_SPM_SIGNATURE_FIX_ARTIFACT_NAMES.map { |name|
|
|
861
|
+
"rm -f \"${CONFIGURATION_BUILD_DIR}\"/#{name}.xcframework-ios.signature"
|
|
862
|
+
}.join("\n") + "\n"
|
|
863
|
+
|
|
864
|
+
changed = rnfirebase_upsert_shell_script_phase!(
|
|
865
|
+
target,
|
|
866
|
+
RNFIREBASE_SPM_SIGNATURE_FIX_PHASE_NAME,
|
|
867
|
+
shell_script: shell_script,
|
|
868
|
+
shell_path: '/bin/sh'
|
|
869
|
+
)
|
|
870
|
+
project_modified ||= changed
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
project.save if project_modified
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
# Applies Release/module-build-system settings that Firebase SPM + dynamic
|
|
878
|
+
# linkage requires on the app's own native target(s) and on the Pods
|
|
879
|
+
# project. Runs automatically on every `pod install`/`pod update` -- see
|
|
880
|
+
# `rnfirebase_hook_cocoapods_post_install!` above -- so you normally never
|
|
881
|
+
# need to call this yourself.
|
|
882
|
+
#
|
|
883
|
+
# 1. `-ObjC` in `OTHER_LDFLAGS` (app target, every configuration): under SPM
|
|
884
|
+
# + dynamic linkage, dead-code stripping can drop Objective-C
|
|
885
|
+
# classes/categories that are only ever discovered via runtime reflection
|
|
886
|
+
# rather than a direct static reference -- e.g. Firebase's
|
|
887
|
+
# FIRLibrary/FIRComponent registration used by RNFBCrashlyticsInitProvider
|
|
888
|
+
# -- which otherwise crashes the app at launch, but only in Release/
|
|
889
|
+
# Archive builds (a TestFlight-only failure that's hard to reproduce from
|
|
890
|
+
# a local Debug build). `-ObjC` forces the linker to keep any object file
|
|
891
|
+
# that defines an ObjC class/category, without disabling dead-code
|
|
892
|
+
# stripping or optimizations for anything else, so it doesn't meaningfully
|
|
893
|
+
# grow the binary or slow down Release builds.
|
|
894
|
+
#
|
|
895
|
+
# 2. `SWIFT_ENABLE_EXPLICIT_MODULES = 'NO'` and `CLANG_ENABLE_EXPLICIT_MODULES
|
|
896
|
+
# = 'NO'` (app target and Pods project, every configuration): Xcode 26
|
|
897
|
+
# enables explicit modules -- separately for Swift and for Clang -- by
|
|
898
|
+
# default, but Firebase's SPM internal targets (`FirebaseCoreInternal`,
|
|
899
|
+
# `FirebaseSharedSwift`) aren't exposed as public products. Explicit
|
|
900
|
+
# modules is a build-system-wide setting for Swift Package products
|
|
901
|
+
# resolved via the app's own project/scheme (SPM packages don't have
|
|
902
|
+
# their own toggle for it), so both settings have to be disabled on the
|
|
903
|
+
# app project too, not just the Pods project -- otherwise pure-Swift
|
|
904
|
+
# Firebase SPM products (Storage, RemoteConfig, Database, InAppMessaging)
|
|
905
|
+
# intermittently fail to have their generated ObjC interop header
|
|
906
|
+
# (*-Swift.h) available when the consuming RNFB Pods target starts
|
|
907
|
+
# compiling. This does NOT disable SPM -- it only makes Swift and Clang
|
|
908
|
+
# use implicit module discovery (the Xcode 16 default) uniformly across
|
|
909
|
+
# the app, CocoaPods, and SPM build boundary. See
|
|
910
|
+
# okf-bundle/ios-spm-native-imports.md.
|
|
911
|
+
#
|
|
912
|
+
# NOT applied (tried and reverted): `-fmodules -fcxx-modules` in
|
|
913
|
+
# `OTHER_CPLUSPLUSFLAGS`, to let `.mm` files use `@import FirebaseCore;`/
|
|
914
|
+
# `@import <ProductName>;` -- every RNFB module's Objective-C header falls
|
|
915
|
+
# back to that Clang module-import syntax (as opposed to a plain `#import
|
|
916
|
+
# <Header.h>`) when `__has_include(<ProductName/Header.h>)` fails to find a
|
|
917
|
+
# classic `<Module/Header.h>`-style include path. It turns out that fallback
|
|
918
|
+
# is never actually exercised for the app target: `rnfirebase_add_spm_core_to_app_target`
|
|
919
|
+
# above already links `FirebaseCore` into the app target as a direct SPM
|
|
920
|
+
# product dependency, and Xcode's SPM integration then adds header search
|
|
921
|
+
# paths for *every* product in that resolved package graph (not just
|
|
922
|
+
# `FirebaseCore`) to any target with at least one product dependency on it
|
|
923
|
+
# -- so `__has_include(<FirebaseAppCheck/FirebaseAppCheck.h>)` (etc.)
|
|
924
|
+
# already succeeds for the app target's own files, and the `@import`
|
|
925
|
+
# fallback branch is dead code there. Forcing C++ modules on anyway just
|
|
926
|
+
# breaks things: Xcode then tries to build Clang modules for anything the
|
|
927
|
+
# app `#import`s, including React Native's own `use_frameworks!` products,
|
|
928
|
+
# and several of those (`glog`, `cxxreact` -- via `folly`) aren't clean
|
|
929
|
+
# under `-fcxx-modules` (e.g. "import of module 'glog.log_severity' appears
|
|
930
|
+
# within namespace 'google'", "no type named 'is_dynamic' in namespace
|
|
931
|
+
# 'facebook::xplat::detail'"), failing the Archive build with "could not
|
|
932
|
+
# build module 'glog'"/`'cxxreact'`. Confirmed via a real `xcodebuild
|
|
933
|
+
# archive`: with this flag, the build fails on React Native's own C++ pods;
|
|
934
|
+
# without it, `@import` is never reached and the archive succeeds cleanly.
|
|
935
|
+
def rnfirebase_apply_spm_build_settings(installer)
|
|
936
|
+
return unless RNFirebaseSPM.active?
|
|
937
|
+
|
|
938
|
+
explicit_modules_settings = %w[SWIFT_ENABLE_EXPLICIT_MODULES CLANG_ENABLE_EXPLICIT_MODULES]
|
|
939
|
+
|
|
940
|
+
add_flag = lambda do |build_settings, key, flag|
|
|
941
|
+
current = rnfirebase_build_setting_list(build_settings[key])
|
|
942
|
+
|
|
943
|
+
next false if current.include?(flag)
|
|
944
|
+
|
|
945
|
+
build_settings[key] = (current << flag).join(' ')
|
|
946
|
+
true
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
installer.aggregate_targets.each do |aggregate_target|
|
|
950
|
+
project = aggregate_target.user_project
|
|
951
|
+
project_modified = false
|
|
952
|
+
|
|
953
|
+
project.native_targets.each do |target|
|
|
954
|
+
target.build_configurations.each do |config|
|
|
955
|
+
ldflags_changed = add_flag.call(config.build_settings, 'OTHER_LDFLAGS', '-ObjC')
|
|
956
|
+
project_modified ||= ldflags_changed
|
|
957
|
+
|
|
958
|
+
explicit_modules_settings.each do |setting|
|
|
959
|
+
unless config.build_settings[setting] == 'NO'
|
|
960
|
+
config.build_settings[setting] = 'NO'
|
|
961
|
+
project_modified = true
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
end
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
project.save if project_modified
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
installer.pods_project.targets.each do |target|
|
|
971
|
+
target.build_configurations.each do |config|
|
|
972
|
+
explicit_modules_settings.each do |setting|
|
|
973
|
+
config.build_settings[setting] = 'NO'
|
|
974
|
+
end
|
|
975
|
+
end
|
|
976
|
+
end
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
rnfirebase_hook_cocoapods_post_install!
|
|
980
|
+
|
|
981
|
+
# @param spec [Pod::Specification] The podspec object (the `s` in podspec DSL)
|
|
982
|
+
# @param version [String] Firebase SDK version (e.g., '12.10.0')
|
|
983
|
+
# @param spm_products [Array<String>] SPM product names (e.g., ['FirebaseAuth'])
|
|
984
|
+
# @param pods [Array<String>, String] CocoaPods dependency names with optional version
|
|
985
|
+
# Can be a single string like 'Firebase/Auth' or an array like ['Firebase/Messaging', 'FirebaseCoreExtension']
|
|
986
|
+
def firebase_dependency(spec, version, spm_products, pods)
|
|
987
|
+
if defined?(spm_dependency) && !rnfirebase_spm_disabled?
|
|
988
|
+
# Tracked ourselves (rather than inspecting RN's internal `SPM` object's
|
|
989
|
+
# dependency list) so `rnfirebase_add_spm_embed_phase` doesn't depend on
|
|
990
|
+
# any RN-internal state shape -- only on whether *we* ever took this path.
|
|
991
|
+
RNFirebaseSPM.activate!(version)
|
|
992
|
+
if defined?(Pod) && defined?(Pod::UI)
|
|
993
|
+
Pod::UI.puts "[react-native-firebase] #{spec.name}: ".yellow +
|
|
994
|
+
"Using SPM for Firebase dependency resolution (products: #{spm_products.join(', ')})"
|
|
995
|
+
end
|
|
996
|
+
spm_dependency(spec,
|
|
997
|
+
url: RNFirebaseSPM.url,
|
|
998
|
+
requirement: { kind: 'upToNextMajorVersion', minimumVersion: version },
|
|
999
|
+
products: spm_products
|
|
1000
|
+
)
|
|
1001
|
+
else
|
|
1002
|
+
if defined?(Pod) && defined?(Pod::UI)
|
|
1003
|
+
if rnfirebase_spm_disabled?
|
|
1004
|
+
Pod::UI.puts "[react-native-firebase] #{spec.name}: ".yellow +
|
|
1005
|
+
"SPM disabled ($RNFirebaseDisableSPM = true), using CocoaPods for Firebase dependencies"
|
|
1006
|
+
elsif !defined?(spm_dependency)
|
|
1007
|
+
Pod::UI.puts "[react-native-firebase] #{spec.name}: ".yellow +
|
|
1008
|
+
"SPM not available (React Native < 0.75), using CocoaPods for Firebase dependencies"
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
pods = [pods] unless pods.is_a?(Array)
|
|
1012
|
+
pods.each do |pod|
|
|
1013
|
+
spec.dependency pod, version
|
|
1014
|
+
end
|
|
1015
|
+
end
|
|
1016
|
+
end
|