Skip to content

feat(ios): add SPM dependency resolution support alongside CocoaPods#8933

Open
jsnavarroc wants to merge 58 commits into
invertase:mainfrom
jsnavarroc:main
Open

feat(ios): add SPM dependency resolution support alongside CocoaPods#8933
jsnavarroc wants to merge 58 commits into
invertase:mainfrom
jsnavarroc:main

Conversation

@jsnavarroc

@jsnavarroc jsnavarroc commented Mar 17, 2026

Copy link
Copy Markdown

Summary

  • Add dual SPM/CocoaPods dependency resolution for Firebase iOS SDK via a centralized firebase_dependency() helper (packages/app/firebase_spm.rb)
  • 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 as fallback.
  • Solves Xcode 26 compilation errors caused by explicit modules not finding Firebase internal modules (FirebaseCoreInternal, FirebaseSharedSwift) when using CocoaPods

Changes

  • packages/app/firebase_spm.rb — New helper with firebase_dependency() function that auto-detects SPM support
  • packages/app/package.json — Added firebaseSpmUrl field as single source of truth
  • 16 podspecs — Updated to use firebase_dependency() instead of direct s.dependency
  • 43 native iOS files — Added #if __has_include guards for dual SPM/CocoaPods imports
  • CI matrix — Extended E2E workflow with dep-resolution: ['spm', 'cocoapods'] dimension (4 job combinations)
  • Unit tests — Added Ruby Minitest suite for firebase_spm.rb logic
  • Documentation — Added docs/ios-spm.md with architecture, integration guides, and troubleshooting

How it works

# Each podspec calls:
firebase_dependency(s, version, ['FirebaseAuth'], 'Firebase/Auth')

# Internally decides:
# - SPM path:      spm_dependency(spec, url: ..., products: ['FirebaseAuth'])
# - CocoaPods path: spec.dependency 'Firebase/Auth', version

Decision logic:

  1. Is spm_dependency() defined? (RN >= 0.75 injects it) → YES → use SPM
  2. Is $RNFirebaseDisableSPM set in Podfile? → YES → force CocoaPods
  3. Neither available → fall back to CocoaPods

User-facing configuration

SPM mode (default for RN >= 0.75):

# ios/Podfile
linkage = 'dynamic'
use_frameworks! :linkage => linkage.to_sym

CocoaPods mode (legacy/opt-out):

# ios/Podfile
$RNFirebaseDisableSPM = true
linkage = 'static'
use_frameworks! :linkage => linkage.to_sym

Xcode 26 workaround (both modes):

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_ENABLE_EXPLICIT_MODULES'] = 'NO'
    end
  end
end

Test plan

  • Ruby unit tests pass (packages/app/__tests__/firebase_spm_test.rb)
  • CI: Code Quality Checks pass (clang-format, linting)
  • CI: E2E iOS debug + SPM passes
  • CI: E2E iOS debug + CocoaPods passes
  • CI: E2E iOS release + SPM passes
  • CI: E2E iOS release + CocoaPods passes
  • $RNFirebaseDisableSPM = true correctly forces CocoaPods
  • Log messages indicate which resolution mode is active

related issue: #9010


Note

Medium Risk
Medium risk because it changes iOS dependency/linkage and native import behavior across many modules, which can break builds in certain Xcode/React Native configurations despite added CI coverage.

Overview
Introduces a centralized firebase_dependency() helper (packages/app/firebase_spm.rb) that switches RNFirebase iOS Firebase dependencies between SPM (RN >= 0.75 by default) and CocoaPods (fallback or $RNFirebaseDisableSPM), with the SPM repo URL sourced from packages/app/package.json.

Updates all affected RNFirebase module podspecs to use the helper (including special-casing Analytics’ SPM vs CocoaPods behavior), adjusts numerous iOS sources to compile under both header layouts via __has_include/module imports, and extends Crashlytics symbol upload scripting to find the SPM checkout.

Expands iOS E2E CI to run a matrix over spm/cocoapods × debug/release (including mode-specific Podfile edits and cache keys), adds Ruby unit tests for the helper and runs them in the Jest workflow, and adds new SPM documentation plus a local SPM verification screen.

Reviewed by Cursor Bugbot for commit 496d97c. Bugbot is set up for automated code reviews on this repo. Configure here.

@CLAassistant

CLAassistant commented Mar 17, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ jsnavarroc
✅ russellwheatley
❌ Johan Navarro


Johan Navarro seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@vercel

vercel Bot commented Mar 17, 2026

Copy link
Copy Markdown

@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
jsnavarroc and others added 2 commits March 18, 2026 11:55
…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
@jsnavarroc

jsnavarroc commented Mar 18, 2026

Copy link
Copy Markdown
Author

Additional fix included: FirebaseAnalyticsCore when SPM + $RNFirebaseAnalyticsWithoutAdIdSupport = true

While integrating this PR in a tvOS app (React Native tvOS 0.77, Xcode 26), we encountered a linker error that is only triggered when all three conditions are true simultaneously:

  1. SPM dependency resolution is active (this PR)
  2. $RNFirebaseAnalyticsWithoutAdIdSupport = true in Podfile
  3. FirebasePerformance is not installed

Linker error

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_APMETaskManager"
  "_OBJC_CLASS_$_APMMeasurement"

Root cause

The FirebaseAnalytics SPM product resolves to GoogleAppMeasurement, which contains cross-references to APMETaskManager and APMMeasurement (Firebase Performance classes). When FirebasePerformance is not in the project, those symbols are missing at link time.

The fix: when SPM + WithoutAdIdSupport = true, use FirebaseAnalyticsCore instead — it resolves to GoogleAppMeasurementCore (no IDFA, no APM dependencies).

Fix applied in RNFBAnalytics.podspec

if defined?(spm_dependency) && !defined?($RNFirebaseDisableSPM) &&
   defined?($RNFirebaseAnalyticsWithoutAdIdSupport) && $RNFirebaseAnalyticsWithoutAdIdSupport
  firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalyticsCore'], 'FirebaseAnalytics/Core')
else
  firebase_dependency(s, firebase_sdk_version, ['FirebaseAnalytics'], 'FirebaseAnalytics/Core')
end

This change is fully backwards compatible — all existing paths (CocoaPods, SPM without the flag, SPM with IDFA) are unchanged.

This fix is already included in the current head of this PR. Happy to extract it into a separate PR if preferred.


Context: why SPM matters for React Native in 2026

This article is highly relevant for the community — it covers the broader roadmap and real-world pain points of migrating React Native apps to SPM, including the Xcode 26 requirement and Firebase-specific issues:

📖 React Native — Roadmap to Swift Package Manager 2026

@jsnavarroc

Copy link
Copy Markdown
Author

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 mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread .github/workflows/tests_e2e_ios.yml Outdated
Comment thread packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.mm
Comment thread packages/analytics/RNFBAnalytics.podspec Outdated
Comment thread packages/analytics/RNFBAnalytics.podspec Outdated
Comment thread packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.mm
Comment thread packages/app/README.md Outdated
Comment thread packages/app/firebase_spm.rb Outdated
Comment thread packages/app/firebase_spm.rb
Comment thread packages/analytics/RNFBAnalytics.podspec Outdated
Comment thread DOCUMENTACION_SPM_IMPLEMENTACION.md Outdated
@mikehardy mikehardy added Workflow: Waiting for User Response Blocked waiting for user response. Workflow: Needs Review Pending feedback or review from a maintainer. and removed Needs Attention labels Mar 19, 2026
@mikehardy

Copy link
Copy Markdown
Collaborator

Hey @jsnavarroc 👋 just a gentle ping on this - are you interested in / have time to work on moving this forward? Happy to collaborate, and it's a pretty important feature so I'm very interested personally. Curious for your thoughts

@mikehardy mikehardy added Workflow: Needs Second Review Waiting on a second review before merge Workflow: Waiting for User Response Blocked waiting for user response. Workflow: Needs Review Pending feedback or review from a maintainer. and removed Workflow: Waiting for User Response Blocked waiting for user response. Workflow: Needs Review Pending feedback or review from a maintainer. Workflow: Needs Second Review Waiting on a second review before merge labels Mar 31, 2026
@jsnavarroc

jsnavarroc commented Apr 22, 2026

Copy link
Copy Markdown
Author

Hey @mikehardy! Yes, absolutely, I'm actively working on this and just pushed a new commit addressing all the review comments. Sorry for the delay.

Changes in the latest commit

  1. Cache key order: Reordered per your suggestion (variable interpolations first)
  2. Analytics podspec comment inconsistency: Fixed, it's FirebasePerformance (APM symbols), not RemoteConfig
  3. GoogleAdsOnDeviceConversion: Added clear documentation about SPM incompatibility + runtime warning when user tries to enable it in SPM mode
  4. AppCheckModule.m spacing: Reverted all unrelated spacing changes, kept only the #if __has_include import block
  5. .npmignore: Added clarifying comment about the !ios/RNFBApp/RNFBVersion.m inclusion and the lack of a files array in package.json
  6. RNFBML.podspec: Removed old commented-out s.dependency and raise lines
  7. firebase_spm.rb: Improved static linkage comment with upstream context, added monorepo/pnpm path resolution note
  8. README: Added Expo section, reframed dynamic/static in terms of pre-built RN core, added monorepo/pnpm notes
  9. ios_config.sh: Replaced find with known deterministic path for SPM upload-symbols
  10. DOCUMENTACION_SPM_IMPLEMENTACION.md: Deleted (was a Spanish duplicate of docs/ios-spm.md)

Ask

If you or anyone on the team has bandwidth to test this branch on different devices/platforms (iOS, tvOS, macCatalyst) and with different configurations (SPM vs CocoaPods, dynamic vs static), that would be incredibly valuable. I've verified it on iOS 26 simulator and tvOS 26 (Apple TV) but broader coverage would help catch edge cases before merge.

I'll reply to each individual review comment below with details.

FirebaseInstallations is a transitive dependency of FirebaseCore.
With SPM dynamic linking, transitive frameworks are not embedded
automatically — they must be declared explicitly as SPM products.

Without this, dyld crashes at launch with:
'symbol not found in flat namespace _FIRInstallationIDDidChangeNotification'

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 496d97c. Configure here.

Comment thread packages/analytics/RNFBAnalytics.podspec
@paulb777

Copy link
Copy Markdown
Contributor

Great to see the progress in this PR! I'm cc'ing @ncooke3, who is now a better contact than me for the firebase-ios-sdk questions in the comments.

@jsnavarroc

jsnavarroc commented Apr 23, 2026

Copy link
Copy Markdown
Author

Heads up: tvOS + Xcode 26 hybrid SPM/CocoaPods edge case worth documenting

While integrating this PR in a tvOS React Native project (hybrid CocoaPods + SPM resolution, Xcode 26, RN 0.77 with react-native-tvos), I ran into a subtle TestFlight-only crash that took considerable time to diagnose. Sharing here so maintainers can decide if it's worth adding a warning or a note in the docs.

Symptom

  • App launches fine on tvOS simulator ✅
  • App launches fine when installed directly from Xcode onto a real Apple TV (WiFi pairing or USB-C) ✅
  • App crashes immediately at launch (<200ms, pre-main()) when the same archive is uploaded via Fastlane and installed from TestFlight ❌

No formal crash log is generated, ReportCrash logs Failed to create bundle record because the process dies too early. The only signal in the system log (from Console.app connected to the device) is:

PineBoard  [app<com.example.myapp>:738] Now flagged as pending exit for reason: launch failed
PineBoard  [app<com.example.myapp>:738] Process exited:
  <RBSProcessExitContext| specific, status:<RBSProcessExitStatus| domain:dyld(6) code:0>>.

Root cause

The combination that triggers it:

  1. Firebase resolved via SPM (source packages, statically linked into the main app binary)
  2. @react-native-firebase/* frameworks compiled as dynamic frameworks with -undefined dynamic_lookup (necessary in hybrid setup to avoid duplicate Firebase class registrations, as documented in other threads)
  3. Release configuration with default stripping settings (DEAD_CODE_STRIPPING=YES, STRIP_SWIFT_SYMBOLS=YES, etc.)

The Release + TestFlight pipeline strips Firebase symbols from the main binary's dynamic symbol table because the static analyzer sees them as "unused", nothing in the binary statically references them, RNFB frameworks resolve them at runtime via flat-namespace dynamic lookup. When dyld tries to resolve them on device after TestFlight processing, they're gone. Local Xcode installs don't exhibit this because development builds don't apply the same aggressive stripping.

Diagnostic evidence

Before fix (binary that crashed in TestFlight):

  • Main app binary size: 3.3 MB
  • nm -g <binary> | grep "FIR" | wc -l0
  • nm -g <binary> | grep "InstallationIDDidChange" → nothing

After fix (binary that works in TestFlight):

  • Binary size: 6.0 MB (+80%)
  • nm -g <binary> | grep "FIR" | wc -l692
  • nm -g <binary> | grep "InstallationIDDidChange"S _FIRInstallationIDDidChangeNotification

Workaround

Anti-stripping settings on the main app target's Release configuration + exporting static symbols to the dynamic symbol table, added via post_install hook in the integrator's Podfile:

main_target.build_configurations.each do |cfg|
  next unless cfg.name == 'Release'
  cfg.build_settings['STRIP_SWIFT_SYMBOLS'] = 'NO'
  cfg.build_settings['DEAD_CODE_STRIPPING'] = 'NO'
  cfg.build_settings['STRIP_INSTALLED_PRODUCT'] = 'NO'
  cfg.build_settings['COPY_PHASE_STRIP'] = 'NO'
  cfg.build_settings['DEPLOYMENT_POSTPROCESSING'] = 'NO'
  ldflags = Array(cfg.build_settings['OTHER_LDFLAGS'] || ['$(inherited)'])
  ldflags << '-Wl,-export_dynamic' unless ldflags.include?('-Wl,-export_dynamic')
  cfg.build_settings['OTHER_LDFLAGS'] = ldflags
end

This belongs in the integrator's Podfile, not in the library, because it's specific to the hybrid linkage strategy and tvOS, and would be intrusive for iOS projects that don't need it (binary size doubles, Release optimizations disabled).

Suggestion

Since this is very hard to diagnose without the crashlog (which doesn't get generated) and only manifests after TestFlight upload (long iteration cycle), it might be worth considering one of:

  1. A non-blocking warning during pod install when the risky combination is detected (platform: tvos + SPM active + main target missing anti-stripping in Release), pointing to the fix.
  2. A troubleshooting section in the README / docs/ios-spm.md documenting the symptom and the Podfile snippet.

Happy to open a separate PR with either approach if maintainers think it's worth it, fully understand if you'd rather keep this PR focused and tackle it separately later. Flagging it here so it doesn't get lost.

Context: reproducible on my setup (Apple TV 4K, tvOS 26, Xcode 26.3, use_frameworks! :linkage => :static, Firebase iOS SDK 12.x via SPM). Others on the same configuration may hit it too.

cc @ncooke3 (per @paulb777's earlier comment pointing to you for firebase-ios-sdk questions), sharing in case this stripping interaction is relevant on the Firebase SDK side.

@ncooke3

ncooke3 commented Apr 23, 2026

Copy link
Copy Markdown

Hi @jsnavarroc, nice testing & investigation! If you didn't consider it, adding the -ObjC linker flag could possibly be an additional path that may (or may not) work, though it's not as precise as the workaround you documented. I'm unsure if it handles the risk of duplicate symbols.

In any case, I'll defer to @mikehardy here, but your suggestions sound reasonable.

…ailures

- Gate the test app's precompiled FirebaseFirestore CocoaPods pod behind
  rnfirebase_spm_disabled? so it no longer collides with the SPM-resolved
  copies of FirebaseCore/FirebaseCoreExtension/FirebaseCoreInternal/
  FirebaseSharedSwift during Xcode Archive builds ("Multiple commands
  produce ... FirebaseCore.framework").
- Link FirebaseCore directly onto the app's own target when SPM is active,
  restoring the "free" linkage CocoaPods always gave apps whose native code
  calls FIRApp/FIROptions APIs directly.
- Fix the SPM embed script to also search Xcode Archive's
  UninstalledProducts folder, not just PackageFrameworks -- real
  `xcodebuild archive` builds were silently embedding zero Firebase SPM
  frameworks and would crash at launch.
…bled

rnfirebase_add_spm_core_to_app_target writes its FirebaseCore package
product dependency into the app's own Xcode project (testing.xcodeproj),
a different project than the one React Native's own SPM integration
manages and cleans up on every install (Pods.xcodeproj). Once that
dependency had been committed into the app's .pbxproj from a prior
SPM-mode `pod install`, switching to `$RNFirebaseDisableSPM = true` and
reinstalling left the stale SPM wiring in place forever: the app target
ended up linked against both Xcode's SPM-resolved firebase-ios-sdk
package graph and the freshly CocoaPods-resolved Firebase/CoreOnly pod
at the same time, and the two copies of Firebase's module graph
collided -- "redefinition of module 'Firebase'" at compile time, and
duplicate App-Intents-metadata build commands at Archive time. This
broke the previously-green `iOS (debug, cocoapods)` and
`iOS Release Archive (cocoapods)` CI jobs.

Add rnfirebase_remove_spm_core_from_app_target as the counterpart to
rnfirebase_add_spm_core_to_app_target: removes the stale product
dependency (and, once nothing else references it, the package
reference itself) whenever SPM is inactive. Also adds Minitest coverage
for both functions -- rnfirebase_add_spm_core_to_app_target previously
had none, which is how this regression went unnoticed.
…y xcframeworks

Xcode's Archive action stages a binary Swift Package's .signature provenance
file into every target's build directory that transitively depends on it, then
fails when copying duplicates into <Archive>.xcarchive/Signatures/ with
"...xcframework-ios.signature couldn't be copied ... item with the same name
already exists". This previously only covered GoogleAppMeasurement*, but the
same collision reproduces for any binary xcframework in the resolved graph
(GoogleAdsOnDeviceConversion, FirebaseAnalytics, FirebaseFirestoreInternal,
absl, grpc, grpcpp, openssl_grpc) -- none of which show up as a reference in
our own podspecs/pbxprojs since GoogleAppMeasurement's own upstream
Package.swift unconditionally pulls in GoogleAdsOnDeviceConversion regardless
of our optional spm_dependency flag. Enumerate every known binary artifact
name from the resolved package graph and clean up all of them in one pass.
# Conflicts:
#	okf-bundle/index.md
#	packages/remote-config/ios/RNFBConfig/RNFBConfigModule.mm
Comment thread packages/app/firebase_spm.rb Outdated
Comment thread tests/ios/Podfile

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks pretty good in general - huge but given how profound the change is and how intractable some of the difficulties are it all holds together

some observations but no problems with the general direction / decisions just ergonomics / DX type things

Comment thread docs/ios-spm.mdx
Comment thread docs/ios-spm.mdx Outdated
Comment thread docs/ios-spm.mdx Outdated
Comment thread tests/ios/Podfile Outdated
Comment thread tests/ios/Podfile Outdated
Comment thread tests/ios/Podfile Outdated
Address mikehardy's PR review feedback: `-ObjC` and
`SWIFT_ENABLE_EXPLICIT_MODULES = 'NO'` were only wired up manually in the
test app's Podfile, requiring every SPM-mode integrator to paste the same
post_install snippet themselves. Move both into a new
`rnfirebase_apply_spm_build_settings` helper, applied automatically via the
existing CocoaPods post_install hook alongside the other SPM fix-ups, with
a documented Podfile fallback only for the case where the hook fails.
…ative's own C++ pods

The `-fmodules -fcxx-modules` app-target flag added to unblock Firebase's
`@import` fallback in Archive builds was never actually needed: linking
FirebaseCore into the app target already gives it header search paths for
every product in the resolved Firebase SPM package graph, so
`__has_include` succeeds and the `@import` fallback is dead code there.
Forcing C++ modules on top of that instead broke Xcode's module build for
React Native's own C++ pods (glog, then cxxreact/folly). Verified with a
real `xcodebuild archive` and a Debug simulator build.
…tic linkage

firebase-ios-sdk's SPM package only ships dynamic library products, so
use_frameworks! :linkage => :static causes every RNFB pod resolving Firebase
via SPM to embed its own copy of the same frameworks, producing confusing
duplicate-symbol linker errors at build time with no pointer back to the
Podfile setting that caused it.

Detect the combination during pod install and raise Pod::Informative with
the offending target name(s) and both fixes (dynamic linkage or
$RNFirebaseDisableSPM = true), instead of letting it surface later at
Xcode's build/link step.
Move "Expo projects" up to immediately follow "Requirements and defaults",
and relabel the two manual-Podfile-editing sections "Use SPM (React Native
CLI)" / "Use CocoaPods (React Native CLI)" with a note that Expo apps should
use expo-build-properties instead, since Expo regenerates ios/Podfile from
config plugins on every prebuild.
…e SPM

The previous wording ("does not disable SPM") read as ambiguous/possibly
contradictory. State the actual rule directly: the variable must be exactly
true to opt out, any other value (including false, nil, or unset) uses SPM.
Prettier flagged a stray leading double-space on the continuation line
introduced by the SWIFT_ENABLE_EXPLICIT_MODULES wording fix, failing the
Spelling & Grammar CI check.
… SPM pod install

`rnfirebase_fail_if_spm_static_linkage!` checked `AggregateTarget#build_as_static?`,
but CocoaPods always builds the aggregate "Pods-<target>" umbrella target as
static_framework/static_library regardless of `use_frameworks!`'s `:linkage`
(see `Installer::Analyzer#generate_aggregate_target`) -- so this was true
unconditionally and broke `pod install` for every SPM job (debug, release,
archive), dynamic linkage included. Check the aggregate target's own
`target_definition.build_type` instead, which correctly reflects the
Podfile's requested linkage. Verified against a real `pod install`: SPM +
dynamic now succeeds, SPM + static still fails fast as intended.

Also updates the Minitest mocks (`MockAggregateTarget`) to model
`target_definition.build_type.static?` instead of a directly-settable
`build_as_static?` flag, since the old shape didn't match the real
CocoaPods object and couldn't have caught this regression, and adds a
dedicated regression test for it.
…plated

`yarn lint:spellcheck` flagged three unknown words in docs/ios-spm.mdx:
FIRLibrary and FIRComponent (bare mentions of Firebase symbols, unlike every
other symbol name in this doc which is backtick-wrapped) and templated (not
covered by the existing env-templated dictionary entry, which is matched as
an exact token). Wrap the two symbol names in backticks to match the doc's
existing style, and reword templated Podfiles to template-based Podfiles to
avoid adding a one-off dictionary entry.
@mikehardy
mikehardy self-requested a review July 21, 2026 15:24

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave it a scan last night and all the stuff I noticed appears to be fixed

Please DO NOT MERGE until v26 is out, I want to make sure (as described in comments above) that we release the current batch of breaking changes before this goes out, since this is such a large change

@mikehardy mikehardy added blocked: do-not-merge Do not merge this issue without approval by the person who labelled this issue as Do Not Merge and removed Workflow: Waiting for User Response Blocked waiting for user response. Workflow: Needs Review Pending feedback or review from a maintainer. Needs Attention labels Jul 22, 2026
… hook guards

rnfirebase_hook_cocoapods_post_install!'s early guard clauses previously
returned silently if Pod::Installer or its post-install hook method were
missing, unlike every other failure path in firebase_spm.rb. Add Pod::UI.warn
to those guards, and add rnfirebase_verify_spm_embed_phase_applied! -- an
unrescued Pod::Informative check confirming the SPM embed phase actually
landed on every target that needs it, so a silent embed failure aborts
`pod install` instead of surfacing later as a missing-library dyld crash at
app launch.
Replace the three bare globals ($rnfirebase_spm_active, $rnfirebase_spm_version,
$firebase_spm_url) threading state between podspec evaluation and post_install
with a single RNFirebaseSPM module (activate!/active?/version/url/reset!).
active? now self-checks internal consistency, raising Pod::Informative if the
active flag is ever true without a recorded version, instead of every
downstream helper trusting a bare boolean blindly. Test suite now uses
RNFirebaseSPM.reset! for state isolation instead of the previous one-way
defined?-based workaround.
…ross podspecs

Plain `require '../app/firebase_spm'` resolves relative to the process's
current working directory, not the requiring file's own location -- it only
works today because CocoaPods happens to chdir into each podspec's directory
before eval'ing it. Switch all 16 podspecs (plus the two firebase_json
requires in RNFBApp/RNFBML) to require_relative, which resolves correctly
regardless of Dir.pwd or monorepo hoisting layout. Update the now-stale
hoisting-risk comment in firebase_spm.rb accordingly.
…cript

The "Configure Dependency Resolution Mode" step was duplicated verbatim
across the ios and ios-release-archive jobs in tests_e2e_ios.yml, and its
plain sed patches silently no-op on a Podfile wording change instead of
failing loudly. Extract both real patches (linkage swap,
$RNFirebaseDisableSPM prepend) into
.github/workflows/scripts/configure-ios-dep-resolution.sh, with a grep
assertion after each patch, and drop the third sed line
(/SWIFT_ENABLE_EXPLICIT_MODULES/d) entirely -- that setting moved into
firebase_spm.rb's Ruby post_install hook and is dead code in the Podfile
today.
…e mocks

Add firebase_spm_shape_test.rb: an opt-in Minitest suite that asserts, against
the real xcodeproj/cocoapods gems, that every class/method firebase_spm_test.rb
mocks still has the shape those mocks assume. Skips cleanly when the gems
aren't installed (e.g. the dependency-free "Test Firebase SPM Helper" CI
step), and runs for real in tests_e2e_other.yml's macOS job right after its
existing `gem update cocoapods xcodeproj` step -- no new job, no new gem
installs. This is the fast, seconds-long check that would have caught the
build_as_static?-is-always-true false positive that previously shipped past
the mocked suite and only surfaced via a real pod install.

Also retrofit real-class/method citation comments onto the four Mock* classes
in firebase_spm_test.rb that were missing them (MockRootObject, MockBuildConfig,
MockAggregateTarget, MockInstaller), matching the convention already used on
MockBuildType and the Xcodeproj module stand-ins.

Verified against the real gems (system Ruby 2.6, cocoapods 1.16.2/xcodeproj
1.27.0): 11 runs, 46 assertions, 0 failures.
…pe-check gaps

Point the "Release launch dyld failure" citations in verify-ios-release-archive.sh
and tests_e2e_ios.yml at the actual "Runtime framework embedding" section (the
heading was renamed without updating callers). Document firebase_spm_shape_test.rb
in the app tests README, add the Crashlytics dSYM-under-SPM behavior and the
missing archive-signature/FIRApp-link/stale-SPM-core troubleshooting entries to
the consumer and OKF docs, and list configure-ios-dep-resolution.sh in the iOS CI
helper scripts table.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blocked: do-not-merge Do not merge this issue without approval by the person who labelled this issue as Do Not Merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants