-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat(ios): add SPM dependency resolution support alongside CocoaPods #8933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
05eea19
00f59b2
27dde8d
7b431cb
496d97c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| require 'json' | ||
| require '../app/firebase_spm' | ||
| package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) | ||
| appPackage = JSON.parse(File.read(File.join('..', 'app', 'package.json'))) | ||
|
|
||
|
|
@@ -45,27 +46,56 @@ Pod::Spec.new do |s| | |
| end | ||
|
|
||
| # Firebase dependencies | ||
| s.dependency 'FirebaseAnalytics/Core', firebase_sdk_version | ||
| if defined?($RNFirebaseAnalyticsWithoutAdIdSupport) && ($RNFirebaseAnalyticsWithoutAdIdSupport == true) | ||
| 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 FirebaseAnalyticsCore when $RNFirebaseAnalyticsWithoutAdIdSupport = true | ||
| # to avoid GoogleAppMeasurement APM symbols (APMETaskManager, APMMeasurement) | ||
| # that require FirebasePerformance at link time. | ||
| # CocoaPods: IdentitySupport is a separate subspec controlled by $RNFirebaseAnalyticsWithoutAdIdSupport. | ||
| if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM) && | ||
| defined?($RNFirebaseAnalyticsWithoutAdIdSupport) && $RNFirebaseAnalyticsWithoutAdIdSupport | ||
| # FirebaseAnalyticsCore uses GoogleAppMeasurementCore (no IDFA, no APM objects). | ||
| # FirebaseAnalytics uses GoogleAppMeasurement which has APMETaskManager/APMMeasurement | ||
| # cross-references that cause linker errors when FirebasePerformance is not linked. | ||
| Pod::UI.puts "#{s.name}: Using FirebaseAnalyticsCore SPM product (no IDFA, uses GoogleAppMeasurementCore)." | ||
| firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalyticsCore'], 'FirebaseAnalytics/Core') | ||
| else | ||
| if !defined?($RNFirebaseAnalyticsWithoutAdIdSupport) | ||
| Pod::UI.puts "#{s.name}: Using FirebaseAnalytics/IdentitySupport with Ad Ids. May require App Tracking Transparency. Not allowed for Kids apps." | ||
| Pod::UI.puts "#{s.name}: You may set variable `$RNFirebaseAnalyticsWithoutAdIdSupport=true` in Podfile to use analytics without ad ids." | ||
| end | ||
| s.dependency 'FirebaseAnalytics/IdentitySupport', firebase_sdk_version | ||
| firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalytics'], 'FirebaseAnalytics/Core') | ||
| end | ||
|
|
||
| # Special pod for on-device conversion | ||
| if defined?($RNFirebaseAnalyticsEnableAdSupport) && ($RNFirebaseAnalyticsEnableAdSupport == true) | ||
| Pod::UI.puts "#{s.name}: Adding Apple AdSupport.framework dependency for optional analytics features" | ||
| s.frameworks = 'AdSupport' | ||
| unless defined?(spm_dependency) | ||
| # CocoaPods-only: conditional IdentitySupport subspec | ||
| if defined?($RNFirebaseAnalyticsWithoutAdIdSupport) && ($RNFirebaseAnalyticsWithoutAdIdSupport == true) | ||
| Pod::UI.puts "#{s.name}: Not installing FirebaseAnalytics/IdentitySupport Pod, no IDFA will be collected." | ||
| else | ||
| if !defined?($RNFirebaseAnalyticsWithoutAdIdSupport) | ||
| Pod::UI.puts "#{s.name}: Using FirebaseAnalytics/IdentitySupport with Ad Ids. May require App Tracking Transparency. Not allowed for Kids apps." | ||
| Pod::UI.puts "#{s.name}: You may set variable `$RNFirebaseAnalyticsWithoutAdIdSupport=true` in Podfile to use analytics without ad ids." | ||
| end | ||
| s.dependency 'FirebaseAnalytics/IdentitySupport', firebase_sdk_version | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IdentitySupport skipped when SPM disabled on RN >= 0.75High Severity The Reviewed by Cursor Bugbot for commit 496d97c. Configure here. |
||
| end | ||
| end | ||
|
|
||
| # Special pod for on-device conversion | ||
| # AdSupport framework (works with both SPM and CocoaPods) | ||
| if defined?($RNFirebaseAnalyticsEnableAdSupport) && ($RNFirebaseAnalyticsEnableAdSupport == true) | ||
| Pod::UI.puts "#{s.name}: Adding Apple AdSupport.framework dependency for optional analytics features" | ||
| s.frameworks = 'AdSupport' | ||
| end | ||
|
|
||
| # GoogleAdsOnDeviceConversion (CocoaPods only) | ||
| # This is a static xcframework distributed separately from firebase-ios-sdk. | ||
| # It is NOT available as an SPM product in the firebase-ios-sdk Package.swift. | ||
| # When using SPM (dynamic linkage), this static xcframework causes duplicate | ||
| # symbol errors. Use CocoaPods mode ($RNFirebaseDisableSPM = true) if you need | ||
| # on-device conversion measurement. | ||
| # See: https://developers.google.com/google-ads/api/docs/conversions/upload-identifiers | ||
| if defined?($RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion) && ($RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion == true) | ||
| Pod::UI.puts "#{s.name}: GoogleAdsOnDeviceConversion pod added" | ||
| s.dependency 'GoogleAdsOnDeviceConversion' | ||
| if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM) | ||
| Pod::UI.warn "#{s.name}: GoogleAdsOnDeviceConversion is not available in SPM mode. " \ | ||
| "Set $RNFirebaseDisableSPM = true in your Podfile to use this feature." | ||
| else | ||
| Pod::UI.puts "#{s.name}: GoogleAdsOnDeviceConversion pod added" | ||
| s.dependency 'GoogleAdsOnDeviceConversion' | ||
| end | ||
| end | ||
|
|
||
| if defined?($RNFirebaseAsStaticFramework) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,12 @@ | |
| * | ||
| */ | ||
|
|
||
| #if __has_include(<Firebase/Firebase.h>) | ||
| #import <Firebase/Firebase.h> | ||
| #else | ||
| @import FirebaseCore; | ||
| @import FirebaseAnalytics; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taking note of your comment here on the FirebasePerformance symbols missing at link time if ad ids are disabled does this
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. Yes, |
||
| #endif | ||
| #import <React/RCTUtils.h> | ||
|
|
||
| #if __has_include(<RNFBAnalytics/RNFBAnalytics-Swift.h>) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,15 @@ | |
| * | ||
| */ | ||
|
|
||
| #if __has_include(<Firebase/Firebase.h>) | ||
| #import <Firebase/Firebase.h> | ||
| #import <FirebaseAppCheck/FIRAppCheck.h> | ||
| #elif __has_include(<FirebaseAppCheck/FirebaseAppCheck.h>) | ||
| #import <FirebaseAppCheck/FirebaseAppCheck.h> | ||
|
Comment on lines
+18
to
+21
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right that The three-branch structure is: CocoaPods umbrella, then CocoaPods individual pods, then SPM ( |
||
| #import <FirebaseCore/FirebaseCore.h> | ||
| #else | ||
| @import FirebaseCore; | ||
| @import FirebaseAppCheck; | ||
| #endif | ||
|
|
||
| #import <React/RCTUtils.h> | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,15 @@ | |
| * | ||
| */ | ||
|
|
||
| #if __has_include(<Firebase/Firebase.h>) | ||
| #import <Firebase/Firebase.h> | ||
| #import <FirebaseAppCheck/FIRAppCheck.h> | ||
| #elif __has_include(<FirebaseAppCheck/FirebaseAppCheck.h>) | ||
| #import <FirebaseAppCheck/FirebaseAppCheck.h> | ||
| #import <FirebaseCore/FirebaseCore.h> | ||
|
Comment on lines
+18
to
+22
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same questions on imports here - I note that Firebase vs FirebaseCore varies here though - I may have missed that above and that might explain the entire structure
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch on Firebase vs FirebaseCore. The difference is intentional: AppCheck needs both |
||
| #else | ||
| @import FirebaseCore; | ||
| @import FirebaseAppCheck; | ||
| #endif | ||
|
|
||
| @interface RNFBAppCheckProvider : NSObject <FIRAppCheckProvider> | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,3 +68,9 @@ android/.settings | |
| type-test.ts | ||
| scripts | ||
| __tests__ | ||
|
|
||
| # Force include generated version file (needed for linking). | ||
| # This file is in .gitignore but must be in the published npm package. | ||
| # Note: this package does not use a "files" array in package.json, | ||
| # so .npmignore is the sole mechanism controlling published file set. | ||
| !ios/RNFBApp/RNFBVersion.m | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be added to the files array in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. This package doesn't use a |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,120 @@ yarn add @react-native-firebase/app | |
|
|
||
| - [Utils](https://rnfirebase.io/app/utils) | ||
|
|
||
| ## iOS Dependency Resolution: SPM vs CocoaPods | ||
|
|
||
| Starting with React Native 0.75+, `@react-native-firebase` supports **Swift Package Manager (SPM)** for resolving Firebase iOS SDK dependencies. SPM is enabled by default when the `spm_dependency` macro is available (injected by React Native >= 0.75) — no configuration needed. | ||
|
|
||
| ### How it works | ||
|
|
||
| Each RNFB module uses `firebase_dependency()` (defined in `firebase_spm.rb`) to declare its Firebase dependencies. This helper automatically chooses between: | ||
|
|
||
| | Condition | Resolution | When to use | | ||
| |-----------|-----------|-------------| | ||
| | RN >= 0.75 and `$RNFirebaseDisableSPM` **not set** | **SPM** (default) | Dynamic linkage / pre-built RN core (`use_frameworks! :linkage => :dynamic`) | | ||
| | `$RNFirebaseDisableSPM = true` in Podfile | **CocoaPods** | Static linkage / no pre-built RN core (`use_frameworks! :linkage => :static`) | | ||
| | RN < 0.75 | **CocoaPods** (automatic fallback) | Older React Native versions without `spm_dependency` support | | ||
|
|
||
| > **Note on linkage:** firebase-ios-sdk SPM products use dynamic linkage. When using `use_frameworks! :linkage => :static`, each pod embeds its own copy of Firebase SPM products, causing duplicate symbol errors. Use CocoaPods mode (`$RNFirebaseDisableSPM = true`) with static linkage. | ||
|
|
||
| ### Configuration | ||
|
|
||
| #### Option A — SPM (default, recommended for Xcode 26+) | ||
|
|
||
| No changes needed. Just make sure your Podfile uses dynamic linkage: | ||
|
|
||
| ```ruby | ||
| # Podfile | ||
| use_frameworks! :linkage => :dynamic | ||
| ``` | ||
|
Comment on lines
+59
to
+64
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the instruction for Expo people in this case? That's our majority use case now I believe - and if it is not yet it definitely will be in the future
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added an Expo section in the latest commit with |
||
|
|
||
| > **Xcode 26 note:** If you see build errors about `FirebaseCoreInternal` or `FirebaseSharedSwift` | ||
| > module resolution, add this to your Podfile `post_install`: | ||
| > ```ruby | ||
| > config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO' | ||
| > ``` | ||
| > This does NOT disable SPM — it only tells the Swift compiler to use implicit module discovery | ||
| > (the Xcode 16 default) so transitive SPM targets are resolved automatically. | ||
|
Comment on lines
+66
to
+72
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll need an Expo section here. It will likely involve instruction to add module
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, Expo section added in latest commit. Includes |
||
|
|
||
| #### Option B — CocoaPods only | ||
|
|
||
| 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. | ||
|
Comment on lines
+76
to
+84
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered in the Expo section added in latest commit. |
||
|
|
||
| #### Expo | ||
|
|
||
| For Expo managed projects, use `expo-build-properties` to configure linkage and Podfile directives: | ||
|
|
||
| ```json | ||
| // app.json | ||
| { | ||
| "expo": { | ||
| "plugins": [ | ||
| [ | ||
| "expo-build-properties", | ||
| { | ||
| "ios": { | ||
| "useFrameworks": "dynamic" | ||
| } | ||
| } | ||
| ] | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| To disable SPM in Expo, add a Podfile directive via a config plugin or `app.json`: | ||
|
|
||
| ```json | ||
| // app.json | ||
| { | ||
| "expo": { | ||
| "plugins": [ | ||
| [ | ||
| "expo-build-properties", | ||
| { | ||
| "ios": { | ||
| "useFrameworks": "static", | ||
| "extraPods": [] | ||
| } | ||
| } | ||
| ] | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Then create a small [config plugin](https://docs.expo.dev/config-plugins/introduction/) to prepend | ||
| `$RNFirebaseDisableSPM = true` to the generated Podfile, or add it manually if you have ejected. | ||
|
|
||
| ### How to verify | ||
|
|
||
| During `pod install`, you will see messages indicating which resolution mode is active: | ||
|
|
||
| ``` | ||
| # SPM mode: | ||
| [react-native-firebase] RNFBApp: Using SPM for Firebase dependency resolution (products: FirebaseCore) | ||
| [react-native-firebase] RNFBAuth: Using SPM for Firebase dependency resolution (products: FirebaseAuth) | ||
|
|
||
| # CocoaPods mode: | ||
| [react-native-firebase] RNFBApp: SPM disabled ($RNFirebaseDisableSPM = true), using CocoaPods for Firebase dependencies | ||
| ``` | ||
|
|
||
| ### Monorepo / pnpm notes | ||
|
|
||
| The `firebase_spm.rb` helper is loaded by each RNFB podspec via `require '../app/firebase_spm'`. | ||
| This relative path assumes the standard `node_modules/@react-native-firebase/` layout. If your | ||
| package manager hoists dependencies differently (e.g., pnpm strict mode), you may need to verify | ||
| that the require path resolves correctly. The SPM URL is read from | ||
| `@react-native-firebase/app/package.json` at the location of `firebase_spm.rb`. | ||
|
|
||
| ## License | ||
|
|
||
| - See [LICENSE](/LICENSE) | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maintainer note (mirrored in similar note in
firebase_spm.rbitself) 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 unfortunatelyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same monorepo/pnpm note applies here. The
../app/path works with the standardnode_modules/@react-native-firebase/layout. Added documentation infirebase_spm.rband the README about this assumption.