feat(ios): add SPM dependency resolution support alongside CocoaPods#8933
feat(ios): add SPM dependency resolution support alongside CocoaPods#8933jsnavarroc wants to merge 3 commits intoinvertase:mainfrom
Conversation
|
@jsnavarroc is attempting to deploy a commit to the Invertase Team on Vercel. A member of the Team first needs to authorize it. |
Add dual SPM/CocoaPods dependency resolution for Firebase iOS SDK. When React Native >= 0.75 is detected, Firebase dependencies are resolved via Swift Package Manager (spm_dependency). For older versions or when explicitly disabled ($RNFirebaseDisableSPM = true), CocoaPods is used. Changes: - Add firebase_spm.rb helper with firebase_dependency() function - Add firebaseSpmUrl to packages/app/package.json (single source of truth) - Update all 16 podspecs to use firebase_dependency() - Add #if __has_include guards in 43 native iOS files for dual imports - Add CI matrix (spm × cocoapods × debug × release) in E2E workflow - Add Ruby unit tests for firebase_spm.rb - Add documentation at docs/ios-spm.md
…dSupport is true When $RNFirebaseAnalyticsWithoutAdIdSupport = true with SPM enabled, FirebaseAnalytics pulls in GoogleAppMeasurement which contains APMETaskManager and APMMeasurement cross-references. These cause linker errors when FirebasePerformance is not installed. Switch to FirebaseAnalyticsCore (-> GoogleAppMeasurementCore) in that case, which has no IDFA and no APM symbols. CocoaPods path is unchanged. docs: add Section 8 with 5 real integration bugs found during tvOS Xcode 26 migration and their solutions
fix(analytics): use FirebaseAnalyticsCore when WithoutAdIdSupport + SPM
Additional fix included:
|
|
Hey @mikehardy — could you approve the workflows to run on this PR when you get a chance? The CI checks are blocked waiting for maintainer approval. Happy to address any feedback once they run. Thanks! |
mikehardy
left a comment
There was a problem hiding this comment.
Wow! This is pretty amazing. Thank you for proposing this - just finished first pass review and while I left comments all over the place I hope none of that gives the feeling that this isn't amazing, and that we won't merge SPM support, it is something this repository obviously needs. So again, thank you
But then of course there are all the comments with some specific questions, some pings out to a firebase-ios-sdk maintainer (hi Paul 👋 ) that I collaborate with on occasion, and some notes to myself regarding testing
| key: ${{ runner.os }}-ios-pods-v3-${{ hashFiles('tests/ios/Podfile.lock') }} | ||
| restore-keys: ${{ runner.os }}-ios-pods-v3 | ||
| key: ${{ runner.os }}-ios-pods-v3-${{ matrix.dep-resolution }}-${{ hashFiles('tests/ios/Podfile.lock') }} | ||
| restore-keys: ${{ runner.os }}-ios-pods-v3-${{ matrix.dep-resolution }} |
There was a problem hiding this comment.
I prefer the variable interpolations at the front and all together and the various naming / key "sauce" at the end
| restore-keys: ${{ runner.os }}-ios-pods-v3-${{ matrix.dep-resolution }} | |
| restore-keys: ${{ runner.os }}-${{ matrix.dep-resolution }}-ios-pods-v3 |
| #import <Firebase/Firebase.h> | ||
| #else | ||
| @import FirebaseCore; | ||
| @import FirebaseAnalytics; |
There was a problem hiding this comment.
Taking note of your comment here on the FirebasePerformance symbols missing at link time if ad ids are disabled does this FirebaseAnalytics import still work here in the FirebaseAnalyticsCore dependency case? #8933 (comment)
| Pod::UI.puts "#{s.name}: Not installing FirebaseAnalytics/IdentitySupport Pod, no IDFA will be collected." | ||
| # Analytics has conditional dependencies that vary between SPM and CocoaPods. | ||
| # SPM: use FirebaseAnalyticsWithoutAdIdSupport when $RNFirebaseAnalyticsWithoutAdIdSupport = true | ||
| # to avoid GoogleAppMeasurement APM symbols that require FirebaseRemoteConfig (linker error). |
There was a problem hiding this comment.
was it FirebasePerformance symbols or FirebaseRemoteConfig symbols missing at link time? The comment on the PR and the comment here in code and slightly lower in code are inconsistent 🤔
| s.frameworks = 'AdSupport' | ||
| end | ||
|
|
||
| # GoogleAdsOnDeviceConversion (CocoaPods only, not available in firebase-ios-sdk SPM) |
There was a problem hiding this comment.
Is there some documentation on how to access on-device conversion in the SPM case? This is surprising to me as I thought on-device conversion was one of the newer features of firebase-ios-sdk which creates the expectation in me that it should be available there somehow ? Perhaps just built in to core SPM dep I'm not sure
| #if __has_include(<Firebase/Firebase.h>) | ||
| #import <Firebase/Firebase.h> | ||
| #import <FirebaseAppCheck/FIRAppCheck.h> | ||
| #elif __has_include(<FirebaseAppCheck/FirebaseAppCheck.h>) | ||
| #import <FirebaseAppCheck/FirebaseAppCheck.h> |
There was a problem hiding this comment.
<Firebase/Firebase.h> will always be found in the CocoaPods case won't it? It's the most fundamental header if I understand correctly. Does it transitively include <FirebaseAppCheck/FIRAppCheck.h> such that the explicit import is no longer required then (or maybe was never required?). Surprising this compiles as I think that preprocessor branch is likely the only __has_include branch that will ever be taken, and it makes me think the #elif __has_include(<FirebaseAppCheck/FirebaseAppCheck.h>) branch may not even be needed, I can't see how <Firebase/Firebase.h> won't be found
| Add this line at the top of your Podfile (before any `target` block): | ||
|
|
||
| ```ruby | ||
| # Podfile | ||
| $RNFirebaseDisableSPM = true | ||
| ``` | ||
|
|
||
| This forces all RNFB modules to use traditional `s.dependency` CocoaPods declarations. | ||
| You can use either static or dynamic linkage with this option. |
There was a problem hiding this comment.
Similarly - if there is something Expo people need to do to add a Podfile directive we'll need to explain it here or we'll have a lot of issues opened in the repo later about it
|
|
||
| # Read Firebase SPM URL from app package.json (single source of truth) | ||
| $firebase_spm_url ||= begin | ||
| app_package_path = File.join(__dir__, 'package.json') |
There was a problem hiding this comment.
maintainer note to check this as recommended relative/package-local file resolution style (as well as the includes of this file from other packages) for all cases - including monorepos with possible package hoists, pnpm etc
| # Set `$RNFirebaseDisableSPM = true` in your Podfile to force CocoaPods-only | ||
| # dependency resolution. This is required when using `use_frameworks! :linkage => :static` | ||
| # because static frameworks cause each pod to embed Firebase SPM products, | ||
| # resulting in duplicate symbol linker errors. |
There was a problem hiding this comment.
This in This is required... is perhaps ambiguous for something this important
| # Set `$RNFirebaseDisableSPM = true` in your Podfile to force CocoaPods-only | |
| # dependency resolution. This is required when using `use_frameworks! :linkage => :static` | |
| # because static frameworks cause each pod to embed Firebase SPM products, | |
| # resulting in duplicate symbol linker errors. | |
| # Set `$RNFirebaseDisableSPM = true` in your Podfile to force CocoaPods-only | |
| # dependency resolution. You must disable SPM when using `use_frameworks! :linkage => :static` | |
| # because static frameworks cause each pod to embed Firebase SPM products, | |
| # resulting in duplicate symbol linker errors. |
But a further question - the implication is that static frameworks and SPM are simply incompatible (because of duplicate symbols). Is that true? Is there a definitive upstream statement that firebase-ios-sdk, when used via SPM, must always be dynamic frameworks? (vs the Cocoapods path where both are/were supported?)
If there is a definitive statement about this requirement I'd prefer to just state it as a requirement without elaboration and reference the definitive statement via URL for context. If there is no definitive statement then perhaps it could be added in docs upstream via request
| @@ -1,4 +1,5 @@ | |||
| require 'json' | |||
| require '../app/firebase_spm' | |||
There was a problem hiding this comment.
maintainer note (mirrored in similar note in firebase_spm.rb itself) to verify this is okay in monorepo (possibly hoisted)/pnpm/etc cases - there is likely a known-good inter-package local file resolution mechanism and it is likely not a simple ../<packagename>/ path unfortunately
| @@ -0,0 +1,1135 @@ | |||
| # Documentacion Completa: Soporte Dual SPM + CocoaPods para Firebase en React Native | |||
There was a problem hiding this comment.
Creo que este archivo markdown sea lo mismo como docs/ios_spm.md pero en español y menos obvio para desarrolladores - pienso que debe borrar este ?
Summary
firebase_dependency()helper (packages/app/firebase_spm.rb)spm_dependency). For older versions or when explicitly disabled ($RNFirebaseDisableSPM = true), CocoaPods is used as fallback.FirebaseCoreInternal,FirebaseSharedSwift) when using CocoaPodsChanges
packages/app/firebase_spm.rb— New helper withfirebase_dependency()function that auto-detects SPM supportpackages/app/package.json— AddedfirebaseSpmUrlfield as single source of truthfirebase_dependency()instead of directs.dependency#if __has_includeguards for dual SPM/CocoaPods importsdep-resolution: ['spm', 'cocoapods']dimension (4 job combinations)firebase_spm.rblogicdocs/ios-spm.mdwith architecture, integration guides, and troubleshootingHow it works
Decision logic:
spm_dependency()defined? (RN >= 0.75 injects it) → YES → use SPM$RNFirebaseDisableSPMset in Podfile? → YES → force CocoaPodsUser-facing configuration
SPM mode (default for RN >= 0.75):
CocoaPods mode (legacy/opt-out):
Xcode 26 workaround (both modes):
Test plan
packages/app/__tests__/firebase_spm_test.rb)$RNFirebaseDisableSPM = truecorrectly forces CocoaPods