Skip to content

feat(ios)!: take the mParticle SDKs from Swift Package Manager by default - #429

Open
thomson-t wants to merge 3 commits into
thomson-t/spm-07-remove-placeholder-mapfrom
thomson-t/spm-08-spm-default
Open

thomson-t wants to merge 3 commits into
thomson-t/spm-07-remove-placeholder-mapfrom
thomson-t/spm-08-spm-default

Conversation

@thomson-t

@thomson-t thomson-t commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

CocoaPods trunk becomes read-only on 2 December 2026 (CocoaPods announcement), and CocoaPods is now deprecated as a way to get the mParticle SDKs. Earlier in this series Swift Package Manager was opt-in, so an app that did nothing would stay on CocoaPods. Once this lands, the iOS mParticle core SDK and its kits come from Swift Package Manager by default. The release is already a breaking major for the placeholder change, so apps take one build change now instead of another major later. The CocoaPods path stays available as a deprecated opt-out.

Programme

Part of the plan to support Swift Package Manager in this package before CocoaPods trunk becomes read-only. This is the eighth pull request in the series merging into the workstation/spm-migration branch, which merges into main once the series is complete and ships in one release; the plan record is internal and cannot be linked here.

What changes

Before: $RNMParticleUseSPM = true turned the mode on, and the Podfile had to call mparticle_spm_post_install(installer, kits: [...]) with each kit's package URL. The Expo plugin defaulted to 'cocoapods' and knew one kit.

After:

  • Podspec: Swift Package Manager unless the Podfile sets $RNMParticleDisableSPM = true. The podspec loads ios/mparticle_spm.rb, which hooks pod install itself, so there is no Podfile call to add. The hooks run when the Podfile evaluates the podspec, which React Native's use_native_modules! does.
  • Podfile settings: $RNMParticleSPMKits lists kits by CocoaPods name, or { url:, product:, version: } for any other kit. $RNMParticleSPMCoreVersion optionally pins the core. An app with no kits needs no change.
  • Kit table: ios/mparticle_spm_kits.json maps every kit of the mParticle Apple SDK, 32 in all, to its Swift package. The Ruby helper and the Expo plugin read the same file. Two Kochava kits ship only as Swift packages, and RoktSDKPlus is supported only from Swift Package Manager, so these names are marked and the Expo plugin's CocoaPods path rejects them. RoktSDKPlus already includes the Rokt kit, so listing both is an error.
  • Checks during pod install:
    • a tvOS target that uses this package stops the install with the opt-out instructions. The podspec keeps tvOS declared in this mode, because React Native's autolinking drops pods that don't support a target's platform, which would leave a tvOS target without this pod and without the error;
    • a pod that would add a second SDK copy stops it before CocoaPods' "does not define modules" error;
    • both messages name the two fixes.
    • With the opt-out, the install warns if the app target still links the mParticle Swift packages.
  • Expo plugin: defaults to 'spm' and writes only the settings above. 'cocoapods' writes the opt-out and the same pods and pre_install hook as before.
  • tvOS with the opt-out: removes two unused SafariServices imports from the Fabric Rokt view. SafariServices doesn't exist on tvOS, so New Architecture tvOS apps failed to compile.
  • Sample and CI: the sample takes the SDKs from Swift Package Manager unless MP_USE_COCOAPODS=1. CI's CocoaPods leg sets it, and both check names are unchanged. The Swift Package Manager leg also checks that a tvOS Podfile stops with the iOS-only error, and both legs now run the Fabric view's props test. Both sample jobs now delete yarn's cached unpacked copy of the packed tarball before installing; yarn 1 reuses it by path, so the samples had been building an old copy of this package.
  • Docs: README and MIGRATING describe the default, the kit settings, troubleshooting and the migration. The CocoaPods instructions move under "CocoaPods (deprecated)".

Start reading at ios/mparticle_spm.rb (the InstallerHooks module at the end), then the podspec. Left alone on purpose:

  • the packages stay on the app target, not the pod, because apps start the SDK in their own AppDelegate and choose their own kits;
  • the pod keeps working with static libraries and both use_frameworks! linkages.

Linked work

Depends on: placeholder names only (#428, branch thomson-t/spm-07-remove-placeholder-map) and the pull requests below it in this series; merge those first.
Related: the opt-in mode this makes the default (#423) and its Expo plugin option (#424); the earlier attempt that linked the SDK through the pod and was reverted (#308, #309).

Rollout

Path: this merges into workstation/spm-migration, not main, so nothing reaches main or a release until the whole series has merged there and that branch is merged into main. It then ships in the next release, which is a major version.
Feature flags: none. The opt-out is $RNMParticleDisableSPM = true in the Podfile, or "iosDependencyManager": "cocoapods" for Expo.
Turning it off: an app sets the opt-out and restores its kit pods. For the package, reverting this pull request makes Swift Package Manager opt-in again in the next release.
What we watch: this repository's issues, for [mParticle] errors from pod install, the red box for a duplicate SDK, and kits reported as unknown.

Risks

  • An app that declares kit pods fails pod install after upgrading. Not prevented, because this is the intended break. It is mitigated by the error naming both fixes, MIGRATING, and the opt-out. We would see that error in partner reports.
  • A CocoaPods release renames or reorders the installer methods the hooks wrap. Contained, because 1.15.2, which CI uses, and 1.16.2 have the same methods in the same order, and CI's Swift Package Manager leg runs a real pod install and build. If the hooks stopped running, the build would fail with the "not linked into the app target" message. We would see that in CI.
  • tvOS apps must take the deprecated CocoaPods opt-out in this release. Not prevented, because Swift Package Manager mode is iOS only. Mitigated by pod install stopping with the opt-out instructions. After CocoaPods trunk freezes on 2 December, those apps stay on the last pod versions. We would see tvOS reports in this repository's issues.
  • The kit table falls behind a new or renamed kit. Contained, because any kit can be given as { url:, product:, version: }, and a test checks every entry's URL and product. We would see "unknown kit" errors.
  • Expo's hosted builds were not run. Contained, because they run the same expo prebuild and pod install, which passed locally. We would see build failures reported from Expo's build service.

Risk class: medium, because this changes how every iOS app on the release gets the SDK.

Who

Written by: an automated coding agent (Claude Code), at an engineer's request.
Code reviewed before opening: an independent review agent reviewed the change before it was committed; its advisory about leftover custom kits widened the opt-out warning. The review fixes were reviewed the same way, which moved a podspec comment and narrowed a README line.
Design reviewed before opening: the requesting engineer chose Swift Package Manager by default with an opt-out, a new pull request on top of the series, and every supported kit in the table.
Decision this implements: the engineering decision on 2026-09-30 to make Swift Package Manager the default, after a design review compared this package with another React Native SDK that also defaults to Swift Package Manager; the record is internal and cannot be linked.
Checked: on 2026-09-30, with Xcode 27.0, an iOS 26.5 simulator and CocoaPods 1.15.2:

  • jest (38 tests), yarn build, yarn build:plugin and trunk.
  • Sample app (React Native 0.84), default: pod install linked the core and Rokt kit, with no mParticle pods in Podfile.lock. Release build, unsigned archive, and one copy of each SDK class, all in the app binary, with static libraries and with use_frameworks! :linkage => :dynamic. Embedded by name and overlay placements rendered. One embedded run ended in PlacementFailure from the placement service; it rendered on the rerun.
  • Sample app, opt-out: pods resolved as before; Release build, archive and one copy of each class; both placements rendered.
  • Failure paths, against the sample's Podfile: a kit pod left in, an unknown kit, RoktSDKPlus with the Rokt kit, and leftover packages with the opt-out each gave their message. No kits linked the core only.
  • Fresh Expo 57 app (React Native 0.86.3): the Podfile got only the settings block. Release build, archive and one copy of each class with static libraries and with useFrameworks: static; both placements rendered, with the same one-off PlacementFailure on one embedded run.
  • Fresh React Native 0.87.1 app: with the old kit pod and hook, pod install stopped with the message; after the three-line migration it linked the packages and archived with one copy of each class.
    Then on 2026-10-05, after this pull request's review, with Xcode 27.0 and CocoaPods 1.16.2:
  • Fresh react-native-tvos 0.83 app (Podfile platform :tvos): before this fix, pod install succeeded and Podfile.lock had no react-native-mparticle; with it, pod install stops with the iOS-only message. With the opt-out, platform :tvos, '15.6' and the README's pre_install hook, pod install succeeded and the app built for the tvOS Simulator once the SafariServices imports were gone. That throwaway app also needed a C++17 setting for React Native's own fmt on Xcode 27.
  • The new CI check, run on the sample: it passes with this fix. Without it, CocoaPods stops on React Native's own tvOS incompatibility instead, and the step fails.
  • RoktSDKPlus with the Expo plugin's CocoaPods output: pod install failed because the Rokt and payment pods under it are statically linked.
  • Sample, default mode: pod install linked the same two packages; the props, sizing, Swift-layer and duplicate-SDK test classes passed (8 tests). jest (39 tests).

Not checked: Expo's hosted builds; tvOS at runtime; physical devices. The sample's unit tests run in CI, in both legs.

Size

Hand-written: about 530 lines added and 325 removed in 11 files; 124 added and 64 removed of them in tests, and about 125 in docs.
Generated: ios/mparticle_spm_kits.json (138 lines), extracted from the mParticle Apple SDK repository.

🤖 Generated with Claude Code

@cursor

cursor Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Breaking change to iOS native dependency resolution that switches the default SDK integration to Swift Package Manager, potentially failing builds for projects with legacy CocoaPods kit configurations until migrated or opted out.

Overview
Switches the default iOS dependency manager for the mParticle Apple SDK and its kits from CocoaPods to Swift Package Manager (SPM). React Native and this library still install through CocoaPods, but mParticle core and kit packages are now automatically resolved, pinned, and linked into the host application target via SPM on pod install. CocoaPods support is retained as a deprecated opt-out via $RNMParticleDisableSPM = true or iosDependencyManager: 'cocoapods'.

Introduces a kit mapping registry (ios/mparticle_spm_kits.json) covering 32 mParticle kits and hooks directly into CocoaPods installer lifecycle methods to automate package linking without requiring explicit post_install calls. Updates the Expo config plugin to default to SPM mode, adds safety guards against duplicate SDK linkages or unsupported tvOS SPM targets, removes unused SafariServices headers to restore tvOS build support under Fabric, and updates migration guides and CI workflows.

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

@thomson-t
thomson-t added this pull request to stack #427 September 30, 2026 20:05

@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 using default effort and found 1 potential issue.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit 2a96b2e. Configure here.

Comment thread plugin/src/withMParticleIOS.ts
Copilot AI balanced review requested due to automatic review settings October 1, 2026 18:29
@thomson-t
thomson-t force-pushed the thomson-t/spm-08-spm-default branch from 2a96b2e to afaa4f8 Compare October 1, 2026 18:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The tvOS validation is bypassed by autolinking, and RoktSDKPlus is incorrectly accepted in CocoaPods mode.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Makes Swift Package Manager the default source for iOS mParticle SDKs while retaining CocoaPods as a deprecated opt-out.

Changes:

  • Adds automatic Swift package linking, validation, and a shared kit catalog.
  • Updates the Expo plugin, sample app, and CI for the new default.
  • Revises migration and setup documentation.
File Description
ios/​mparticle_spm.rb Adds installer hooks and package management.
ios/​mparticle_spm_kits.json Defines supported Swift package kits.
react-native-mparticle.podspec Enables SPM mode by default.
plugin/​src/​withMParticleIOS.ts Generates SPM or CocoaPods settings.
plugin/​src/​withMParticle.ts Updates plugin option documentation.
js/​__tests__/​plugin-ios-spm.test.ts Tests plugin generation and kit metadata.
sample/​ios/​Podfile Makes the sample use SPM by default.
.github/​workflows/​pull-request.yml Selects CocoaPods only for its CI leg.
README.md Documents setup and troubleshooting.
MIGRATING.md Adds migration instructions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ios/mparticle_spm.rb
Comment thread ios/mparticle_spm_kits.json Outdated
@thomson-t
thomson-t force-pushed the thomson-t/spm-08-spm-default branch 2 times, most recently from 4f5545e to 28d1f59 Compare October 5, 2026 18:06

@nickolas-dimitrakas nickolas-dimitrakas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Requesting changes. Two small items, then this is good to go.

1. The tvOS guard in check_podfile! can't fire.
In SPM mode the podspec is iOS-only, and React Native's autolinking skips pods that don't support the target's platform (AutolinkingUtils.is_platform_supported? in autolinking.rb). A tvOS target therefore never registers react-native-mparticle. check_podfile! looks for that dependency, so it finds nothing and never raises. A tvOS app that upgrades would silently lose the native module instead of getting the error the PR describes. The description also says a real Podfile with a tvOS target wasn't checked. Please detect tvOS while the podspec is evaluated, or otherwise keep a marker before autolinking filters the spec. Please also add a test against a real tvOS-target Podfile.

2. RoktSDKPlus isn't marked Swift-package-only in mparticle_spm_kits.json.
Without that, the Expo 'cocoapods' path accepts iosKits: ['RoktSDKPlus'] and writes a pod 'RoktSDKPlus' line that can't resolve. Upstream v9.6.1 marks it spm_package_only: true.

Non-blocking:

  • Pod::Installer.prepend overrides CocoaPods' private methods (resolve_dependencies, validate_targets, run_podfile_post_install_hooks). It's verified on 1.15.2 and 1.16.2, but a note in the README, or a version check, would make a future break easier to spot.
  • readSpmKitTable() is called inside a .filter(), so the JSON is read once per kit. Read it once into a local variable, as the SPM path already does.

@thomson-t
thomson-t force-pushed the thomson-t/spm-08-spm-default branch 2 times, most recently from be49556 to 94f5284 Compare October 5, 2026 21:50
@thomson-t

Copy link
Copy Markdown
Contributor Author

Thanks, both fixed in 94f5284.

  1. tvOS: the podspec keeps tvOS declared in SPM mode, so autolinking adds the pod and check_podfile! stops pod install. On a real react-native-tvos 0.83 app, pod install used to succeed without the pod; it now stops with the error. CI's SPM leg checks a tvOS Podfile too. Testing the opt-out also showed tvOS builds failing on an unused SafariServices import, now removed.
  2. RoktSDKPlus: the pod is on trunk and resolves, but pod install from the plugin's 'cocoapods' output fails on the statically linked Rokt and payment pods under it. It's now marked SPM-only, with a reworded error.

Non-blocking: the kit table is read once, and the README names the hooked CocoaPods methods.

thomson-t and others added 3 commits October 5, 2026 21:45
…ault

The mParticle Apple SDK will publish no CocoaPods releases after CocoaPods
trunk becomes read-only on 2 December 2026. The mParticle core SDK and its
kits now come from Swift Package Manager by default, linked into the app
target. React Native and this package still install with CocoaPods.

The podspec loads ios/mparticle_spm.rb, which hooks pod install itself, so no
Podfile call is needed. Kits are listed by CocoaPods name in
$RNMParticleSPMKits and mapped through ios/mparticle_spm_kits.json, which
covers every kit of the mParticle Apple SDK. pod install stops with both
fixes named when a kit pod or tvOS target is left in.
$RNMParticleDisableSPM = true keeps the CocoaPods path, which is deprecated.

The Expo config plugin now defaults to 'spm' and reads the same kit table;
'cocoapods' is the opt-out. The sample and CI flip to match.

BREAKING CHANGE: an app that declares mParticle kit pods must move them to
$RNMParticleSPMKits, or set $RNMParticleDisableSPM = true.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
React Native's autolinking skips any pod whose spec doesn't support a
target's platform. In Swift Package Manager mode the podspec declared
iOS only, so a tvOS target never received react-native-mparticle, the
iOS-only check in MParticleSPM.check_podfile! never matched, and
pod install succeeded without the native module. Keep tvOS declared in
that mode, so autolinking adds the pod and the check stops pod install
with the opt-out instructions, which now also name Expo's
iosDependencyManager 'cocoapods'.

For that opt-out to work on tvOS, remove the unused SafariServices
imports from the Fabric Rokt view. The framework doesn't exist on tvOS,
so New Architecture tvOS builds failed to compile; the imports date
from 3.x.

Also from review:
- Mark RoktSDKPlus as supported only from Swift Package Manager. Its
  pod exists, but the Expo plugin's CocoaPods path links it without the
  Rokt and payment pods under it, so pod install fails.
- Read the kit table once in the Expo plugin's CocoaPods path.
- README: name the CocoaPods private methods the install hooks override
  and the CocoaPods versions they were tested with.

CI's Swift Package Manager leg now checks that a tvOS Podfile stops with
the iOS-only error, and runs RoktNativeLayoutComponentViewPropsTests,
which the test list had missed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Yarn 1 unpacks a local .tgz dependency into .tmp in its cache, keyed only
by the file's path, and reuses that copy on later installs even when the
tarball has changed. actions/setup-node restores the yarn cache, and the
sample's file:../react-native-mparticle-latest.tgz path is the same in
every run, so both sample jobs installed the build of this package that
was cached when the cache was saved, not the one `yarn dev:pack` had just
built. Remove the cached copy before installing.

On this branch the iOS sample job had been running a build from before
the Fabric props fix and the tvOS podspec change, so the props test and
the tvOS check failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@thomson-t
thomson-t force-pushed the thomson-t/spm-08-spm-default branch from df9591d to 466bf61 Compare October 6, 2026 01:45
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.

3 participants