chore: source posthog-ios via SPM as opt-in#81
Conversation
Adds an spm_dependency branch to the podspec so RN >= 0.75 consumers fetch posthog-ios through Swift Package Manager. CocoaPods trunk fallback retained for RN < 0.75. Unblocks PostHog/posthog-ios#472.
|
Thank you for this @turnipdabeets! CI is not happy, though. I assume this worked locally so Xcode versions maybe? |
The Pods.xcodeproj written by the React Native spm_dependency helper requires Xcode 16.3+ to parse; macos-15-xlarge picks Xcode 16.4 as latest-stable.
Yep, tested on Xcode 26.4.1 locally, looks like CI runner has Xcode 16.2 and spm_dependency needs Xcode 16.3+ so bumping the CI runner. |
Xcode 26.3's xcodebuild fails to load Pods.xcodeproj entries written by the spm_dependency helper (XCSwiftPackageProductDependency _setSavedArchiveVersion: selector). 26.4 parses the file.
|
CI failure related? I looked into how others were solving this and our solution looks functionally equivalent edit: I see the comment above |
Xcode 16.4 is the macos-15 default and the Xcode shipped alongside the React Native spm_dependency helper (RN 0.75). Xcode 16.2 (macos-14 default) and Xcode 26.0-26.3 fail to load the Pods.xcodeproj produced by the helper.
The previous commit on this branch made spm_dependency the default for RN >= 0.75. That broke the build-ios-static-lib CI job and would have broken every consumer using static linking, because RN's spm_dependency helper requires use_frameworks! :linkage => :dynamic. Instead, gate the SPM path on `"posthog.useSpm": "true"` in the consumer's ios/Podfile.properties.json. Default behavior stays on CocoaPods trunk (byte-identical to today). Adds a parallel build-ios-spm CI job that exercises the opt-in path with dynamic frameworks. Revert the CI runner / Xcode pin commits since the default job runs on the same configuration as before.
Set `contents: read` at workflow level so every job runs with the minimum necessary GITHUB_TOKEN scope. Addresses the CodeQL "Actions job or workflow does not limit the permissions of the GITHUB_TOKEN" finding.
The opt-in SPM path requires a runner Xcode that simultaneously parses spm_dependency's Pods.xcodeproj output AND compiles RN 0.77's cxxreact sources cleanly. No GitHub-hosted Xcode currently covers both: 16.2 and 26.3 fail the pbxproj load, 16.4 fails the cxxreact compile. Verified locally on Xcode 26.4.1 (not yet on hosted runners). Add CI coverage in a follow-up once the RN/Xcode compatibility window aligns.
This reverts commit 755d517.
Switch the SPM CI job to macos-26 (latest hosted runner, default Xcode), build for iOS Simulator instead of generic ios, and drop the xcpretty wrapper so any compile error surfaces in the log. Mirrors maplibre-react-native's iOS CI, which is a production library successfully shipping SPM-resolved native deps via React Native.
Xcode 26.4+ Clang rejects fmt's consteval-backed FMT_STRING. Patch fmt/base.h in post_install to force FMT_USE_CONSTEVAL=0 so the example app builds on macos-26. Ports the same workaround from posthog-js's example-expo-53 Podfile.
Replace the bare RN 0.77.3 example with a fresh @react-native-community/cli init scaffold on RN 0.79.6. The opt-in SPM path forces dynamic frameworks (an spm_dependency constraint) and RN 0.77.3 + dynamic frameworks + Xcode 26 has baseline build issues (React-debug headers don't expose). RN 0.79.6 builds clean. Add example/.npmrc with node-linker=hoisted so pnpm provides a flat node_modules layout to bare RN's iOS autolinking; the symlinked layout caused Xcode "Multiple commands produce" errors when Pods copied headers from shared node_modules paths. Ports forward: workspace dep on session-replay, fmt/base.h FMT_USE_CONSTEVAL patch, react-native.config.js autolinking config, App.tsx PostHog usage in src/.
Set node-linker=hoisted at the workspace root .npmrc. pnpm's default symlinked layout (with .pnpm/) causes Xcode 'Multiple commands produce' errors when CocoaPods copies React Native ReactCommon headers from the shared react-native node_modules path. The hoisted layout gives bare RN iOS autolinking and CocoaPods the flat dependency tree they expect. Required for the SPM opt-in CI job to build (which forces dynamic frameworks). The default static-linked path also builds clean with the hoisted layout. Remove the redundant per-package example/.npmrc; the root setting applies workspace-wide.
|
What was the CI fix afterall? Does this mean that we will be potentially breaking people's builds? |
|
dont we need also a Package.swift? because technically the spm dep. is still resolved by the podspec file but at some point the whole podspec file should be gone |
marandaneto
left a comment
There was a problem hiding this comment.
left a few comments but prob not blockers
Address review feedback on the RN 0.79.6 scaffold side effects: - Restore buildToolsVersion 36.0.0, compileSdkVersion 36, targetSdkVersion 36 (the fresh scaffold defaulted to 35, which is a regression from the previous example). - Move MainActivity.kt and MainApplication.kt back to the original 'posthogreactnativesessionreplay.example' package; restore namespace/applicationId in app/build.gradle. The scaffold defaulted to 'com.posthogreactnativesessionreplayexample' and the rename was unnecessary noise. Locally verified: iOS default + opt-in paths both '** BUILD SUCCEEDED **'; Android 'pnpm run build:android' 'BUILD SUCCESSFUL'.
Good question — yes eventually, but not yet, and not in this PR. Two reasons:
The pattern I'm seeing is to offer an opt-in approach to help unblock. See getsentry/sentry-react-native#6182 One thing worth calling out for consumers: opting into SPM today requires |
The job-level permissions block previously only granted security-events:write and packages:read. CodeQL's analyze action needs actions:read (to inspect the workflow run) and contents:read (to checkout the source and upload SARIF results). Without these, Perform CodeQL Analysis fails with: Please check that your token is valid and has the required permissions: contents: read, security-events: write Matches the canonical permissions block in github/codeql-action README.
i dont follow this, what should i see from sentry's issue? any new info that is not written down here already? PostHog/posthog-ios#472 or just AI slop? |
Updated the link in message, it was just an example of one opt-in approach, but there are others. Only to show we're not reinventing the wheel. |
The podspec previously had the posthog-ios version literal in two places (s.dependency '~> 3.58.1' and spm_dependency minimumVersion: '3.58.1'). Hoist into a local 'posthog_ios_version' variable so future bumps are a single-line edit, matching the manual chore-PR workflow shown by recent commits #74/#78/etc.
Problem
CocoaPods trunk goes permanently read-only on 2026-12-02. Ahead of that, the
posthog-iosteam is removing CocoaPods publishing from their release pipeline (tracked in PostHog/posthog-ios#472) and is gated on the two hybrid SDKs (this one andposthog-flutter) being SPM-ready first — otherwise thes.dependency 'PostHog'line in this podspec stops resolving the momentposthog-iosdrops its.podspec, and every RN consumer'spod installbreaks.posthog-flutteralready ships aPackage.swift(PR #346). This PR is the analogous unblock from the React Native side, so the iOS team can proceed.Changes
1. Opt-in SPM resolution path for
posthog-ios(the load-bearing change)The podspec now reads
posthog.useSpmfrom the consumer'sios/Podfile.properties.json. When that property is"true"and React Native >= 0.75 is in use,posthog-iosis resolved through Swift Package Manager using RN'sspm_dependencyhelper; otherwise the dependency continues to come from CocoaPods trunk vias.dependency 'PostHog', '~> 3.58.1'(byte-identical to today).The SPM constraint
upToNextMinorVersion: 3.58.1matches the existing CocoaPods~> 3.58.1semantic exactly (>= 3.58.1, < 3.59.0), so the per-release bump cadence forposthog-iosis unchanged.2. Consumer migration: opting into SPM
The opt-in path requires
use_frameworks! :linkage => :dynamic(or theUSE_FRAMEWORKS=dynamicenv var). This is a documented constraint of RN'sspm_dependencyhelper (RN PR #44627 comment) — Swift modules from SPM packages do not resolve correctly under static linkage from a podspec.3. Example app upgrade (RN 0.77.3 → 0.79.6)
The opt-in path forces dynamic frameworks. RN 0.77.3 + dynamic frameworks + Xcode 26 has a baseline build issue where
React-cxxreactcannot findReact-debug's headers (reproduced locally; not introduced by this diff). RN 0.79.6 builds cleanly under dynamic frameworks on the same toolchain, so the example app is scaffolded fresh on RN 0.79.6 to exercise the SPM CI job.The fresh scaffold uses the new
AppDelegate.swifttemplate and pulls in@react-native-community/cli@20. App-level files (App.tsx,react-native.config.js,package.jsondeps and scripts) are ported forward; the example'sApp.tsxPostHog usage is unchanged.4. pnpm hoisted node_modules at the workspace root
.npmrcnow setsnode-linker=hoisted. pnpm's default symlinked layout (the.pnpm/store with symlinks into each package'snode_modules) causes Xcode "Multiple commands produce" errors when CocoaPods copies React Native ReactCommon headers from the sharedreact-nativenode_modules path — multiple Pods all resolve to the same.pnpm/react-native@.../...source path, so Xcode registers duplicate copy commands and fails. The hoisted layout gives bare RN's iOS autolinking and CocoaPods the flat dependency tree they expect.Without this change, neither
build-ios-spm(opt-in + dynamic) norbuild-android(on the new RN 0.79.6 scaffold) builds in CI.5. fmt/base.h patch in the example Podfile post_install
Xcode 26.4+ Clang rejects fmt's
FMT_STRINGconsteval. Patchfmt/base.hinpost_installto forceFMT_USE_CONSTEVAL=0. Same workaround used inposthog-js'sexamples/example-expo-53. Upstream issues:6. New
build-ios-spmCI jobParallel to the existing
build-ios-static-lib(default path), the new job exercises the opt-in path withUSE_FRAMEWORKS=dynamicandposthog.useSpm: trueinjected into the example app. Runs onmacos-26with the runner's default Xcode 26.x. Builds for iOS Simulator.7. Workflow-level
permissions: contents: readAddresses the CodeQL "Actions job or workflow does not limit the permissions of the GITHUB_TOKEN" finding for every job.
How this unblocks us
posthog-iosvia SPM. The momentposthog-iosdeletes its.podspec, this path keeps working.posthog-ios: withposthog-flutteralready SPM-ready and this PR landing on the RN side, the hybrid-SDK gate from PostHog/posthog-ios#472 is fully cleared.Verification
build-ios-static-lib✓build-ios-spm✓build-android✓Both CI jobs run on every PR. Local verification used Xcode 26.4.1 with clean DerivedData; the example app builds in both modes from a fresh
pnpm install.Checklist
If releasing new changes
pnpm changesetto generate a changeset file