Skip to content

fix(analytics, messaging): dedupe GoogleUtilities classes under SPM dynamic frameworks - #9327

Draft
russellwheatley wants to merge 1 commit into
mainfrom
feature/cprn-465-9322-spm-dynamic-frameworks-googleutilities-classes
Draft

russellwheatley wants to merge 1 commit into
mainfrom
feature/cprn-465-9322-spm-dynamic-frameworks-googleutilities-classes

Conversation

@russellwheatley

@russellwheatley russellwheatley commented Sep 18, 2026

Copy link
Copy Markdown
Member

RNFBAnalytics.podspec and RNFBMessaging.podspec only declared their direct Firebase SPM product (FirebaseAnalytics, FirebaseMessaging). Before this fix, GULNetwork, GULReachability, and GULMethodSwizzler ended up compiled privately into both RNFBAnalytics.framework and RNFBMessaging.framework instead of a single shared dynamic framework, so several GUL* classes were defined twice at runtime once both pods were loaded together.

Confirmed: the raw FirebaseAnalytics/GoogleAppMeasurement .xcframework binaries don't bake in copies of these classes (checked with nm), they only reference them as undefined externals like any source-based product.

Not fully confirmed: why Xcode's SPM integration fails to promote this specific cluster to a shared framework once Analytics is in the graph, when an equivalent pure-source pairing (Messaging + Auth, sharing the GULAppDelegateSwizzler cluster instead) promotes cleanly with zero private copies. It isn't simply "transitive vs explicit" either: firebase-ios-sdk's own Package.swift already lists GULNetwork/GULMethodSwizzler as explicit top-level products on FirebaseAnalyticsWrapper, in the same array as GULAppDelegateSwizzler, and only the latter shared correctly. Whether Analytics's target also carrying a binaryTarget dependency changes Xcode's per-target sharing heuristic is a live, untested hypothesis.

What's confirmed to fix it: declaring these same GoogleUtilities products as an explicit top-level spm_dependency directly on both podspecs makes Xcode build one shared dynamic framework per product and link both RNFBAnalytics and RNFBMessaging against it.

test-expo-ios-link.sh gained a check that walks every built .framework under the Expo SPM dynamic frameworks path, nm's its symbols, and fails if any of the affected GUL* classes are defined in more than one framework, or privately inside RNFBAnalytics/RNFBMessaging rather than a shared package product. Verified it fails without the podspec change and passes with it.

Also dropped the hardcoded CC=clang/LD=clang xcodebuild args in that script, they broke the pure-Swift SPM target (FirebaseCoreInternal) this repro pulls in once Analytics is added alongside Messaging.


Maintainer note: Fixes internal CPRN-465

@russellwheatley
russellwheatley force-pushed the feature/cprn-465-9322-spm-dynamic-frameworks-googleutilities-classes branch from e1f0368 to 0dc6c5a Compare September 18, 2026 15:33
…ynamic frameworks

Under Expo's SPM + dynamic frameworks path, RNFBAnalytics.podspec and
RNFBMessaging.podspec only declared their direct Firebase SPM products
(FirebaseAnalytics, FirebaseMessaging). GULNetwork, GULReachability, and
GULMethodSwizzler were only reachable transitively through those, and
Xcode's SPM integration doesn't reliably promote a transitively-only
reached product to a shared PackageProduct.framework. It compiled a
private copy of each straight into RNFBAnalytics.framework and
RNFBMessaging.framework instead, so GULNetwork/GULMutableDictionary/
GULNetworkURLSession/GULReachabilityChecker/GULSessionDeallocTracker/
GULSwizzler ended up defined twice at runtime once both pods were loaded.

The fix declares those same GoogleUtilities products as an explicit
top-level spm_dependency on both podspecs, the same way GULAppDelegateSwizzler
already was. That's enough for Xcode to treat them as shared and build one
dynamic framework per product instead of duplicating them.

test-expo-ios-link.sh gained a new #9322 check that walks every built
.framework, nm's its undefined/external symbols, and fails if any of the
affected GUL classes are defined in more than one framework or privately
inside RNFBAnalytics/RNFBMessaging rather than a shared PackageProduct.
Also removed the hardcoded CC=clang/LD=clang xcodebuild args, which broke
pure-Swift SPM targets pulled in by this repro (FirebaseCoreInternal).
@russellwheatley
russellwheatley force-pushed the feature/cprn-465-9322-spm-dynamic-frameworks-googleutilities-classes branch from 0dc6c5a to 9a052ab Compare September 18, 2026 15:46
@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.83%. Comparing base (695161f) to head (9a052ab).
⚠️ Report is 19 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #9327      +/-   ##
============================================
+ Coverage     69.49%   69.83%   +0.35%     
- Complexity     2129     2130       +1     
============================================
  Files           439      439              
  Lines         25472    25518      +46     
  Branches       4251     4257       +6     
============================================
+ Hits          17700    17819     +119     
+ Misses         6365     6358       -7     
+ Partials       1407     1341      -66     
Flag Coverage Δ
android-native 65.92% <ø> (+0.01%) ⬆️
e2e-ts-android 54.47% <ø> (+0.04%) ⬆️
e2e-ts-ios 53.89% <ø> (-0.02%) ⬇️
e2e-ts-macos 49.95% <ø> (-0.01%) ⬇️
ios-ruby 100.00% <ø> (ø)
jest 48.83% <ø> (+1.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐛] SPM + dynamic frameworks (Expo SDK 57): GoogleUtilities classes duplicated in RNFBAnalytics / RNFBMessaging

1 participant