Skip to content

chore!: require Xcode 26 (swift-tools-version 6.2) - #898

Merged
turnipdabeets merged 1 commit into
chore/v4-remove-deprecatedfrom
chore/v4-swift-tools-6
Oct 5, 2026
Merged

turnipdabeets merged 1 commit into
chore/v4-remove-deprecatedfrom
chore/v4-swift-tools-6

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Part of the 4.0 stack, on top of #876.

posthog-ios 4 requires Xcode 26 (swift-tools-version 6.2), matching Apple's App Store Connect upload requirement since April 2026.

Every iOS, iPadOS, tvOS, visionOS and watchOS app that ships through the App Store (including TestFlight) already builds with a Swift 6.2 compiler. Since April 28, 2026, App Store Connect has required Xcode 26 and the 26 SDKs (Apple: upcoming requirements), and Xcode 26 ships Swift 6.2. So this floor costs nothing for any of those apps that reach users through Apple's stores.

Who is excluded: builds that never go through App Store Connect and still use Xcode 16.x or older. That covers enterprise/in-house distribution, local and CI builds, and Mac apps. Apple's notice lists the iOS, iPadOS, tvOS, visionOS and watchOS SDKs, not macOS, so Mac apps on older Xcode, in or out of the Mac App Store, are affected too.

This changes the toolchain/manifest floor only. The SDK still compiles in Swift 5 language mode (swiftLanguageModes: [.v5]; the podspec keeps swift_versions = "5.3", which Xcode compiles as -swift-version 5). There are no strict-concurrency changes and no public API changes (make apiCheck snapshot unchanged).

Why now:

  • Package.swift (tools 5.3) and Package@swift-5.9.swift were near-copies that had drifted: the 5.3 one lacked .visionOS(.v1) and lacked .macCatalyst in the PHPLCrashReporter conditions.
  • On chore!: raise minimum iOS deployment target to 15 #876 (min iOS 15) the 5.3 manifest's .iOS(.v15) doesn't evaluate ('v15' is unavailable; it needs PackageDescription 5.5). CI never loads that file, because Swift 5.9+ picks the 5.9 manifest. This PR deletes it.
  • 7 compiler-version guards in PostHog/ only existed for pre-Swift-6 compilers.

What this unlocks: optional components

SwiftPM package traits (SE-0450) need tools 6.1+. In the 6.2 manifest API, Package(traits:) and trait conditions on build settings and target dependencies are available.

With one manifest at 6.2, a later 4.x minor could add traits that let SPM users opt out of heavy components without splitting posthog-ios into separate packages. Examples: session replay's libwebp and error tracking's PLCrashReporter (#301, and the "break up SDK in packages" item in #557). Default traits would keep everything on, so adding them would be non-breaking.

This PR adds no traits. CocoaPods has no traits; subspecs would be its equivalent. Whether traits fit our replay and error-tracking code layout (conditional compilation via #if on trait-defined conditions) is unverified and needs its own investigation.

What changed

  • One Package.swift at tools 6.2, carrying the 5.9 manifest's content plus swiftLanguageModes: [.v5]. Platform minimums are unchanged from chore!: raise minimum iOS deployment target to 15 #876. None of them (.iOS(.v15), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)) is deprecated in the 6.2 PackageDescription API, and the manifest evaluates without warnings.
  • Removed the fallbacks and kept the Swift 6 branch:
    • internal import instead of @_implementationOnly import in PostHogCrashReportProcessor.swift, PostHogErrorTrackingAutoCaptureIntegration.swift, URLSessionSwizzler.swift, BoxedBeforeSend.swift and UIImage+WebP.swift. The PrivateModules modulemap pattern is unchanged. internal import needs no feature flag in Swift 5 mode with a Swift 6 compiler.
    • nonisolated(unsafe) on PostHogSDK.shared without the #if swift(>=5.10) fallback (API snapshot identical).
    • The iOS 18 accessibility hit-test path in SwiftUITapAutocapture.swift no longer sits behind #if compiler(>=6.0).
    • The compiler(>=6.4) OOM guards stay, because they still gate something.
  • CI, all moved to Xcode 26.0.1, the oldest Xcode 26 on GitHub's macos-15 images (26.0.1 / 17A400, per the runner-images readme):
    • The Xcode 15.4 "Swift 5 compatibility" CocoaPods job now tests the floor and adds a swift build step so the manifest is checked at the floor too. It's renamed to Build SDK (minimum toolchain, Xcode 26.0.1), so if branch protection requires the old check name, it needs updating.
    • The Example Pods (Static Lib (Xcode 16.2)) job is now Static Lib (Xcode 26.0.1) on macos-15-xlarge. It's renamed too, with the same branch-protection caveat.
    • The new swift build step runs make buildSdkSpm, which make buildSdk also uses.
    • CodeQL (codeql.yml, PRs to main only) moves from macos-14-xlarge (Xcode 16.2) to macos-15-xlarge.
    • CocoaPods publishing in release.yml moved from Xcode 16.2 on macos-14 (which has no Xcode 26) to Xcode 26.0.1 on macos-15. pod trunk push doesn't read Package.swift, but publishing shouldn't validate on a compiler below the supported floor. Nothing else in the release path runs SwiftPM; the other release jobs are Ubuntu-only.
  • Docs: AGENTS.md/CLAUDE.md tech stack and CONTRIBUTING ("Install Xcode 26 or later").

Stacked on #876 (min iOS 15), so the single manifest already has .iOS(.v15).

Wrapper SDKs

Each wrapper was built against this branch through a local override in throwaway copies. Nothing was committed to any wrapper repo.

Wrapper Path Result
posthog-flutter (main, Flutter 3.44.1) CocoaPods (Podfile.local → local path) iOS sim build OK. PostHog compiles with -swift-version 5, no PostHog warnings
posthog-flutter SPM (plugin Package.swift tools 5.9 → local path) Clean iOS sim build OK (-swift-version 5, no PostHog warnings). Ran on an iOS 26.4 simulator against a local mock server: /batch received swift6_tools_verify ($lib=posthog-flutter)
posthog-js RN plugin (example RN 0.79.6) CocoaPods (pod 'PostHog', :path) iOS sim build OK, -swift-version 5, no PostHog warnings (not run)
posthog-js RN plugin SPM (posthog.useSpm, spm_dependency → file:// repo at this branch) iOS sim build OK. SPM resolved this branch's commit, compiled -swift-version 5 (not run)
posthog-kmp (main, Kotlin 2.4.20 SwiftPM import) localSwiftPackage(...) in place of swiftPackage(url=...) cinterop + iosSimulatorArm64Test OK (38 tests, 0 failures). Sample app not run

Wrapper ecosystems' own minimum Xcode:

  • Flutter 3.44: requires 15, recommends 16.
  • RN 0.79.6: 15.1. RN main: 16.1.
  • Kotlin 2.4.x: compatibility table lists Xcode 26.4.

Apps from all three that ship through App Store Connect are already on Xcode 26.

💚 How did you test it?

After restacking onto #876: make test (909 Swift Testing + 169 XCTest, 0 failures), make buildSdk and make apiCheck pass; the manifest evaluates. The wrapper builds below ran before the restack, against iOS 13 minimums.

On Xcode 26.4.1 (Swift 6.3.1):

  • make format / make lint: clean (32 SwiftLint warnings, same as base).
  • make test: 909 Swift Testing tests + 169 XCTest, 0 failures.
  • make buildSdk: iOS, macOS (SPM + xcodebuild), Mac Catalyst, tvOS, watchOS, visionOS.
  • make buildExamples: platform examples, the SPM example (iOS + Catalyst), the XCFramework example, and the Pods static lib / static framework / dynamic framework examples.
  • make apiCheck: snapshot up to date.
  • pod lib lint PostHog.podspec --allow-warnings --skip-tests: passed.
  • DOWNGRADE_REF=3.58.0 make testDowngradeCompatibility: passed. Its tools-5.3 smoke package depends on the 6.2 package.

I couldn't test Xcode 26.0.1 locally; the new CI floor job covers it.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code (Opus) did the work and the wrapper verification. Decisions:

  • 6.2 rather than 6.0, to match the Xcode 26 App Store Connect requirement.
  • Keep the podspec at swift_versions = "5.3": CocoaPods' swift_versions sets the language mode, and 6.x would turn on Swift 6 checking.
  • Leave SwiftFormat's --swiftversion 5.3 alone to avoid unrelated rewrites.
  • Retarget the old-Xcode CI jobs to the new floor rather than delete them.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[High risk] Raises minimum build toolchain from Xcode 15 to Xcode 26.

The PR appears safe to merge; no outstanding finding or new actionable issue remains.

Reviews (2) · Last reviewed commit: "chore!: require Xcode 26 (swift-tools-ve..."

Comment thread .github/workflows/build.yml Outdated
@turnipdabeets
turnipdabeets force-pushed the chore/v4-swift-tools-6 branch from 957839f to e61747c Compare October 2, 2026 23:13
@turnipdabeets
turnipdabeets changed the base branch from chore/v4-remove-deprecated to chore/v4-min-ios-15 October 2, 2026 23:13
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-10-05 22:59:42 UTC
Duration: 210476ms

⚠️ Some Tests Failed

44/45 tests passed, 1 failed


Capture Tests

⚠️ 28/29 tests passed, 1 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 2809ms
Format Validation.Event Has Uuid ✅ 2700ms
Format Validation.Event Has Lib Properties ✅ 2726ms
Format Validation.Distinct Id Is String ✅ 2673ms
Format Validation.Token Is Present ✅ 302ms
Format Validation.Custom Properties Preserved ✅ 2694ms
Format Validation.Event Has Timestamp ✅ 238ms
Retry Behavior.Retries On 503 ✅ 5306ms
Retry Behavior.Does Not Retry On 400 ✅ 4744ms
Retry Behavior.Does Not Retry On 401 ✅ 4753ms
Retry Behavior.Respects Retry After Header ✅ 7765ms
Retry Behavior.Implements Backoff ✅ 21232ms
Retry Behavior.Retries On 500 ✅ 8740ms
Retry Behavior.Retries On 502 ✅ 5567ms
Retry Behavior.Retries On 504 ✅ 9266ms
Retry Behavior.Max Retries Respected ❌ 21786ms
Deduplication.Generates Unique Uuids ✅ 2807ms
Deduplication.Preserves Uuid On Retry ✅ 9288ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 16256ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7728ms
Deduplication.No Duplicate Events In Batch ✅ 2727ms
Deduplication.Different Events Have Different Uuids ✅ 2702ms
Compression.Sends Gzip When Enabled ✅ 2736ms
Batch Format.Uses Proper Batch Structure ✅ 2729ms
Batch Format.Flush With No Events Sends Nothing ✅ 273ms
Batch Format.Multiple Events Batched Together ✅ 2813ms
Error Handling.Does Not Retry On 403 ✅ 2368ms
Error Handling.Does Not Retry On 413 ✅ 4668ms
Error Handling.Retries On 408 ✅ 5374ms

Failures

retry_behavior.max_retries_respected

Expected 4 requests, got 6

Feature_Flags Tests

✅ 16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 2800ms
Request Payload.Flags Request Uses V2 Query Param ✅ 2705ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2738ms
Request Payload.Flags Request Omits Authorization Header ✅ 2649ms
Request Payload.Token In Flags Body Matches Init ✅ 2778ms
Request Payload.Groups Round Trip ✅ 2662ms
Request Payload.Groups Default To Empty Object ✅ 2930ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It ✅ 2740ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 2647ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2721ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2773ms
Request Lifecycle.No Flags Request On Init Alone ✅ 57ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 2708ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5460ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2697ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 3003ms

@turnipdabeets
turnipdabeets force-pushed the chore/v4-swift-tools-6 branch from e61747c to dcd721b Compare October 5, 2026 16:16
@turnipdabeets
turnipdabeets force-pushed the chore/v4-swift-tools-6 branch from dcd721b to 3c380b4 Compare October 5, 2026 16:42
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 5, 2026 17:14
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 5, 2026 17:14
Base automatically changed from chore/v4-min-ios-15 to chore/v4-remove-deprecated October 5, 2026 22:47
Collapse Package.swift and Package@swift-5.9.swift into a single
swift-tools-version 6.2 manifest that keeps Swift 5 language mode, and
drop the compiler-version fallbacks that a Swift 6 floor makes dead.
CI's minimum-toolchain, Pods-example and CocoaPods publish jobs move to
Xcode 26.0.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@turnipdabeets
turnipdabeets force-pushed the chore/v4-swift-tools-6 branch from 3c380b4 to 4352748 Compare October 5, 2026 22:48
@turnipdabeets
turnipdabeets merged commit 723a58f into chore/v4-remove-deprecated Oct 5, 2026
42 checks passed
@turnipdabeets
turnipdabeets deleted the chore/v4-swift-tools-6 branch October 5, 2026 22:54
turnipdabeets added a commit that referenced this pull request Oct 7, 2026
Collapse Package.swift and Package@swift-5.9.swift into a single
swift-tools-version 6.2 manifest that keeps Swift 5 language mode, and
drop the compiler-version fallbacks that a Swift 6 floor makes dead.
CI's minimum-toolchain, Pods-example and CocoaPods publish jobs move to
Xcode 26.0.1.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets added a commit that referenced this pull request Oct 9, 2026
Collapse Package.swift and Package@swift-5.9.swift into a single
swift-tools-version 6.2 manifest that keeps Swift 5 language mode, and
drop the compiler-version fallbacks that a Swift 6 floor makes dead.
CI's minimum-toolchain, Pods-example and CocoaPods publish jobs move to
Xcode 26.0.1.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

2 participants