Skip to content

Commit cdf99cd

Browse files
Mastersam07mikehardy
authored andcommitted
fix(app, ios): clarify SPM static linkage error when use_frameworks is absent
1 parent 89e270f commit cdf99cd

4 files changed

Lines changed: 160 additions & 14 deletions

File tree

‎docs/ios-spm.mdx‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ use_frameworks! :linkage => :dynamic
3434
```
3535

3636
Do not use RNFB's SPM mode with `use_frameworks! :linkage => :static`.
37+
A Podfile with no `use_frameworks!` line (or `use_frameworks! false`) is static
38+
linkage too — that is the stock React Native / bare Expo default.
3739
firebase-ios-sdk SPM products are automatic libraries (plain `.library(...)`,
3840
not `type: .dynamic`), so each pod that depends on them embeds its own copy.
3941
With static pod linkage those copies collide at link time as duplicate-symbol
@@ -200,7 +202,16 @@ time with duplicate symbols (and `pod install` rejects the combination).
200202
Remove `$RNFirebaseDisableSPM = true` and Expo `ios.disableSPM`. Use
201203
`use_frameworks! :linkage => :dynamic` and React Native 0.75+.
202204
- **`pod install` fails with "SPM + static linkage is not supported":** Use
203-
dynamic linkage (`use_frameworks! :linkage => :dynamic`).
205+
dynamic linkage (`use_frameworks! :linkage => :dynamic`). A Podfile with no
206+
`use_frameworks!` line (or `use_frameworks! false`), the stock React Native /
207+
Expo default, is static linkage too.
208+
- With the stock template Podfile, `USE_FRAMEWORKS=dynamic pod install` also
209+
works, but it must be set for every `pod install` (including CI); the
210+
Podfile line is the durable fix.
211+
- Expo: set `ios.useFrameworks` to `"dynamic"` in the `expo-build-properties`
212+
plugin (see [Expo projects](#expo-projects)), then re-run
213+
`npx expo prebuild`. Only if you maintain `ios/` by hand (no prebuild), set
214+
`"ios.useFrameworks": "dynamic"` in `ios/Podfile.properties.json` instead.
204215
- **`FirebaseApp.app()` is `nil` / Auth looks signed-out inside another native
205216
pod, or runtime logs duplicate `FIRApp` classes:** SPM mode cannot share one
206217
`FirebaseCore` across dynamic pod frameworks. See

‎packages/app/__tests__/firebase_spm_shape_test.rb‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,48 @@ def test_build_type_static_matches_real_linkage_semantics
137137
assert Pod::BuildType.static_library.static?
138138
end
139139

140+
# Real Pod::Podfile target definitions (no install context needed) for
141+
# every Podfile linkage form rnfirebase_fail_if_spm_static_linkage!
142+
# distinguishes: `build_type.static?` selects the failing targets and
143+
# `uses_frameworks?` splits "no use_frameworks!" (static libraries) from
144+
# `use_frameworks! :linkage => :static`. MockTargetDefinition in
145+
# firebase_spm_test.rb assumes exactly these pairs.
146+
def test_target_definition_static_and_uses_frameworks_match_podfile_linkage
147+
podfile = Pod::Podfile.new do
148+
# No block: a target with no `use_frameworks!` line at all.
149+
target 'NoFrameworks'
150+
target 'FrameworksFalse' do
151+
use_frameworks! false
152+
end
153+
target 'StaticFrameworks' do
154+
use_frameworks! linkage: :static
155+
target 'StaticChild' do
156+
inherit! :complete
157+
end
158+
end
159+
target 'DynamicFrameworks' do
160+
use_frameworks! linkage: :dynamic
161+
end
162+
end
163+
164+
expected = {
165+
'NoFrameworks' => [true, false],
166+
'FrameworksFalse' => [true, false],
167+
'StaticFrameworks' => [true, true],
168+
'DynamicFrameworks' => [false, true],
169+
'StaticChild' => [true, true]
170+
}
171+
expected.each do |name, pair|
172+
definition = podfile.target_definitions.fetch(name)
173+
assert_equal pair, [definition.build_type.static?, definition.uses_frameworks?],
174+
"Pod::Podfile target `#{name}` no longer reports " \
175+
"[build_type.static?, uses_frameworks?] == #{pair.inspect} -- " \
176+
'rnfirebase_fail_if_spm_static_linkage! (and MockTargetDefinition) ' \
177+
'rely on this pair to select and label static-linkage targets.'
178+
end
179+
assert_equal 'StaticFrameworks', podfile.target_definitions.fetch('StaticChild').parent.name
180+
end
181+
140182
def test_pod_target_exposes_name_and_build_type
141183
assert Pod::PodTarget.method_defined?(:name)
142184
build_type_defined = Pod::PodTarget.method_defined?(:build_type) ||

‎packages/app/__tests__/firebase_spm_test.rb‎

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,19 @@ def ==(other)
253253
end
254254
end
255255

256+
# `uses_frameworks: false` models a Podfile with no `use_frameworks!` at all:
257+
# real CocoaPods then reports a `static_library` build type, which is also
258+
# `static?`, so the check must name that case distinctly in its message.
256259
class MockTargetDefinition
257260
attr_reader :build_type
258261

259-
def initialize(static: false)
260-
@build_type = MockBuildType.new(static: static)
262+
def initialize(static: false, uses_frameworks: true)
263+
@uses_frameworks = uses_frameworks
264+
@build_type = uses_frameworks ? MockBuildType.new(static: static) : MockBuildType.static_library
265+
end
266+
267+
def uses_frameworks?
268+
@uses_frameworks
261269
end
262270
end
263271

@@ -272,10 +280,10 @@ def initialize(static: false)
272280
class MockAggregateTarget
273281
attr_reader :user_project, :name, :target_definition
274282

275-
def initialize(user_project, name: 'Pods-testing', static_linkage: false)
283+
def initialize(user_project, name: 'Pods-testing', static_linkage: false, uses_frameworks: true)
276284
@user_project = user_project
277285
@name = name
278-
@target_definition = MockTargetDefinition.new(static: static_linkage)
286+
@target_definition = MockTargetDefinition.new(static: static_linkage, uses_frameworks: uses_frameworks)
279287
end
280288
end
281289

@@ -2239,9 +2247,73 @@ def test_fail_fast_raises_pod_informative_when_spm_active_and_a_target_is_static
22392247
rnfirebase_fail_if_spm_static_linkage!(installer)
22402248
end
22412249
assert_includes error.message, 'SPM + static linkage is not supported'
2242-
assert_includes error.message, 'Pods-testing'
2243-
assert_includes error.message, 'use_frameworks! :linkage => :dynamic'
2244-
refute_includes error.message, '$RNFirebaseDisableSPM = true'
2250+
assert_includes error.message, '- `use_frameworks! :linkage => :static` ' \
2251+
'(incl. `USE_FRAMEWORKS=static` / Expo `useFrameworks: "static"`): Pods-testing'
2252+
refute_includes error.message, 'no `use_frameworks!`'
2253+
assert_static_linkage_fix_wording(error.message)
2254+
end
2255+
2256+
# Remediation wording shared by every static-linkage failure: dynamic
2257+
# linkage only (Podfile line, `USE_FRAMEWORKS=dynamic` env with its
2258+
# every-install caveat, Expo plugin config + prebuild with the generated
2259+
# Podfile.properties.json only for hand-maintained ios/). The SPM opt-out
2260+
# must not be offered here.
2261+
def assert_static_linkage_fix_wording(message)
2262+
assert_includes message, "Fix your Podfile to use dynamic linkage, then run `pod install` again:\n\n " \
2263+
"use_frameworks! :linkage => :dynamic\n"
2264+
assert_includes message, 'With the stock template Podfile you can instead run ' \
2265+
'`USE_FRAMEWORKS=dynamic pod install`, but it must be set for every `pod install` ' \
2266+
'(including CI) or linkage reverts to static; the Podfile line above is the durable fix.'
2267+
assert_includes message, 'Expo: set `ios.useFrameworks` to `"dynamic"` in the `expo-build-properties` ' \
2268+
'plugin config, then re-run `npx expo prebuild`'
2269+
assert_includes message, 'Only if you maintain `ios/` by hand (no prebuild), ' \
2270+
'set `"ios.useFrameworks": "dynamic"` in `ios/Podfile.properties.json`'
2271+
assert_includes message, 'See https://rnfirebase.io/ios-spm'
2272+
refute_includes message, 'RNFirebaseDisableSPM'
2273+
refute_includes message, 'disableSPM'
2274+
refute_includes message, 'Opt out of SPM'
2275+
end
2276+
2277+
def test_fail_fast_names_missing_use_frameworks_as_static_libraries
2278+
load_firebase_spm
2279+
RNFirebaseSPM.activate!('12.10.0')
2280+
2281+
# Stock React Native / bare Expo Podfile: no `use_frameworks!` line, so
2282+
# CocoaPods builds every pod as a static library. That is static linkage
2283+
# too, but the message must not only point at a `use_frameworks!` line
2284+
# the user doesn't have.
2285+
installer = MockInstaller.new([
2286+
MockAggregateTarget.new(nil, name: 'Pods-testing', uses_frameworks: false)
2287+
])
2288+
2289+
error = assert_raises(Pod::Informative) do
2290+
rnfirebase_fail_if_spm_static_linkage!(installer)
2291+
end
2292+
assert_includes error.message,
2293+
'- no `use_frameworks!` (or `use_frameworks! false`) -- ' \
2294+
'pods build as static libraries: Pods-testing'
2295+
refute_includes error.message, '- `use_frameworks! :linkage => :static`'
2296+
assert_static_linkage_fix_wording(error.message)
2297+
end
2298+
2299+
def test_fail_fast_groups_static_library_and_static_framework_targets
2300+
load_firebase_spm
2301+
RNFirebaseSPM.activate!('12.10.0')
2302+
2303+
installer = MockInstaller.new([
2304+
MockAggregateTarget.new(nil, name: 'Pods-app', uses_frameworks: false),
2305+
MockAggregateTarget.new(nil, name: 'Pods-ext', static_linkage: true)
2306+
])
2307+
2308+
error = assert_raises(Pod::Informative) do
2309+
rnfirebase_fail_if_spm_static_linkage!(installer)
2310+
end
2311+
expected = <<~DETECTED
2312+
Detected static pod linkage:
2313+
- no `use_frameworks!` (or `use_frameworks! false`) -- pods build as static libraries: Pods-app
2314+
- `use_frameworks! :linkage => :static` (incl. `USE_FRAMEWORKS=static` / Expo `useFrameworks: "static"`): Pods-ext
2315+
DETECTED
2316+
assert_includes error.message, expected
22452317
end
22462318

22472319
def test_fail_fast_ignores_aggregate_targets_own_always_static_build_type

‎packages/app/firebase_spm.rb‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,7 @@ def rnfirebase_verify_spm_embed_phase_applied!(installer)
577577
# (`duplicate symbol`). Under dynamic pod linkage the app builds, but each
578578
# dynamic framework still keeps a private copy (runtime class duplication /
579579
# split FirebaseApp registries). There is no supported combination of RNFB's
580-
# SPM mode with static linkage; the fix is always to switch to dynamic
581-
# linkage or opt out of SPM entirely.
580+
# SPM mode with static linkage; the fix is to switch to dynamic linkage.
582581
#
583582
# Raises `Pod::Informative` -- CocoaPods' own user-facing error class, which
584583
# `pod install` prints as a plain, readable message with no Ruby backtrace --
@@ -604,18 +603,40 @@ def rnfirebase_fail_if_spm_static_linkage!(installer)
604603
end
605604
return if static_targets.empty?
606605

607-
target_names = static_targets.map(&:name).join(', ')
606+
# `build_type.static?` is true for two different Podfiles: no
607+
# `use_frameworks!` at all (CocoaPods' default static libraries -- the stock
608+
# React Native / bare Expo Podfile) and `use_frameworks! :linkage => :static`.
609+
# Name the one the user actually has so they can find it in their Podfile.
610+
library_targets, framework_targets = static_targets.partition do |target|
611+
!target.target_definition.uses_frameworks?
612+
end
613+
detected = []
614+
unless library_targets.empty?
615+
library_label = 'no `use_frameworks!` (or `use_frameworks! false`) -- pods build as static libraries'
616+
detected << " - #{library_label}: #{library_targets.map(&:name).join(', ')}"
617+
end
618+
unless framework_targets.empty?
619+
framework_label = '`use_frameworks! :linkage => :static` ' \
620+
'(incl. `USE_FRAMEWORKS=static` / Expo `useFrameworks: "static"`)'
621+
detected << " - #{framework_label}: #{framework_targets.map(&:name).join(', ')}"
622+
end
608623

609624
raise Pod::Informative, <<~MESSAGE
610-
[react-native-firebase] SPM + static linkage is not supported (target(s): #{target_names}).
625+
[react-native-firebase] SPM + static linkage is not supported.
611626
612-
firebase-ios-sdk's Swift Package products are automatic libraries (not `type: .dynamic`), so each react-native-firebase pod that resolves Firebase via SPM embeds its own copy. With `use_frameworks! :linkage => :static` those copies collide at link time as duplicate-symbol errors.
627+
Detected static pod linkage:
628+
#{detected.join("\n")}
629+
630+
firebase-ios-sdk's Swift Package products are automatic libraries (not `type: .dynamic`), so each react-native-firebase pod that resolves Firebase via SPM embeds its own copy. With static pod linkage those copies collide at link time as duplicate-symbol errors.
613631
614632
Fix your Podfile to use dynamic linkage, then run `pod install` again:
615633
616634
use_frameworks! :linkage => :dynamic
617635
618-
CocoaPods Firebase is no longer available with Firebase Apple SDK 13+. See https://rnfirebase.io/ios-spm
636+
With the stock template Podfile you can instead run `USE_FRAMEWORKS=dynamic pod install`, but it must be set for every `pod install` (including CI) or linkage reverts to static; the Podfile line above is the durable fix.
637+
Expo: set `ios.useFrameworks` to `"dynamic"` in the `expo-build-properties` plugin config, then re-run `npx expo prebuild`. Only if you maintain `ios/` by hand (no prebuild), set `"ios.useFrameworks": "dynamic"` in `ios/Podfile.properties.json`.
638+
639+
See https://rnfirebase.io/ios-spm
619640
MESSAGE
620641
end
621642

0 commit comments

Comments
 (0)