Skip to content

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

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

thomson-t wants to merge 1 commit into
thomson-t/spm-07-remove-placeholder-mapfrom
thomson-t/spm-08-spm-default

Conversation

@thomson-t

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, so their pod-style names are marked. 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 before CocoaPods' generic platform 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.
  • 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.
  • 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.
  • 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.
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. The tvOS check was run against CocoaPods Podfile objects: it rejects a tvOS target that uses this package and ignores an unrelated one.
  • 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.
    Not checked: Expo's hosted builds; a real Podfile with a tvOS target; physical devices. The sample's unit tests run in CI, in both legs.

Size

Hand-written: about 500 lines added and 320 removed in 9 files; 121 added and 64 removed of them in tests, and about 120 in docs.
Generated: ios/mparticle_spm_kits.json (134 lines), extracted from the mParticle Apple SDK repository.

🤖 Generated with Claude Code

…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) <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes the default iOS native dependency resolution to Swift Package Manager, which is a breaking change for iOS apps with explicit kit pod declarations during pod install.

Overview
Defaults to Swift Package Manager (SPM) for resolving the mParticle iOS core SDK and kits, replacing CocoaPods as the default mechanism ahead of CocoaPods trunk becoming read-only. Apps can temporarily opt out and remain on CocoaPods by setting $RNMParticleDisableSPM = true in the Podfile or specifying iosDependencyManager: 'cocoapods' in the Expo plugin.

Introduces automatic Podfile installer hooks and a kit registry (ios/mparticle_spm_kits.json) covering 32 mParticle kits. During pod install, ios/mparticle_spm.rb hooks into Pod::Installer to link configured SPM packages into app targets, validate kit configurations from $RNMParticleSPMKits, and prevent duplicate SDK installations or unsupported tvOS configurations.

Updates the Expo config plugin, sample app, CI, and documentation to default to SPM generation, update matrix testing for both SPM and CocoaPods modes, and detail migration steps for bare and Expo React Native projects.

Reviewed by Cursor Bugbot for commit 2a96b2e. 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.


const swiftPackageOnly = (props.iosKits ?? []).filter(kit =>
readSpmKitTable().swiftPackageOnly.includes(kit)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON file read repeatedly inside filter callback

Low Severity

readSpmKitTable() is called inside the .filter() callback, so it reads and parses mparticle_spm_kits.json from disk once per kit in iosKits. The SPM path in getSpmSettings correctly reads it once into a local table variable. The cocoapods path here could do the same to avoid redundant I/O and JSON parsing.

Fix in Cursor Fix in Web

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

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.

1 participant