Skip to content

[FSSDK-12409] feat: add Swift Package Manager (SPM) support for iOS - #116

Open
muzahidul-opti wants to merge 10 commits into
masterfrom
feature/spm-support
Open

[FSSDK-12409] feat: add Swift Package Manager (SPM) support for iOS#116
muzahidul-opti wants to merge 10 commits into
masterfrom
feature/spm-support

Conversation

@muzahidul-opti

@muzahidul-opti muzahidul-opti commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Swift Package Manager (SPM) support for the iOS plugin while maintaining full CocoaPods backward compatibility. Flutter 3.44+ uses SPM by default for iOS dependency resolution — this change enables that path.

What changed

iOS directory layout (canonical SPM structure)

  • Adopted the official Flutter SPM plugin layout: ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/
  • Removed the old ios/optimizely-flutter-sdk/Classes/ layout and the ios/optimizely_flutter_sdk symlink
  • Created Package.swift declaring OptimizelySwiftSDK 5.4.2 as an SPM dependency
  • ObjC files (.m/.h) are excluded from the SPM target (SPM doesn't support mixed Swift/ObjC in a single target) but remain on disk for CocoaPods builds

Plugin registration

  • Changed pluginClass in pubspec.yaml from OptimizelyFlutterSdkPlugin (ObjC) to SwiftOptimizelyFlutterSdkPlugin (Swift) for direct SPM registration

CocoaPods backward compatibility

  • Updated podspec source_files glob to 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*'
  • No changes needed for CocoaPods consumers — flutter build ios with SPM disabled still resolves via podspec

CI: version drift check (FR-014)

  • New .github/scripts/check-version-drift.sh extracts OptimizelySwiftSDK version from both podspec and Package.swift, exits non-zero if they diverge
  • Added version_drift_check job to flutter.yml that runs before unit_test_coverage

Docs

  • Updated CLAUDE.md with new file paths, version management for dual SPM/CocoaPods
  • Updated README.md noting SPM and CocoaPods dual support

How dependency resolution works

Flutter version SPM enabled? Resolution path
3.44+ Yes (default) Package.swift → SPM fetches swift-sdk.git
3.44+ No (opt-out) podspec → CocoaPods fetches OptimizelySwiftSDK
< 3.44 N/A podspec → CocoaPods (SPM not available)

Test plan

  • flutter test — 140 tests pass
  • flutter analyze — 0 issues
  • Version drift check passes (both files declare 5.4.2)
  • SPM build: flutter build ios --simulator --no-codesign with SPM enabled
  • CocoaPods build: same command with SPM disabled
  • CI: all 5 jobs pass (version_drift_check, unit_test_coverage, build_test_android, build_test_ios, integration tests)

Ticket:

🤖 Generated with Claude Code

muzahidul-opti and others added 8 commits August 20, 2026 18:17
Feature specification for migrating the Flutter SDK and test app from
CocoaPods to Swift Package Manager, including clarifications on iOS
deployment targets, version pinning strategy, and non-regression
requirements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Research, data model, quickstart validation guide, and full
implementation plan covering Flutter SPM plugin structure, source file
migration, CI updates, and constitution compliance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarification session resolved 4 ambiguities: test app scope (CI + SPM
config), mixed CocoaPods/SPM state (Flutter handles automatically),
version drift enforcement (CI check via FR-014), and no deprecation
notices. Updated all design artifacts to reflect these decisions.

Generated tasks.md with 39 tasks across 7 phases organized by user
story. Cross-artifact analysis found no critical issues; remediated
3 medium findings (plan/CI inconsistency, missing script path,
ambiguous CI decision).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move iOS source files from ios/Classes/ to SPM-standard layout at
ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/. Create
Package.swift declaring OptimizelySwiftSDK dependency via SPM with
exact version pinning matching the podspec.

Use a symlink (ios/optimizely_flutter_sdk → ios/optimizely-flutter-sdk)
to work around Flutter/SPM identity normalization: SPM converts
underscores to hyphens in package identity, but Flutter looks for the
underscored directory name.

Exclude ObjC bridge files from the SPM target (mixed-language not
supported in SPM 5.9) — they remain for CocoaPods builds. Change
pubspec.yaml pluginClass to SwiftOptimizelyFlutterSdkPlugin so both
SPM and CocoaPods register the plugin via the Swift class directly.

Update podspec source_files to point to the new directory layout.
Both SPM and CocoaPods builds verified on iOS simulator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move sources from Sources/optimizely_flutter_sdk/ to Classes/ for
  simpler, Flutter-conventional directory structure
- Add path: "Classes" to Package.swift target (SPM supports custom paths)
- Update podspec source_files glob to match new layout
- Add version drift CI job to enforce OptimizelySwiftSDK version parity
  between podspec and Package.swift
- Update CLAUDE.md file paths and README.md with SPM support note

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pin all jobs to Flutter 3.44.0 for consistent caching
- Remove duplicate flutter-action call and stale 3.0.5 pin from build_test_android
- Add version_drift_check dependency to build_test_ios
- Enable SPM only in build_test_ios (not needed for Dart-only unit tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document SPM identity normalization (SE-0292) and symlink workaround
- Document mixed Swift/ObjC limitation and exclude approach
- Document pluginClass change to SwiftOptimizelyFlutterSdkPlugin
- Update directory paths from Sources/optimizely_flutter_sdk/ to Classes/
- Update Package.swift template with path override and exclude
- Update CI section with Flutter version pinning (3.44.0 SDK, 3.16.0 testapp)
- Update key risks with resolved/mitigated status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@muzahidul-opti muzahidul-opti changed the title feat: add Swift Package Manager (SPM) support for iOS [FSSDK-12409] feat: add Swift Package Manager (SPM) support for iOS Aug 21, 2026

@pvcraven pvcraven 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.

Kimi complaints. Are these valid?

1. Integration tests are failing

Per the project constitution, all five CI jobs must pass before merge. The two downstream integration checks are currently red:

Job Result Failure
optimizely/optimizely-flutter-testapp (ios) ❌ fail 124 passed, 2 failed
optimizely/optimizely-flutter-testapp (android) ❌ fail Emulator boot timeout

iOS failures (both in CMAB cache tests):

❌ CMAB Integration Tests 2. Cache Hit - Same user and attributes should use cache
Expected: true
Actual: <false>
First call should be a cache miss

❌ CMAB Integration Tests 3. Cache Miss - Changing attributes should trigger new decision
Expected: true
Actual: <false>
Should log cache attributes mismatch when attributes change

These failures must be understood before merge. If they are pre-existing on master, provide evidence; if they are caused or exposed by the SPM migration, fix the root cause.

2. Missing CHANGELOG entry

SPM support is a user-facing feature. Add an entry in CHANGELOG.md under ## Unreleased (or the target release version).


⚠️ Required changes

3. Update .specify/memory/constitution.md Principle VIII

Principle VIII currently names only ios/optimizely_flutter_sdk.podspec as the iOS native SDK source-of-truth. Because ios/optimizely-flutter-sdk/Package.swift now also declares the pinned OptimizelySwiftSDK version, the constitution should list both files.

4. Fix stale layout in specs/001-cocoapods-to-spm-migration/data-model.md

data-model.md describes a Sources/optimizely_flutter_sdk/ layout and a podspec path of optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*. The actual implementation uses Classes/ and optimizely-flutter-sdk/Classes/**/*. Sync the spec artifact with the real file layout.

5. Add @objc(SwiftOptimizelyFlutterSdkPlugin)

pubspec.yaml now registers the Swift class directly:

ios:
  pluginClass: SwiftOptimizelyFlutterSdkPlugin

To ensure a stable Objective-C symbol across CocoaPods and SPM module headers, annotate the class explicitly:

@objc(SwiftOptimizelyFlutterSdkPlugin)
public class SwiftOptimizelyFlutterSdkPlugin: NSObject, FlutterPlugin { ... }

This is a standard Flutter SPM plugin convention and removes ambiguity in how the generated plugin registrant resolves the class.


📝 Optional / follow-up

  • Confirm Package.swift product naming. The library product is named optimizely-flutter-sdk (hyphenated) while the target is optimizely_flutter_sdk (underscored). Verify that Flutter’s generated FlutterGeneratedPluginSwiftPackage looks up the hyphenated product name; otherwise consumers will fail to link the plugin.
  • Dead Objective-C wrapper. Since pluginClass points directly at the Swift class, OptimizelyFlutterSdkPlugin.m/.h are no longer the registration point for either SPM or CocoaPods. Consider removing them in a follow-up cleanup or explicitly documenting that they are kept as a transitional artifact.
  • CocoaPods fallback in example app. The regenerated example/ios/Runner.xcodeproj/project.pbxproj removed the [CP] Embed Pods Frameworks build phase while still linking Pods_Runner.framework. Confirm that flutter build ios --no-enable-swift-package-manager still embeds CocoaPods frameworks correctly.
  • Flutter 3.44.0 pinning. Pinning all jobs to 3.44.0 is good for reproducibility on the iOS SPM job, but unnecessary for unit tests and Android builds. Consider using channel: stable for non-iOS jobs to avoid breakage if 3.44.0 has any availability issues.

Merge checklist

  • iOS CMAB integration test failures resolved (not just re-run).
  • Android integration test re-run green or confirmed flaky/infrastructure-only.
  • CHANGELOG.md updated with SPM support note.
  • .specify/memory/constitution.md Principle VIII updated to include Package.swift.
  • specs/001-cocoapods-to-spm-migration/data-model.md syncs with actual Classes/ layout.
  • @objc(SwiftOptimizelyFlutterSdkPlugin) added to the plugin class.
  • All five CI jobs green.

Move iOS source files from Classes/ to Sources/optimizely_flutter_sdk/
per the official Flutter SPM plugin guide. Remove the hyphenated directory
and symlink workaround — Flutter 3.44+ discovers the package natively.
Pre-3.44 users resolve via CocoaPods and are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@muzahidul-opti

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! Here's the breakdown:

Addressed

  • Principle VIII (constitution) — Updated to include Package.swift alongside the podspec as the iOS native SDK version source-of-truth. ✅

Not applicable

  • data-model.md stale layout — The spec actually matches the current implementation. The layout is Sources/optimizely_flutter_sdk/, not Classes/. The podspec source_files path is optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*, which matches what data-model.md documents. No change needed.

  • @objc(SwiftOptimizelyFlutterSdkPlugin) — Not needed. Swift auto-generates the correct unqualified ObjC name for public NSObject subclasses in the -Swift.h bridging header. GeneratedPluginRegistrant.m already resolves SwiftOptimizelyFlutterSdkPlugin correctly — this plugin has shipped for years without it. Flutter's own flutter create --template=plugin also generates bare classes without @objc. The SPM path doesn't use ObjC interop at all (the generated FlutterGeneratedPluginSwiftPackage.swift is pure Swift).

Separate work

  • iOS CMAB integration test failures — These are pre-existing on master and will be addressed in a separate testapp CI PR.
  • CHANGELOG — Will be added with the release notes.

Optional items — no action needed

  • Product naming (hyphen vs underscore) — Verified working. Flutter's generated FlutterGeneratedPluginSwiftPackage/Package.swift correctly resolves .product(name: "optimizely-flutter-sdk", package: "optimizely_flutter_sdk"). The CI iOS build confirms it.

  • Dead ObjC wrapperOptimizelyFlutterSdkPlugin.m/.h are not dead — they're still required for CocoaPods builds. GeneratedPluginRegistrant.m imports and calls into them. They're already excluded from SPM via Package.swift's exclude list with a comment explaining why.

  • CocoaPods fallback — Covered by our e2e testapp which validates the legacy CocoaPods path.

  • Flutter 3.44.0 pinning — Intentional for reproducibility. Our e2e testapp ensures compatibility with legacy Flutter versions separately.

Co-Authored-By: Claude Opus 4.6 (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