From 3e5a3795df0b01e4fa0da947ec99af3a0448bda7 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 20 Aug 2026 18:17:57 +0600 Subject: [PATCH 01/10] docs: add spec for CocoaPods to SPM migration 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) --- .specify/feature.json | 3 + .../checklists/requirements.md | 37 +++++ specs/001-cocoapods-to-spm-migration/spec.md | 132 ++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 .specify/feature.json create mode 100644 specs/001-cocoapods-to-spm-migration/checklists/requirements.md create mode 100644 specs/001-cocoapods-to-spm-migration/spec.md diff --git a/.specify/feature.json b/.specify/feature.json new file mode 100644 index 0000000..7b91d9a --- /dev/null +++ b/.specify/feature.json @@ -0,0 +1,3 @@ +{ + "feature_directory": "specs/001-cocoapods-to-spm-migration" +} diff --git a/specs/001-cocoapods-to-spm-migration/checklists/requirements.md b/specs/001-cocoapods-to-spm-migration/checklists/requirements.md new file mode 100644 index 0000000..89a1e2a --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: CocoaPods to SPM Migration + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-08-20 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- All items pass validation. Spec is ready for `/speckit-plan`. +- FR-010 clarified: SDK keeps iOS 10.0, test app keeps iOS 13.0 (resolved during clarification). +- The spec references both the SDK repo and the test app repo as in-scope. +- Re-validated after clarification session on 2026-08-20 (3 questions answered). diff --git a/specs/001-cocoapods-to-spm-migration/spec.md b/specs/001-cocoapods-to-spm-migration/spec.md new file mode 100644 index 0000000..b770b06 --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/spec.md @@ -0,0 +1,132 @@ +# Feature Specification: CocoaPods to Swift Package Manager Migration + +**Feature Branch**: `001-cocoapods-to-spm-migration` + +**Created**: 2026-08-20 + +**Status**: Draft + +**Input**: User description: "Flutter going to drop CocoaPods support. Want to move Swift Package Manager. Migrate the Flutter SDK and test app from CocoaPods to SPM gracefully. Follow web and Flutter official guidelines for the migration." + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - SDK Consumers Build iOS Without CocoaPods (Priority: P1) + +A developer who integrates the Optimizely Flutter SDK into their Flutter app can build and run the iOS target without CocoaPods installed. The SDK's iOS dependency (OptimizelySwiftSDK) is resolved through Swift Package Manager instead. The developer's existing workflow (`flutter build ios`, `flutter run`) continues to work without manual SPM configuration steps. + +**Why this priority**: This is the core deliverable. Without this, the SDK cannot function once Flutter drops CocoaPods support. It directly unblocks all iOS users. + +**Independent Test**: Can be tested by removing CocoaPods from the build environment, running `flutter build ios` against a fresh Flutter app that depends on `optimizely_flutter_sdk`, and verifying a successful build with OptimizelySwiftSDK resolved via SPM. + +**Acceptance Scenarios**: + +1. **Given** a Flutter app with `optimizely_flutter_sdk` as a dependency, **When** the developer runs `flutter build ios` on a machine without CocoaPods, **Then** the build succeeds and OptimizelySwiftSDK is resolved via SPM. +2. **Given** the SDK plugin's iOS platform configuration, **When** Flutter tooling resolves iOS dependencies, **Then** the OptimizelySwiftSDK package is fetched from its SPM-compatible repository and linked correctly. +3. **Given** a developer upgrades from a prior CocoaPods-based version of the SDK, **When** they run `flutter pub get` and `flutter build ios`, **Then** the migration to SPM happens transparently without manual intervention beyond cleaning old CocoaPods artifacts. + +--- + +### User Story 2 - Test App Migrated to SPM (Priority: P1) + +The `optimizely-flutter-testapp` project's iOS target builds and runs using SPM instead of CocoaPods. Integration tests and CI pipelines that depend on the test app continue to pass. + +**Why this priority**: The test app is the SDK's validation layer. CI integration tests (`integration_ios_tests`) depend on it. Without migrating it, the team cannot verify the SDK works end-to-end on iOS. + +**Independent Test**: Can be tested by building and running the test app's iOS target from the `optimizely-flutter-testapp` repo after removing Podfile/Pods, and verifying all integration tests pass. + +**Acceptance Scenarios**: + +1. **Given** the test app repository, **When** a developer runs `flutter build ios` or `flutter run` targeting iOS, **Then** the build succeeds without CocoaPods and uses SPM for native dependency resolution. +2. **Given** the test app's CI pipeline, **When** the `integration_ios_tests` workflow runs, **Then** it completes successfully with SPM-based dependency resolution. + +--- + +### User Story 3 - Clean Removal of CocoaPods Artifacts (Priority: P2) + +All CocoaPods-specific configuration files (Podfile, Podfile.lock, .podspec, Pods directory references) are removed from both the SDK and test app repositories. No orphaned CocoaPods configuration remains that could confuse developers or tooling. + +**Why this priority**: Leaving CocoaPods artifacts creates confusion and maintenance burden. However, the migration must work first (P1) before cleanup matters. + +**Independent Test**: Can be tested by searching both repositories for CocoaPods references (Podfile, .podspec, pod commands) and verifying none remain, then building successfully. + +**Acceptance Scenarios**: + +1. **Given** the SDK repository after migration, **When** searching for CocoaPods artifacts (`Podfile`, `*.podspec`, `Pods/` references), **Then** none are found. +2. **Given** the test app repository after migration, **When** searching for CocoaPods artifacts, **Then** none are found (Podfile, Podfile.lock, Pods directory are removed). +3. **Given** the SDK's CLAUDE.md and documentation, **When** reviewing setup instructions, **Then** all references to `pod install` are removed or replaced with SPM equivalents. + +--- + +### User Story 4 - CI Pipeline Adapted for SPM (Priority: P2) + +CI workflows (`build_test_ios`, `integration_ios_tests`) are updated to work without CocoaPods. Build steps that previously ran `pod install` are removed or replaced. SPM dependency caching is configured for reasonable build times. + +**Why this priority**: CI must pass for any PR to merge. This is essential but follows from the migration itself being complete. + +**Independent Test**: Can be tested by triggering the CI pipeline on a branch with SPM changes and verifying all iOS-related jobs pass. + +**Acceptance Scenarios**: + +1. **Given** the CI workflow for `build_test_ios`, **When** the job runs, **Then** it builds successfully without any `pod install` step. +2. **Given** the CI workflow for `integration_ios_tests`, **When** the job runs against the migrated test app, **Then** integration tests pass with SPM dependency resolution. + +--- + +### Edge Cases + +- What happens when a developer has an existing project with cached CocoaPods artifacts (Pods/ directory, Podfile.lock) from a prior SDK version and upgrades? +- How does the migration behave when the developer's Xcode version does not support the minimum SPM features required? +- What happens if the OptimizelySwiftSDK SPM package URL or version tag format differs from the CocoaPods version specifier? +- How does the SDK handle a scenario where Flutter's own SPM migration tooling is partially adopted (mixed CocoaPods/SPM state during Flutter's transition period)? + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The SDK MUST declare its iOS native dependency (OptimizelySwiftSDK) using Swift Package Manager configuration instead of a CocoaPods podspec. +- **FR-002**: The SDK MUST use Flutter's official SPM integration mechanism (Package.swift in the plugin's iOS directory) to declare the native dependency. +- **FR-003**: The SDK MUST specify the OptimizelySwiftSDK version using exact pinning (`.exact("5.4.2")`) in the SPM package dependency, matching the version previously pinned in the podspec. +- **FR-004**: The SDK MUST remove the `optimizely_flutter_sdk.podspec` file from the `ios/` directory. This is a clean cut — no dual CocoaPods/SPM support period. +- **FR-005**: The SDK MUST maintain all existing iOS functionality (MethodChannel handlers, logger bridge, type encoding) without any behavioral changes. +- **FR-006**: The test app MUST remove its Podfile, Podfile.lock, and Pods directory and rely solely on SPM for iOS dependency resolution. +- **FR-007**: The SDK's `pubspec.yaml` plugin declaration for iOS MUST remain compatible with Flutter's SPM plugin system. +- **FR-008**: All CI workflows involving iOS builds MUST be updated to remove CocoaPods steps and work with SPM. +- **FR-009**: The SDK documentation (README.md, CLAUDE.md) MUST be updated to remove CocoaPods references and reflect SPM-based setup instructions. +- **FR-010**: The SDK MUST retain iOS 10.0 as its minimum deployment target. The test app maintains its own minimum of iOS 13.0. + +- **FR-011**: All existing SDK unit tests (`flutter test`) MUST pass with zero failures after the migration — no test regressions are acceptable. +- **FR-012**: All test app end-to-end test cases MUST pass with zero failures after the migration on both Android and iOS platforms. +- **FR-013**: All five CI pipeline jobs MUST pass on the migration PR: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. The migration MUST NOT break any existing CI workflow. + +### Key Entities + +- **Package.swift**: SPM package manifest that replaces the podspec, declaring the OptimizelySwiftSDK dependency and linking the plugin's Swift source files. +- **Plugin Registration**: The mechanism by which Flutter discovers and loads the native iOS plugin, which must work through SPM instead of CocoaPods. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: A fresh Flutter project depending on `optimizely_flutter_sdk` builds for iOS successfully without CocoaPods installed on the build machine. +- **SC-002**: All existing SDK unit tests (`flutter test`) pass with zero failures — no test regressions. +- **SC-003**: All five CI pipeline jobs pass on the migration PR: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. +- **SC-004**: All test app end-to-end test cases pass on both Android and iOS platforms with zero failures. +- **SC-005**: Zero CocoaPods-specific files remain in either the SDK or test app repositories after migration. +- **SC-006**: Developer setup time for iOS does not increase — no new manual steps are required beyond `flutter pub get` and `flutter build ios`. + +## Clarifications + +### Session 2026-08-20 + +- Q: What should the minimum iOS deployment target be after the SPM migration? → A: SDK keeps iOS 10.0; test app keeps iOS 13.0 (independent targets). +- Q: Should the SDK support both CocoaPods and SPM during a transition period? → A: Clean cut — CocoaPods removed entirely in this release, no dual-support period. +- Q: How should the OptimizelySwiftSDK version be pinned in SPM Package.swift? → A: Exact version pinning (`.exact("5.4.2")`), matching current podspec behavior. + +## Assumptions + +- The OptimizelySwiftSDK already publishes SPM-compatible releases (it does — the library supports SPM via its Package.swift). +- Flutter's SPM integration for plugins is stable enough for production use (Flutter has been shipping SPM support and is actively deprecating CocoaPods). +- The SDK retains iOS 10.0 as its minimum deployment target; the test app uses iOS 13.0. These are independent and intentional. +- The example app (`example/`) within the SDK repo, if it has iOS-specific CocoaPods setup, will also be migrated as part of this effort. +- macOS plugin platform (declared but experimental in pubspec.yaml) is out of scope for this migration unless it shares the same CocoaPods configuration. +- The `optimizely-flutter-testapp` repository is accessible and modifiable as part of this migration effort. From 5cff7f02d30034d72755139f1be9448230dc61a5 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 20 Aug 2026 18:27:19 +0600 Subject: [PATCH 02/10] docs: add implementation plan for CocoaPods to SPM migration 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) --- .../data-model.md | 67 +++++++++ specs/001-cocoapods-to-spm-migration/plan.md | 138 ++++++++++++++++++ .../quickstart.md | 122 ++++++++++++++++ .../research.md | 113 ++++++++++++++ 4 files changed, 440 insertions(+) create mode 100644 specs/001-cocoapods-to-spm-migration/data-model.md create mode 100644 specs/001-cocoapods-to-spm-migration/plan.md create mode 100644 specs/001-cocoapods-to-spm-migration/quickstart.md create mode 100644 specs/001-cocoapods-to-spm-migration/research.md diff --git a/specs/001-cocoapods-to-spm-migration/data-model.md b/specs/001-cocoapods-to-spm-migration/data-model.md new file mode 100644 index 0000000..af75f88 --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/data-model.md @@ -0,0 +1,67 @@ +# Data Model: CocoaPods to SPM Migration + +**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) + +This is an infrastructure migration — no new data entities, attributes, or state transitions are introduced. The SDK's existing data models (`lib/src/data_objects/`) are unchanged. + +## File Structure Changes + +The migration changes the physical layout of iOS source files, not data models. + +### Before (CocoaPods) + +| Path | Role | +|------|------| +| `ios/optimizely_flutter_sdk.podspec` | Dependency manifest (CocoaPods) | +| `ios/Classes/*.swift` | Plugin Swift sources | +| `ios/Classes/HelperClasses/*.swift` | Helper Swift sources | +| `ios/Classes/*.h`, `*.m` | ObjC bridging for plugin registration | +| `ios/Assets/` | Empty assets directory | + +### After (SPM) + +| Path | Role | +|------|------| +| `ios/optimizely_flutter_sdk/Package.swift` | Dependency manifest (SPM) | +| `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/*.swift` | All plugin Swift sources (flattened) | + +### Removed + +| Path | Reason | +|------|--------| +| `ios/optimizely_flutter_sdk.podspec` | Replaced by Package.swift | +| `ios/Classes/` | Sources moved to SPM layout | +| `ios/Assets/` | Empty, not needed by SPM | +| `ios/Classes/OptimizelyFlutterSdkPlugin.h` | ObjC bridge — evaluate for removal | +| `ios/Classes/OptimizelyFlutterSdkPlugin.m` | ObjC bridge — evaluate for removal | + +## Configuration Changes + +### SDK (`pubspec.yaml`) + +No changes to the `flutter.plugin.platforms.ios` section required — Flutter detects SPM support by the presence of Package.swift. + +### Test App + +| File | Change | +|------|--------| +| `ios/Podfile` | Remove | +| `ios/Podfile.lock` | Remove | +| `ios/Pods/` | Remove | + +### Example App (`example/`) + +| File | Change | +|------|--------| +| `example/ios/Podfile` | Remove | +| `example/ios/Podfile.lock` | Remove | +| `example/ios/Pods/` | Remove | + +## Dependency Graph Change + +``` +Before: pubspec.yaml → podspec → CocoaPods → OptimizelySwiftSDK (pod) +After: pubspec.yaml → Package.swift → SPM → OptimizelySwiftSDK (SPM package) +``` + +The Dart layer, MethodChannel bridge, and Android layer are completely unaffected. diff --git a/specs/001-cocoapods-to-spm-migration/plan.md b/specs/001-cocoapods-to-spm-migration/plan.md new file mode 100644 index 0000000..c222c86 --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/plan.md @@ -0,0 +1,138 @@ +# Implementation Plan: CocoaPods to SPM Migration + +**Branch**: `001-cocoapods-to-spm-migration` | **Date**: 2026-08-20 | **Spec**: [spec.md](spec.md) + +**Input**: Feature specification from `specs/001-cocoapods-to-spm-migration/spec.md` + +## Summary + +Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to Swift Package Manager. This is a clean cut — no dual-support period. The SDK plugin will declare its OptimizelySwiftSDK dependency via a `Package.swift` file following Flutter's official SPM plugin structure. Source files move from `ios/Classes/` to the SPM-standard layout. The example app, test app, CI workflows, and documentation are all updated to remove CocoaPods references. + +## Technical Context + +**Language/Version**: Dart >=2.16.2 <4.0.0, Swift 5.0+, Java/Kotlin 2.1.0 + +**Primary Dependencies**: Flutter SDK (stable), OptimizelySwiftSDK 5.4.2 (via SPM), android-sdk (unchanged via Gradle) + +**Storage**: N/A + +**Testing**: `flutter test` (Dart unit tests), `flutter test integration_test` (e2e via test app) + +**Target Platform**: iOS 10.0+ (SDK), iOS 13.0 (test app), Android API 21+ (unchanged) + +**Project Type**: Flutter plugin (cross-platform library wrapping native SDKs) + +**Performance Goals**: N/A — infrastructure migration, no behavioral changes + +**Constraints**: Must be backward compatible at the Dart API level. iOS minimum stays at 10.0. All existing tests must pass. + +**Scale/Scope**: 2 repositories (SDK + test app), ~7 Swift source files to move, 2 CI workflows to update + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|-----------|--------|-------| +| I. Bridge Pattern Integrity | PASS | No changes to the three-layer architecture. Only the dependency resolution mechanism changes. | +| II. Response Object Pattern | PASS | No API changes — all methods continue returning BaseResponse derivatives. | +| III. Platform Parity | PASS | Android layer is completely unaffected. iOS functionality is preserved. | +| IV. Type Safety Across Bridge | PASS | No changes to type encoding or `Utils.convertToTypedMap()`. | +| V. Thread Safety | PASS | No changes to main-thread dispatch patterns. | +| VI. Multi-Instance State Isolation | PASS | No changes to state management. | +| VII. Version Synchronisation | PASS | No version bump in this migration (infra change). | +| VIII. Native SDK Version Pinning | PASS | Version pinning preserved: `.exact("5.4.2")` in Package.swift matches podspec. | +| IX. CMAB & Async Decide | PASS | No changes to CMAB functionality. | +| X. ODP Integration | PASS | No changes to ODP functionality. | +| XI. Event Batching Configuration | PASS | No changes to event batching. | +| XII. Logger Bridge Architecture | PASS | Logger channel and bridge files are moved but unchanged. | + +**Gate Result**: ALL PASS — no violations. + +## Project Structure + +### Documentation (this feature) + +```text +specs/001-cocoapods-to-spm-migration/ +├── plan.md # This file +├── research.md # Phase 0 output — SPM integration research +├── data-model.md # Phase 1 output — file structure changes +├── quickstart.md # Phase 1 output — validation guide +└── tasks.md # Phase 2 output (created by /speckit-tasks) +``` + +### Source Code (repository root) + +```text +# SDK repo — iOS layer changes +ios/ +├── optimizely_flutter_sdk/ # NEW: SPM package directory +│ ├── Package.swift # NEW: SPM manifest +│ └── Sources/ +│ └── optimizely_flutter_sdk/ # NEW: SPM sources +│ ├── SwiftOptimizelyFlutterSdkPlugin.swift # MOVED from Classes/ +│ ├── OptimizelyFlutterLogger.swift # MOVED from Classes/ +│ ├── Constants.swift # MOVED from Classes/HelperClasses/ +│ ├── OptimizelyConfig+Extension.swift # MOVED from Classes/HelperClasses/ +│ └── Utils.swift # MOVED from Classes/HelperClasses/ +├── Classes/ # REMOVED (files moved to SPM layout) +├── Assets/ # REMOVED (empty) +└── optimizely_flutter_sdk.podspec # REMOVED (replaced by Package.swift) + +# SDK repo — example app +example/ios/ +├── Podfile # REMOVED +├── Podfile.lock # REMOVED +└── Pods/ # REMOVED + +# Test app repo +optimizely-flutter-testapp/ios/ +├── Podfile # REMOVED +├── Podfile.lock # REMOVED +└── Pods/ # REMOVED + +# Test app CI +optimizely-flutter-testapp/.github/workflows/ +└── ios.yml # MODIFIED: remove cocoapods install step + +# SDK CI +.github/workflows/ +└── flutter.yml # NO CHANGES needed (no pod commands) + +# Dart layer — NO CHANGES +# Android layer — NO CHANGES +``` + +**Structure Decision**: Only the `ios/` directory structure changes. All files move from the CocoaPods-style `ios/Classes/` to the SPM-style `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. The Dart and Android layers are untouched. + +### ObjC Bridging Files Decision + +The ObjC files (`OptimizelyFlutterSdkPlugin.h`, `OptimizelyFlutterSdkPlugin.m`) are a thin bridge for CocoaPods-style plugin registration. During implementation, determine if: +1. They can be removed entirely (pure Swift SPM registration) — preferred +2. They need to be kept in a separate clang target within Package.swift + +This is an implementation detail to resolve during the task execution phase. + +## Complexity Tracking + +No constitution violations — this section is empty. + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| (none) | — | — | + +## Research Artifacts + +See [research.md](research.md) for detailed findings on: +- Flutter plugin SPM integration structure +- OptimizelySwiftSDK SPM compatibility +- Source file migration approach +- Example and test app migration +- CI impact analysis + +## Key Risks + +1. **SPM version tag**: The podspec pins OptimizelySwiftSDK 5.4.2, but the git tag format on `swift-sdk` repo needs verification (could be `5.4.2` or `v5.4.2`). +2. **ObjC/Swift mixed target**: SPM doesn't support mixed ObjC/Swift in a single target. The ObjC bridging files need special handling. +3. **Flutter version requirement**: SPM plugin support requires Flutter 3.44+. Consumers on older Flutter versions will not be able to use this version of the SDK. diff --git a/specs/001-cocoapods-to-spm-migration/quickstart.md b/specs/001-cocoapods-to-spm-migration/quickstart.md new file mode 100644 index 0000000..0dee62d --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/quickstart.md @@ -0,0 +1,122 @@ +# Quickstart Validation Guide: CocoaPods to SPM Migration + +**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) + +## Prerequisites + +- Flutter SDK (stable channel, 3.44+) +- Xcode 15+ with iOS simulators +- No CocoaPods required (that's the point) +- Access to `optimizely-flutter-testapp` repo + +## Validation Scenario 1: SDK Plugin Builds via SPM + +**Goal**: Verify the SDK's Package.swift is correctly structured and the OptimizelySwiftSDK dependency resolves via SPM. + +```bash +# From SDK repo root +flutter clean +flutter pub get +cd example +flutter build ios --simulator --no-codesign +``` + +**Expected**: Build succeeds. Console shows SPM resolving `swift-sdk` package (not pod install). No CocoaPods warnings. + +## Validation Scenario 2: Unit Tests Pass + +**Goal**: Verify no test regressions from the migration. + +```bash +# From SDK repo root +flutter test +``` + +**Expected**: All existing tests pass with zero failures. Test count matches pre-migration count. + +## Validation Scenario 3: Lint Clean + +**Goal**: Verify no analysis issues introduced. + +```bash +flutter analyze +``` + +**Expected**: No issues found. + +## Validation Scenario 4: Test App Builds and Runs + +**Goal**: Verify the test app resolves the SDK via SPM and integration tests pass. + +```bash +# From optimizely-flutter-testapp repo +flutter clean +flutter pub get +flutter build ios --simulator --no-codesign +``` + +**Expected**: Build succeeds without CocoaPods. The SDK plugin is resolved via SPM. + +## Validation Scenario 5: Integration Tests Pass + +**Goal**: Verify end-to-end functionality on iOS simulator. + +```bash +# From optimizely-flutter-testapp repo +flutter test integration_test -d +``` + +**Expected**: All e2e test cases pass with zero failures. + +## Validation Scenario 6: CocoaPods Artifacts Removed + +**Goal**: Verify no CocoaPods remnants in either repo. + +```bash +# From SDK repo root +find . -name "Podfile" -o -name "Podfile.lock" -o -name "*.podspec" -o -name "Pods" -type d | grep -v ".git" + +# From test app repo root +find . -name "Podfile" -o -name "Podfile.lock" -o -name "Pods" -type d | grep -v ".git" +``` + +**Expected**: Both commands return empty output (no matches). + +## Validation Scenario 7: Clean Machine Build + +**Goal**: Verify a developer without CocoaPods can build the SDK. + +```bash +# Temporarily hide CocoaPods if installed +which pod && echo "CocoaPods present — test on clean env or rename binary" + +# Create a fresh Flutter project and add the SDK +flutter create spm_test_app +cd spm_test_app +# Add SDK dependency pointing to local path +flutter pub add optimizely_flutter_sdk --path /path/to/optimizely-flutter-sdk +flutter build ios --simulator --no-codesign +``` + +**Expected**: Build succeeds. No errors about missing CocoaPods or podspec. + +## Validation Scenario 8: CI Pipeline + +**Goal**: Verify all five CI jobs pass. + +Push the migration branch and create a PR against `master`. Monitor: + +1. `unit_test_coverage` — Dart tests + coverage +2. `build_test_android` — Android build (should be unaffected) +3. `build_test_ios` — iOS build via SPM +4. `integration_android_tests` — Android e2e (should be unaffected) +5. `integration_ios_tests` — iOS e2e via SPM + +**Expected**: All five jobs pass green. + +## Troubleshooting + +- **SPM resolution fails**: Verify the `swift-sdk` git tag for version 5.4.2 exists. Check Package.swift URL. +- **Mixed ObjC/Swift errors**: If ObjC bridging files were kept, ensure they're in a separate target or removed. +- **Xcode cache issues**: Run `flutter clean`, delete `~/Library/Developer/Xcode/DerivedData/`, rebuild. +- **Old Pods artifacts**: Delete `ios/Pods/`, `ios/Podfile.lock`, `example/ios/Pods/`, `example/ios/Podfile.lock`. diff --git a/specs/001-cocoapods-to-spm-migration/research.md b/specs/001-cocoapods-to-spm-migration/research.md new file mode 100644 index 0000000..6867bc0 --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/research.md @@ -0,0 +1,113 @@ +# Research: CocoaPods to SPM Migration + +**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) + +## 1. Flutter Plugin SPM Integration + +**Decision**: Use Flutter's official SPM plugin structure with `Package.swift` in `ios//`. + +**Rationale**: Flutter's official documentation defines the canonical directory layout and Package.swift structure for SPM-based plugins. This is the only supported approach for plugins that want to work with Flutter's SPM integration. + +**Key Findings**: + +- **Package.swift location**: `ios/optimizely_flutter_sdk/Package.swift` +- **Source files location**: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/` +- Source files must be moved from `ios/Classes/` to the new SPM sources directory +- Library product name replaces underscores with hyphens: `optimizely-flutter-sdk` +- `FlutterFramework` dependency with `path: "../FlutterFramework"` is mandatory — Flutter tooling provides this at build time +- The ObjC bridging files (`.h`, `.m`) are used for CocoaPods plugin registration; SPM plugins may handle registration differently + +**Package.swift Template**: +```swift +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "optimizely_flutter_sdk", + platforms: [.iOS("10.0")], + products: [ + .library(name: "optimizely-flutter-sdk", targets: ["optimizely_flutter_sdk"]) + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework"), + .package(url: "https://github.com/optimizely/swift-sdk.git", exact: "5.4.2") + ], + targets: [ + .target( + name: "optimizely_flutter_sdk", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework"), + .product(name: "Optimizely", package: "swift-sdk") + ] + ) + ] +) +``` + +**Alternatives Considered**: +- Dual CocoaPods+SPM support: Rejected by user (clean cut decision) +- Custom SPM layout: Rejected — must follow Flutter's canonical structure + +## 2. OptimizelySwiftSDK SPM Compatibility + +**Decision**: Use `https://github.com/optimizely/swift-sdk.git` with `.exact("5.4.2")` version pinning. + +**Rationale**: The OptimizelySwiftSDK has first-class SPM support. The SPM product name is `Optimizely` (from the `swift-sdk` package). Exact version pinning matches the current podspec strategy and aligns with Constitution Principle VIII. + +**Key Findings**: +- SPM repo URL: `https://github.com/optimizely/swift-sdk.git` +- Package name: `swift-sdk` +- Product/library name: `Optimizely` +- Supported platforms: iOS 10.0+, tvOS 10.0+, macOS 10.14+, watchOS 3.0+ +- swift-tools-version: 5.3 + +**Verification Required**: Confirm that git tag `5.4.2` exists on the `swift-sdk` repo. If the tag format is `v5.4.2`, the exact version string may need adjustment. + +**Alternatives Considered**: +- `.upToNextMinor(from: "5.4.2")`: Rejected — user chose exact pinning for stability +- `.upToNextMajor(from: "5.4.2")`: Rejected — too permissive for a bridge SDK + +## 3. Source File Migration + +**Decision**: Move all Swift source files from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. Evaluate ObjC bridging files for removal or migration. + +**Rationale**: SPM requires sources in a specific directory structure. The current `ios/Classes/` layout is CocoaPods-specific. + +**Current Files**: +- `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` — main plugin +- `ios/Classes/OptimizelyFlutterLogger.swift` — logger bridge +- `ios/Classes/HelperClasses/Constants.swift` — constants +- `ios/Classes/HelperClasses/OptimizelyConfig+Extension.swift` — config extension +- `ios/Classes/HelperClasses/Utils.swift` — utilities +- `ios/Classes/OptimizelyFlutterSdkPlugin.h` — ObjC header (bridging) +- `ios/Classes/OptimizelyFlutterSdkPlugin.m` — ObjC implementation (bridging) + +**ObjC Bridging Files**: The `.h` and `.m` files are a thin bridge that calls `SwiftOptimizelyFlutterSdkPlugin.registerWithRegistrar()`. SPM does not support mixed ObjC/Swift in a single target without a separate clang target. Options: +1. Convert to pure Swift registration (preferred for clean cut) +2. Use separate SPM targets for ObjC and Swift (adds complexity) + +## 4. Example App Migration + +**Decision**: Remove CocoaPods artifacts from `example/ios/` (Podfile, Podfile.lock, Pods/). Flutter tooling will resolve the SDK plugin via SPM automatically. + +**Rationale**: The example app follows the same pattern as any consuming Flutter app. Once the plugin has a Package.swift, Flutter resolves it via SPM. + +**Current State**: Example app has `Podfile` (platform :ios, '11.0'), `Podfile.lock`, and `Pods/` directory. + +## 5. Test App Migration + +**Decision**: Remove CocoaPods artifacts from `optimizely-flutter-testapp/ios/` and update CI workflow to remove CocoaPods installation step. + +**Rationale**: The test app is the SDK's end-to-end validation layer. Its CI (`ios.yml`) currently runs `brew install cocoapods` + `pod repo update` — these steps must be removed. + +**Current State**: Test app has `Podfile` (platform :ios, '13.0'), `Podfile.lock`, `Pods/` directory. iOS CI has explicit CocoaPods installation. + +**CI Change Required**: In `optimizely-flutter-testapp/.github/workflows/ios.yml`, remove or replace the "Install Xcode Dependencies" step that runs `brew install cocoapods` and `pod repo update`. + +## 6. SDK CI Impact + +**Decision**: SDK CI workflows (`flutter.yml`) require no changes to CocoaPods steps. + +**Rationale**: The SDK CI does not have explicit `pod install` calls — Flutter tooling handles dependency resolution. Integration test jobs just trigger the test app repo. The `build_test_ios` and `unit_test_coverage` jobs only run `flutter pub get` + `flutter test`. + +**Note**: While the SDK CI doesn't need CocoaPods step changes, the integration test jobs trigger the test app, which does need changes (covered in section 5). From 7117c5d9ffecc4ca644f650d1ea78dca04ec56e0 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 20 Aug 2026 20:19:50 +0600 Subject: [PATCH 03/10] docs: add tasks and update spec artifacts for CocoaPods to SPM migration 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) --- .../checklists/requirements.md | 7 +- .../data-model.md | 68 +++--- specs/001-cocoapods-to-spm-migration/plan.md | 85 +++---- .../quickstart.md | 107 +++++---- .../research.md | 108 ++++++--- specs/001-cocoapods-to-spm-migration/spec.md | 121 +++++----- specs/001-cocoapods-to-spm-migration/tasks.md | 220 ++++++++++++++++++ 7 files changed, 511 insertions(+), 205 deletions(-) create mode 100644 specs/001-cocoapods-to-spm-migration/tasks.md diff --git a/specs/001-cocoapods-to-spm-migration/checklists/requirements.md b/specs/001-cocoapods-to-spm-migration/checklists/requirements.md index 89a1e2a..e00fbcf 100644 --- a/specs/001-cocoapods-to-spm-migration/checklists/requirements.md +++ b/specs/001-cocoapods-to-spm-migration/checklists/requirements.md @@ -1,4 +1,4 @@ -# Specification Quality Checklist: CocoaPods to SPM Migration +# Specification Quality Checklist: Add SPM Support with CocoaPods Backward Compatibility **Purpose**: Validate specification completeness and quality before proceeding to planning **Created**: 2026-08-20 @@ -32,6 +32,7 @@ ## Notes - All items pass validation. Spec is ready for `/speckit-plan`. -- FR-010 clarified: SDK keeps iOS 10.0, test app keeps iOS 13.0 (resolved during clarification). +- Spec revised on 2026-08-20: changed from "clean cut" to dual CocoaPods + SPM support. +- FR-010 clarified: SDK keeps iOS 10.0, test app keeps iOS 13.0. - The spec references both the SDK repo and the test app repo as in-scope. -- Re-validated after clarification session on 2026-08-20 (3 questions answered). +- Re-validated after spec revision (dual support) on 2026-08-20. diff --git a/specs/001-cocoapods-to-spm-migration/data-model.md b/specs/001-cocoapods-to-spm-migration/data-model.md index af75f88..60b9462 100644 --- a/specs/001-cocoapods-to-spm-migration/data-model.md +++ b/specs/001-cocoapods-to-spm-migration/data-model.md @@ -1,14 +1,14 @@ -# Data Model: CocoaPods to SPM Migration +# Data Model: Add SPM Support with CocoaPods Backward Compatibility -**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) +**Date**: 2026-08-20 (Revised) | **Feature**: [spec.md](spec.md) -This is an infrastructure migration — no new data entities, attributes, or state transitions are introduced. The SDK's existing data models (`lib/src/data_objects/`) are unchanged. +This is an infrastructure change — no new data entities, attributes, or state transitions are introduced. The SDK's existing data models (`lib/src/data_objects/`) are unchanged. ## File Structure Changes -The migration changes the physical layout of iOS source files, not data models. +The migration changes the physical layout of iOS source files. Both CocoaPods and SPM reference the same files in the new location. -### Before (CocoaPods) +### Before | Path | Role | |------|------| @@ -18,50 +18,64 @@ The migration changes the physical layout of iOS source files, not data models. | `ios/Classes/*.h`, `*.m` | ObjC bridging for plugin registration | | `ios/Assets/` | Empty assets directory | -### After (SPM) +### After | Path | Role | |------|------| -| `ios/optimizely_flutter_sdk/Package.swift` | Dependency manifest (SPM) | +| `ios/optimizely_flutter_sdk.podspec` | Dependency manifest (CocoaPods) — **updated `source_files` path** | +| `ios/optimizely_flutter_sdk/Package.swift` | Dependency manifest (SPM) — **NEW** | | `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/*.swift` | All plugin Swift sources (flattened) | +| `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m` | ObjC bridge implementation | +| `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h` | ObjC public header | ### Removed | Path | Reason | |------|--------| -| `ios/optimizely_flutter_sdk.podspec` | Replaced by Package.swift | | `ios/Classes/` | Sources moved to SPM layout | -| `ios/Assets/` | Empty, not needed by SPM | -| `ios/Classes/OptimizelyFlutterSdkPlugin.h` | ObjC bridge — evaluate for removal | -| `ios/Classes/OptimizelyFlutterSdkPlugin.m` | ObjC bridge — evaluate for removal | +| `ios/Assets/` | Empty, not needed | + +### Retained (Updated) + +| Path | Change | +|------|--------| +| `ios/optimizely_flutter_sdk.podspec` | `source_files` path updated to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*'` | ## Configuration Changes ### SDK (`pubspec.yaml`) -No changes to the `flutter.plugin.platforms.ios` section required — Flutter detects SPM support by the presence of Package.swift. +No changes needed — Flutter detects SPM support by the presence of Package.swift and falls back to CocoaPods if SPM is not available. -### Test App +### Example App (`example/`) -| File | Change | -|------|--------| -| `ios/Podfile` | Remove | -| `ios/Podfile.lock` | Remove | -| `ios/Pods/` | Remove | +No changes — keeps existing Podfile for CocoaPods backward compatibility. Flutter tooling selects the appropriate mechanism. -### Example App (`example/`) +### Test App -| File | Change | -|------|--------| -| `example/ios/Podfile` | Remove | -| `example/ios/Podfile.lock` | Remove | -| `example/ios/Pods/` | Remove | +The test app needs CI workflow updates and SPM configuration added to its iOS project. The existing Podfile is kept for CocoaPods fallback. CI workflows (`ios.yml`) are updated for SPM-compatible build steps. -## Dependency Graph Change +## Dependency Graph ``` -Before: pubspec.yaml → podspec → CocoaPods → OptimizelySwiftSDK (pod) -After: pubspec.yaml → Package.swift → SPM → OptimizelySwiftSDK (SPM package) +SPM Path: pubspec.yaml → Package.swift → SPM → OptimizelySwiftSDK (SPM package) +CocoaPods: pubspec.yaml → podspec → CocoaPods → OptimizelySwiftSDK (pod) ``` +Both paths coexist. Flutter selects which path to use based on: +- Flutter version (3.44+ defaults to SPM) +- `--enable-swift-package-manager` / `--no-enable-swift-package-manager` flag +- Presence of Package.swift in the plugin + The Dart layer, MethodChannel bridge, and Android layer are completely unaffected. + +## Version Synchronization + +Two files now declare the OptimizelySwiftSDK version: + +| File | Format | Current | +|------|--------|---------| +| `ios/optimizely_flutter_sdk.podspec` | `s.dependency 'OptimizelySwiftSDK', '5.4.2'` | 5.4.2 | +| `ios/optimizely_flutter_sdk/Package.swift` | `.package(url: "...", exact: "5.4.2")` | 5.4.2 (new) | + +Both MUST be updated simultaneously when bumping native SDK versions (Constitution Principle VIII). diff --git a/specs/001-cocoapods-to-spm-migration/plan.md b/specs/001-cocoapods-to-spm-migration/plan.md index c222c86..86af321 100644 --- a/specs/001-cocoapods-to-spm-migration/plan.md +++ b/specs/001-cocoapods-to-spm-migration/plan.md @@ -1,18 +1,18 @@ -# Implementation Plan: CocoaPods to SPM Migration +# Implementation Plan: Add SPM Support with CocoaPods Backward Compatibility -**Branch**: `001-cocoapods-to-spm-migration` | **Date**: 2026-08-20 | **Spec**: [spec.md](spec.md) +**Branch**: `001-cocoapods-to-spm-migration` | **Date**: 2026-08-20 (Revised) | **Spec**: [spec.md](spec.md) **Input**: Feature specification from `specs/001-cocoapods-to-spm-migration/spec.md` ## Summary -Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to Swift Package Manager. This is a clean cut — no dual-support period. The SDK plugin will declare its OptimizelySwiftSDK dependency via a `Package.swift` file following Flutter's official SPM plugin structure. Source files move from `ios/Classes/` to the SPM-standard layout. The example app, test app, CI workflows, and documentation are all updated to remove CocoaPods references. +Add Swift Package Manager support to the Optimizely Flutter SDK's iOS plugin while retaining CocoaPods backward compatibility. Source files move from `ios/Classes/` to the SPM-standard layout at `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. Both the new Package.swift and the existing podspec (with updated source paths) reference the same files. Flutter selects the dependency mechanism based on version and configuration. The Dart and Android layers are unaffected. ## Technical Context **Language/Version**: Dart >=2.16.2 <4.0.0, Swift 5.0+, Java/Kotlin 2.1.0 -**Primary Dependencies**: Flutter SDK (stable), OptimizelySwiftSDK 5.4.2 (via SPM), android-sdk (unchanged via Gradle) +**Primary Dependencies**: Flutter SDK (stable), OptimizelySwiftSDK 5.4.2 (via SPM AND CocoaPods), android-sdk (unchanged via Gradle) **Storage**: N/A @@ -22,11 +22,11 @@ Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to **Project Type**: Flutter plugin (cross-platform library wrapping native SDKs) -**Performance Goals**: N/A — infrastructure migration, no behavioral changes +**Performance Goals**: N/A — infrastructure change, no behavioral changes -**Constraints**: Must be backward compatible at the Dart API level. iOS minimum stays at 10.0. All existing tests must pass. +**Constraints**: Must maintain backward compatibility with CocoaPods. iOS minimum stays at 10.0. All existing tests must pass. Both dependency mechanisms must pin the same OptimizelySwiftSDK version. -**Scale/Scope**: 2 repositories (SDK + test app), ~7 Swift source files to move, 2 CI workflows to update +**Scale/Scope**: 2 repositories (SDK + test app), ~7 Swift/ObjC source files to move, 1 podspec to update, 1 Package.swift to create ## Constitution Check @@ -40,8 +40,8 @@ Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to | IV. Type Safety Across Bridge | PASS | No changes to type encoding or `Utils.convertToTypedMap()`. | | V. Thread Safety | PASS | No changes to main-thread dispatch patterns. | | VI. Multi-Instance State Isolation | PASS | No changes to state management. | -| VII. Version Synchronisation | PASS | No version bump in this migration (infra change). | -| VIII. Native SDK Version Pinning | PASS | Version pinning preserved: `.exact("5.4.2")` in Package.swift matches podspec. | +| VII. Version Synchronisation | PASS | No version bump for this change (infra). | +| VIII. Native SDK Version Pinning | PASS | Version pinning preserved in BOTH files: `.exact("5.4.2")` in Package.swift AND `'5.4.2'` in podspec. Source-of-truth files now include both. | | IX. CMAB & Async Decide | PASS | No changes to CMAB functionality. | | X. ODP Integration | PASS | No changes to ODP functionality. | | XI. Event Batching Configuration | PASS | No changes to event batching. | @@ -49,6 +49,8 @@ Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to **Gate Result**: ALL PASS — no violations. +**Post-Design Re-Check**: ALL PASS — dual support adds a version synchronization requirement across two files, which strengthens Principle VIII rather than violating it. FR-014 (CI version drift check) provides automated enforcement. + ## Project Structure ### Documentation (this feature) @@ -56,7 +58,7 @@ Migrate the Optimizely Flutter SDK's iOS dependency management from CocoaPods to ```text specs/001-cocoapods-to-spm-migration/ ├── plan.md # This file -├── research.md # Phase 0 output — SPM integration research +├── research.md # Phase 0 output — dual support research ├── data-model.md # Phase 1 output — file structure changes ├── quickstart.md # Phase 1 output — validation guide └── tasks.md # Phase 2 output (created by /speckit-tasks) @@ -67,52 +69,48 @@ specs/001-cocoapods-to-spm-migration/ ```text # SDK repo — iOS layer changes ios/ +├── optimizely_flutter_sdk.podspec # UPDATED: source_files path changed ├── optimizely_flutter_sdk/ # NEW: SPM package directory │ ├── Package.swift # NEW: SPM manifest │ └── Sources/ │ └── optimizely_flutter_sdk/ # NEW: SPM sources -│ ├── SwiftOptimizelyFlutterSdkPlugin.swift # MOVED from Classes/ -│ ├── OptimizelyFlutterLogger.swift # MOVED from Classes/ -│ ├── Constants.swift # MOVED from Classes/HelperClasses/ -│ ├── OptimizelyConfig+Extension.swift # MOVED from Classes/HelperClasses/ -│ └── Utils.swift # MOVED from Classes/HelperClasses/ +│ ├── include/ +│ │ └── optimizely_flutter_sdk/ +│ │ └── OptimizelyFlutterSdkPlugin.h # MOVED from Classes/ +│ ├── OptimizelyFlutterSdkPlugin.m # MOVED from Classes/ (import path updated) +│ ├── SwiftOptimizelyFlutterSdkPlugin.swift # MOVED from Classes/ +│ ├── OptimizelyFlutterLogger.swift # MOVED from Classes/ +│ ├── Constants.swift # MOVED from Classes/HelperClasses/ +│ ├── OptimizelyConfig+Extension.swift # MOVED from Classes/HelperClasses/ +│ └── Utils.swift # MOVED from Classes/HelperClasses/ ├── Classes/ # REMOVED (files moved to SPM layout) -├── Assets/ # REMOVED (empty) -└── optimizely_flutter_sdk.podspec # REMOVED (replaced by Package.swift) +└── Assets/ # REMOVED (empty) -# SDK repo — example app +# Example app — NO CHANGES (keeps Podfile for CocoaPods backward compat) example/ios/ -├── Podfile # REMOVED -├── Podfile.lock # REMOVED -└── Pods/ # REMOVED +├── Podfile # KEPT +├── Podfile.lock # KEPT (regenerated on next pod install) +└── Pods/ # KEPT -# Test app repo +# Test app — CI updates + SPM configuration added optimizely-flutter-testapp/ios/ -├── Podfile # REMOVED -├── Podfile.lock # REMOVED -└── Pods/ # REMOVED +├── Podfile # KEPT for CocoaPods fallback +├── Podfile.lock # KEPT +└── Pods/ # KEPT # Test app CI optimizely-flutter-testapp/.github/workflows/ -└── ios.yml # MODIFIED: remove cocoapods install step +└── ios.yml # UPDATED: SPM-compatible build steps # SDK CI .github/workflows/ -└── flutter.yml # NO CHANGES needed (no pod commands) +└── flutter.yml # UPDATED: version drift check step added (FR-014) # Dart layer — NO CHANGES # Android layer — NO CHANGES ``` -**Structure Decision**: Only the `ios/` directory structure changes. All files move from the CocoaPods-style `ios/Classes/` to the SPM-style `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. The Dart and Android layers are untouched. - -### ObjC Bridging Files Decision - -The ObjC files (`OptimizelyFlutterSdkPlugin.h`, `OptimizelyFlutterSdkPlugin.m`) are a thin bridge for CocoaPods-style plugin registration. During implementation, determine if: -1. They can be removed entirely (pure Swift SPM registration) — preferred -2. They need to be kept in a separate clang target within Package.swift - -This is an implementation detail to resolve during the task execution phase. +**Structure Decision**: Source files move from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/` (SPM-standard layout). The podspec's `source_files` is updated to point to the new location. Both dependency mechanisms share the same physical files. ObjC header goes into an `include/` subdirectory per SPM convention. ## Complexity Tracking @@ -125,14 +123,17 @@ No constitution violations — this section is empty. ## Research Artifacts See [research.md](research.md) for detailed findings on: -- Flutter plugin SPM integration structure +- Flutter plugin dual SPM + CocoaPods support (official recommendation) +- Source file layout for dual support +- ObjC bridging file handling in SPM +- Podspec path updates - OptimizelySwiftSDK SPM compatibility -- Source file migration approach -- Example and test app migration -- CI impact analysis +- Version synchronization requirements ## Key Risks 1. **SPM version tag**: The podspec pins OptimizelySwiftSDK 5.4.2, but the git tag format on `swift-sdk` repo needs verification (could be `5.4.2` or `v5.4.2`). -2. **ObjC/Swift mixed target**: SPM doesn't support mixed ObjC/Swift in a single target. The ObjC bridging files need special handling. -3. **Flutter version requirement**: SPM plugin support requires Flutter 3.44+. Consumers on older Flutter versions will not be able to use this version of the SDK. +2. **ObjC header search paths**: SPM requires headers in an `include/` subdirectory with `cSettings`. This must be validated against both SPM and CocoaPods builds. +3. **Podspec source path**: After moving files, `pod lib lint` must pass to confirm CocoaPods can still find all sources. Note: the current glob is `'Classes/**/*'` (all types), so the updated path must preserve this pattern. +4. **Version drift**: Two files now declare the native SDK version. **Mitigated by FR-014**: a CI validation check MUST fail the build if versions diverge. +5. **Mixed CocoaPods/SPM state**: Resolved — Flutter's tooling handles resolution automatically. No SDK-level detection or intervention needed. diff --git a/specs/001-cocoapods-to-spm-migration/quickstart.md b/specs/001-cocoapods-to-spm-migration/quickstart.md index 0dee62d..9f7bd7c 100644 --- a/specs/001-cocoapods-to-spm-migration/quickstart.md +++ b/specs/001-cocoapods-to-spm-migration/quickstart.md @@ -1,40 +1,67 @@ -# Quickstart Validation Guide: CocoaPods to SPM Migration +# Quickstart Validation Guide: Add SPM Support with CocoaPods Backward Compatibility -**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) +**Date**: 2026-08-20 (Revised) | **Feature**: [spec.md](spec.md) ## Prerequisites -- Flutter SDK (stable channel, 3.44+) +- Flutter SDK (stable channel) - Xcode 15+ with iOS simulators -- No CocoaPods required (that's the point) +- CocoaPods installed (for backward compatibility validation) - Access to `optimizely-flutter-testapp` repo ## Validation Scenario 1: SDK Plugin Builds via SPM -**Goal**: Verify the SDK's Package.swift is correctly structured and the OptimizelySwiftSDK dependency resolves via SPM. +**Goal**: Verify the SDK's Package.swift is correctly structured and OptimizelySwiftSDK resolves via SPM. ```bash -# From SDK repo root +# From SDK repo root (with Flutter 3.44+ or SPM enabled) +flutter config --enable-swift-package-manager +flutter clean +flutter pub get +cd example +flutter build ios --simulator --no-codesign +``` + +**Expected**: Build succeeds. Console shows SPM resolving `swift-sdk` package. No CocoaPods warnings. + +## Validation Scenario 2: SDK Plugin Builds via CocoaPods + +**Goal**: Verify the podspec still works with updated source file paths. + +```bash +# From SDK repo root (with CocoaPods path) +flutter config --no-enable-swift-package-manager flutter clean flutter pub get cd example flutter build ios --simulator --no-codesign ``` -**Expected**: Build succeeds. Console shows SPM resolving `swift-sdk` package (not pod install). No CocoaPods warnings. +**Expected**: Build succeeds via CocoaPods. `pod install` finds source files at the new path. No missing file errors. + +## Validation Scenario 3: Podspec Lint + +**Goal**: Verify the podspec is valid after source path changes. + +```bash +cd ios +pod lib lint optimizely_flutter_sdk.podspec --allow-warnings +``` + +**Expected**: Lint passes. Source files found at updated paths. -## Validation Scenario 2: Unit Tests Pass +## Validation Scenario 4: Unit Tests Pass -**Goal**: Verify no test regressions from the migration. +**Goal**: Verify no test regressions from the file restructuring. ```bash # From SDK repo root flutter test ``` -**Expected**: All existing tests pass with zero failures. Test count matches pre-migration count. +**Expected**: All existing tests pass with zero failures. -## Validation Scenario 3: Lint Clean +## Validation Scenario 5: Lint Clean **Goal**: Verify no analysis issues introduced. @@ -44,20 +71,21 @@ flutter analyze **Expected**: No issues found. -## Validation Scenario 4: Test App Builds and Runs +## Validation Scenario 6: Test App Builds via SPM **Goal**: Verify the test app resolves the SDK via SPM and integration tests pass. ```bash -# From optimizely-flutter-testapp repo +# From optimizely-flutter-testapp repo (with SPM enabled) +flutter config --enable-swift-package-manager flutter clean flutter pub get flutter build ios --simulator --no-codesign ``` -**Expected**: Build succeeds without CocoaPods. The SDK plugin is resolved via SPM. +**Expected**: Build succeeds with SPM dependency resolution. -## Validation Scenario 5: Integration Tests Pass +## Validation Scenario 7: Test App Integration Tests **Goal**: Verify end-to-end functionality on iOS simulator. @@ -68,55 +96,50 @@ flutter test integration_test -d **Expected**: All e2e test cases pass with zero failures. -## Validation Scenario 6: CocoaPods Artifacts Removed +## Validation Scenario 8: Version Consistency -**Goal**: Verify no CocoaPods remnants in either repo. +**Goal**: Verify both dependency files declare the same OptimizelySwiftSDK version. ```bash # From SDK repo root -find . -name "Podfile" -o -name "Podfile.lock" -o -name "*.podspec" -o -name "Pods" -type d | grep -v ".git" - -# From test app repo root -find . -name "Podfile" -o -name "Podfile.lock" -o -name "Pods" -type d | grep -v ".git" +grep "OptimizelySwiftSDK" ios/optimizely_flutter_sdk.podspec +grep "exact:" ios/optimizely_flutter_sdk/Package.swift ``` -**Expected**: Both commands return empty output (no matches). +**Expected**: Both show version `5.4.2` (or whatever the current pinned version is). -## Validation Scenario 7: Clean Machine Build +## Validation Scenario 9: CI Version Drift Check (FR-014) -**Goal**: Verify a developer without CocoaPods can build the SDK. +**Goal**: Verify the CI validation step detects version mismatches between Package.swift and podspec. ```bash -# Temporarily hide CocoaPods if installed -which pod && echo "CocoaPods present — test on clean env or rename binary" - -# Create a fresh Flutter project and add the SDK -flutter create spm_test_app -cd spm_test_app -# Add SDK dependency pointing to local path -flutter pub add optimizely_flutter_sdk --path /path/to/optimizely-flutter-sdk -flutter build ios --simulator --no-codesign +# Extract versions and compare +PODSPEC_VER=$(grep "OptimizelySwiftSDK" ios/optimizely_flutter_sdk.podspec | grep -oE "'[0-9]+\.[0-9]+\.[0-9]+'" | tr -d "'") +SPM_VER=$(grep 'exact:' ios/optimizely_flutter_sdk/Package.swift | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"') +[ "$PODSPEC_VER" = "$SPM_VER" ] && echo "PASS: versions match ($PODSPEC_VER)" || echo "FAIL: podspec=$PODSPEC_VER spm=$SPM_VER" ``` -**Expected**: Build succeeds. No errors about missing CocoaPods or podspec. +**Expected**: Versions match. If intentionally mismatched for testing, the check should fail. -## Validation Scenario 8: CI Pipeline +## Validation Scenario 10: CI Pipeline **Goal**: Verify all five CI jobs pass. -Push the migration branch and create a PR against `master`. Monitor: +Push the branch and create a PR against `master`. Monitor: 1. `unit_test_coverage` — Dart tests + coverage -2. `build_test_android` — Android build (should be unaffected) -3. `build_test_ios` — iOS build via SPM -4. `integration_android_tests` — Android e2e (should be unaffected) -5. `integration_ios_tests` — iOS e2e via SPM +2. `build_test_android` — Android build (unaffected) +3. `build_test_ios` — iOS build +4. `integration_android_tests` — Android e2e (unaffected) +5. `integration_ios_tests` — iOS e2e **Expected**: All five jobs pass green. ## Troubleshooting - **SPM resolution fails**: Verify the `swift-sdk` git tag for version 5.4.2 exists. Check Package.swift URL. -- **Mixed ObjC/Swift errors**: If ObjC bridging files were kept, ensure they're in a separate target or removed. +- **CocoaPods can't find source files**: Verify podspec `source_files` path matches the new directory layout. +- **ObjC header not found**: Check that the `.h` file is in `include/optimizely_flutter_sdk/` and import path in `.m` is updated. +- **Mixed ObjC/Swift errors in SPM**: Ensure `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` is in Package.swift target. - **Xcode cache issues**: Run `flutter clean`, delete `~/Library/Developer/Xcode/DerivedData/`, rebuild. -- **Old Pods artifacts**: Delete `ios/Pods/`, `ios/Podfile.lock`, `example/ios/Pods/`, `example/ios/Podfile.lock`. +- **Wrong dependency path used**: Use `flutter config --enable-swift-package-manager` or `--no-enable-swift-package-manager` to force a specific path for testing. diff --git a/specs/001-cocoapods-to-spm-migration/research.md b/specs/001-cocoapods-to-spm-migration/research.md index 6867bc0..23808e8 100644 --- a/specs/001-cocoapods-to-spm-migration/research.md +++ b/specs/001-cocoapods-to-spm-migration/research.md @@ -1,21 +1,22 @@ -# Research: CocoaPods to SPM Migration +# Research: Add SPM Support with CocoaPods Backward Compatibility -**Date**: 2026-08-20 | **Feature**: [spec.md](spec.md) +**Date**: 2026-08-20 (Revised) | **Feature**: [spec.md](spec.md) -## 1. Flutter Plugin SPM Integration +## 1. Flutter Plugin Dual SPM + CocoaPods Support -**Decision**: Use Flutter's official SPM plugin structure with `Package.swift` in `ios//`. +**Decision**: Add SPM support via Package.swift while retaining the podspec with updated source paths. Follow Flutter's official recommendation for dual support. -**Rationale**: Flutter's official documentation defines the canonical directory layout and Package.swift structure for SPM-based plugins. This is the only supported approach for plugins that want to work with Flutter's SPM integration. +**Rationale**: Flutter explicitly states: "Flutter plugins should support both Swift Package Manager and CocoaPods until further notice." The official guide provides a migration path that preserves CocoaPods compatibility. **Key Findings**: - **Package.swift location**: `ios/optimizely_flutter_sdk/Package.swift` - **Source files location**: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/` -- Source files must be moved from `ios/Classes/` to the new SPM sources directory +- Source files move from `ios/Classes/` to the new SPM sources directory - Library product name replaces underscores with hyphens: `optimizely-flutter-sdk` - `FlutterFramework` dependency with `path: "../FlutterFramework"` is mandatory — Flutter tooling provides this at build time -- The ObjC bridging files (`.h`, `.m`) are used for CocoaPods plugin registration; SPM plugins may handle registration differently +- **Podspec stays**: Update `s.source_files` from `'Classes/**/*.swift'` to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*.swift'` +- Both Package.swift and podspec reference the same files in the new location **Package.swift Template**: ```swift @@ -38,40 +39,46 @@ let package = Package( dependencies: [ .product(name: "FlutterFramework", package: "FlutterFramework"), .product(name: "Optimizely", package: "swift-sdk") + ], + cSettings: [ + .headerSearchPath("include/optimizely_flutter_sdk") ] ) ] ) ``` +**Updated Podspec Changes**: +```ruby +# Before: s.source_files = 'Classes/**/*' +# After: +s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' +``` + **Alternatives Considered**: -- Dual CocoaPods+SPM support: Rejected by user (clean cut decision) -- Custom SPM layout: Rejected — must follow Flutter's canonical structure +- Clean cut (remove CocoaPods entirely): Rejected by user — backward compatibility required +- Keep source files in `ios/Classes/` and symlink for SPM: Rejected — fragile, not recommended by Flutter ## 2. OptimizelySwiftSDK SPM Compatibility -**Decision**: Use `https://github.com/optimizely/swift-sdk.git` with `.exact("5.4.2")` version pinning. +**Decision**: Use `https://github.com/optimizely/swift-sdk.git` with `.exact("5.4.2")` version pinning in Package.swift. Keep `OptimizelySwiftSDK`, `5.4.2` in podspec. -**Rationale**: The OptimizelySwiftSDK has first-class SPM support. The SPM product name is `Optimizely` (from the `swift-sdk` package). Exact version pinning matches the current podspec strategy and aligns with Constitution Principle VIII. +**Rationale**: The OptimizelySwiftSDK has first-class SPM support. Both dependency mechanisms must pin the same version to prevent drift (FR-006). **Key Findings**: - SPM repo URL: `https://github.com/optimizely/swift-sdk.git` - Package name: `swift-sdk` - Product/library name: `Optimizely` - Supported platforms: iOS 10.0+, tvOS 10.0+, macOS 10.14+, watchOS 3.0+ -- swift-tools-version: 5.3 +- CocoaPods pod name: `OptimizelySwiftSDK` **Verification Required**: Confirm that git tag `5.4.2` exists on the `swift-sdk` repo. If the tag format is `v5.4.2`, the exact version string may need adjustment. -**Alternatives Considered**: -- `.upToNextMinor(from: "5.4.2")`: Rejected — user chose exact pinning for stability -- `.upToNextMajor(from: "5.4.2")`: Rejected — too permissive for a bridge SDK - -## 3. Source File Migration +## 3. Source File Migration (Dual Support Layout) -**Decision**: Move all Swift source files from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. Evaluate ObjC bridging files for removal or migration. +**Decision**: Move all source files from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. ObjC headers go into an `include/` subdirectory. Update podspec paths to match. -**Rationale**: SPM requires sources in a specific directory structure. The current `ios/Classes/` layout is CocoaPods-specific. +**Rationale**: This is the canonical Flutter dual-support layout. Both Package.swift and podspec reference the same physical files, just via different path declarations. **Current Files**: - `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` — main plugin @@ -82,32 +89,63 @@ let package = Package( - `ios/Classes/OptimizelyFlutterSdkPlugin.h` — ObjC header (bridging) - `ios/Classes/OptimizelyFlutterSdkPlugin.m` — ObjC implementation (bridging) -**ObjC Bridging Files**: The `.h` and `.m` files are a thin bridge that calls `SwiftOptimizelyFlutterSdkPlugin.registerWithRegistrar()`. SPM does not support mixed ObjC/Swift in a single target without a separate clang target. Options: -1. Convert to pure Swift registration (preferred for clean cut) -2. Use separate SPM targets for ObjC and Swift (adds complexity) +**New Layout**: +``` +ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/ +├── include/ +│ └── optimizely_flutter_sdk/ +│ └── OptimizelyFlutterSdkPlugin.h # ObjC public header +├── OptimizelyFlutterSdkPlugin.m # ObjC bridge implementation +├── SwiftOptimizelyFlutterSdkPlugin.swift +├── OptimizelyFlutterLogger.swift +├── Constants.swift +├── OptimizelyConfig+Extension.swift +└── Utils.swift +``` + +**ObjC Bridging Files**: +- Public header (`.h`) goes in `include/optimizely_flutter_sdk/` subdirectory +- Implementation (`.m`) stays in the main sources directory +- Import path in `.m` must be updated: `#import "OptimizelyFlutterSdkPlugin.h"` → `#import "./include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h"` +- Package.swift needs `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` + +## 4. Podspec Updates + +**Decision**: Keep `optimizely_flutter_sdk.podspec` at `ios/optimizely_flutter_sdk.podspec` and update `source_files` to point to the new directory layout. + +**Changes Required**: +```ruby +# Before +s.source_files = 'Classes/**/*' + +# After +s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' +``` + +**Verification**: Run `pod lib lint optimizely_flutter_sdk.podspec` after changes to ensure CocoaPods can still find and compile all source files. -## 4. Example App Migration +## 5. Example App -**Decision**: Remove CocoaPods artifacts from `example/ios/` (Podfile, Podfile.lock, Pods/). Flutter tooling will resolve the SDK plugin via SPM automatically. +**Decision**: No changes needed to the example app itself. Flutter tooling will use whichever dependency mechanism is available (SPM on 3.44+, CocoaPods on older versions). The example app's Podfile, Podfile.lock, and Pods/ remain as-is for CocoaPods compatibility. -**Rationale**: The example app follows the same pattern as any consuming Flutter app. Once the plugin has a Package.swift, Flutter resolves it via SPM. +**Rationale**: The example app is a consumer of the plugin. Flutter handles the CocoaPods-to-SPM routing transparently. Removing the Podfile would break builds on older Flutter versions. -**Current State**: Example app has `Podfile` (platform :ios, '11.0'), `Podfile.lock`, and `Pods/` directory. +## 6. Test App -## 5. Test App Migration +**Decision**: The test app needs both CI workflow updates and SPM configuration added to its iOS project. The test app's Podfile can be kept for CocoaPods fallback or removed if the CI Flutter version defaults to SPM. -**Decision**: Remove CocoaPods artifacts from `optimizely-flutter-testapp/ios/` and update CI workflow to remove CocoaPods installation step. +**Rationale**: The test app validates the SDK end-to-end. Per clarification, changes include CI workflow updates + adding SPM configuration to the test app's iOS project. This ensures US-3 acceptance scenarios can be verified. -**Rationale**: The test app is the SDK's end-to-end validation layer. Its CI (`ios.yml`) currently runs `brew install cocoapods` + `pod repo update` — these steps must be removed. +## 7. SDK CI Impact -**Current State**: Test app has `Podfile` (platform :ios, '13.0'), `Podfile.lock`, `Pods/` directory. iOS CI has explicit CocoaPods installation. +**Decision**: SDK CI workflows (`flutter.yml`) require no changes — they don't have explicit `pod install` calls. The test app CI (`ios.yml`) may need adjustment if it currently uses `brew install cocoapods` and the CI Flutter version defaults to SPM. -**CI Change Required**: In `optimizely-flutter-testapp/.github/workflows/ios.yml`, remove or replace the "Install Xcode Dependencies" step that runs `brew install cocoapods` and `pod repo update`. +**Note**: The `brew install cocoapods` + `pod repo update` step in test app CI can remain (for CocoaPods fallback) or be removed (if CI Flutter version uses SPM by default). The test app CI should be reviewed but not necessarily changed if it works. -## 6. SDK CI Impact +## 8. Version Synchronization (Critical) -**Decision**: SDK CI workflows (`flutter.yml`) require no changes to CocoaPods steps. +**Decision**: Both Package.swift and podspec MUST declare the same OptimizelySwiftSDK version. When bumping native SDK versions, both files must be updated simultaneously. A CI validation check (FR-014) MUST fail the build if versions diverge. -**Rationale**: The SDK CI does not have explicit `pod install` calls — Flutter tooling handles dependency resolution. Integration test jobs just trigger the test app repo. The `build_test_ios` and `unit_test_coverage` jobs only run `flutter pub get` + `flutter test`. +**Implementation**: Add a CI validation step that extracts and compares the OptimizelySwiftSDK version from both files. Add a note to CLAUDE.md's version management section about the dual-file requirement. Constitution Principle VIII (Native SDK Version Pinning) already covers this — the source-of-truth files now include both `ios/optimizely_flutter_sdk.podspec` AND `ios/optimizely_flutter_sdk/Package.swift`. -**Note**: While the SDK CI doesn't need CocoaPods step changes, the integration test jobs trigger the test app, which does need changes (covered in section 5). +**No Deprecation Notices**: Per clarification, this release does not include deprecation notices or migration guides for CocoaPods users. That will be addressed closer to Flutter's actual CocoaPods removal. diff --git a/specs/001-cocoapods-to-spm-migration/spec.md b/specs/001-cocoapods-to-spm-migration/spec.md index b770b06..f3aaecc 100644 --- a/specs/001-cocoapods-to-spm-migration/spec.md +++ b/specs/001-cocoapods-to-spm-migration/spec.md @@ -1,132 +1,141 @@ -# Feature Specification: CocoaPods to Swift Package Manager Migration +# Feature Specification: Add SPM Support with CocoaPods Backward Compatibility **Feature Branch**: `001-cocoapods-to-spm-migration` **Created**: 2026-08-20 -**Status**: Draft +**Status**: Draft (Revised) -**Input**: User description: "Flutter going to drop CocoaPods support. Want to move Swift Package Manager. Migrate the Flutter SDK and test app from CocoaPods to SPM gracefully. Follow web and Flutter official guidelines for the migration." +**Input**: User description: "Flutter going to drop CocoaPods support. Want to move to Swift Package Manager. Support both SPM and CocoaPods. Migrate the Flutter SDK and test app gracefully." ## User Scenarios & Testing *(mandatory)* -### User Story 1 - SDK Consumers Build iOS Without CocoaPods (Priority: P1) +### User Story 1 - SDK Consumers Build iOS via SPM (Priority: P1) -A developer who integrates the Optimizely Flutter SDK into their Flutter app can build and run the iOS target without CocoaPods installed. The SDK's iOS dependency (OptimizelySwiftSDK) is resolved through Swift Package Manager instead. The developer's existing workflow (`flutter build ios`, `flutter run`) continues to work without manual SPM configuration steps. +A developer who integrates the Optimizely Flutter SDK into their Flutter app can build and run the iOS target using Swift Package Manager. The SDK's iOS dependency (OptimizelySwiftSDK) is resolved through SPM when the developer's Flutter tooling supports it. The developer's existing workflow (`flutter build ios`, `flutter run`) continues to work without manual SPM configuration steps. -**Why this priority**: This is the core deliverable. Without this, the SDK cannot function once Flutter drops CocoaPods support. It directly unblocks all iOS users. +**Why this priority**: This is the core deliverable. Flutter is deprecating CocoaPods support, and SPM readiness ensures the SDK works with future Flutter versions. -**Independent Test**: Can be tested by removing CocoaPods from the build environment, running `flutter build ios` against a fresh Flutter app that depends on `optimizely_flutter_sdk`, and verifying a successful build with OptimizelySwiftSDK resolved via SPM. +**Independent Test**: Run `flutter build ios` against a fresh Flutter app (on Flutter 3.44+) that depends on `optimizely_flutter_sdk`, and verify a successful build with OptimizelySwiftSDK resolved via SPM. **Acceptance Scenarios**: -1. **Given** a Flutter app with `optimizely_flutter_sdk` as a dependency, **When** the developer runs `flutter build ios` on a machine without CocoaPods, **Then** the build succeeds and OptimizelySwiftSDK is resolved via SPM. -2. **Given** the SDK plugin's iOS platform configuration, **When** Flutter tooling resolves iOS dependencies, **Then** the OptimizelySwiftSDK package is fetched from its SPM-compatible repository and linked correctly. -3. **Given** a developer upgrades from a prior CocoaPods-based version of the SDK, **When** they run `flutter pub get` and `flutter build ios`, **Then** the migration to SPM happens transparently without manual intervention beyond cleaning old CocoaPods artifacts. +1. **Given** a Flutter app with `optimizely_flutter_sdk` as a dependency on Flutter 3.44+, **When** the developer runs `flutter build ios`, **Then** the build succeeds and OptimizelySwiftSDK is resolved via SPM. +2. **Given** the SDK plugin's iOS platform configuration, **When** Flutter tooling detects SPM support (Package.swift present), **Then** the OptimizelySwiftSDK package is fetched from its SPM-compatible repository and linked correctly. --- -### User Story 2 - Test App Migrated to SPM (Priority: P1) +### User Story 2 - SDK Consumers Continue Building iOS via CocoaPods (Priority: P1) -The `optimizely-flutter-testapp` project's iOS target builds and runs using SPM instead of CocoaPods. Integration tests and CI pipelines that depend on the test app continue to pass. +A developer on an older Flutter version (pre-3.44) or one who has not yet adopted SPM can continue building the iOS target using CocoaPods. The existing podspec remains functional and resolves the same OptimizelySwiftSDK version. -**Why this priority**: The test app is the SDK's validation layer. CI integration tests (`integration_ios_tests`) depend on it. Without migrating it, the team cannot verify the SDK works end-to-end on iOS. +**Why this priority**: Backward compatibility is essential. Not all consumers will be on Flutter 3.44+ immediately. Dropping CocoaPods support abruptly would break existing users. -**Independent Test**: Can be tested by building and running the test app's iOS target from the `optimizely-flutter-testapp` repo after removing Podfile/Pods, and verifying all integration tests pass. +**Independent Test**: Run `flutter build ios` against a Flutter app (on any supported Flutter version) using CocoaPods, and verify the build succeeds with the same SDK functionality. **Acceptance Scenarios**: -1. **Given** the test app repository, **When** a developer runs `flutter build ios` or `flutter run` targeting iOS, **Then** the build succeeds without CocoaPods and uses SPM for native dependency resolution. -2. **Given** the test app's CI pipeline, **When** the `integration_ios_tests` workflow runs, **Then** it completes successfully with SPM-based dependency resolution. +1. **Given** a Flutter app with `optimizely_flutter_sdk` as a dependency on Flutter <3.44, **When** the developer runs `flutter build ios`, **Then** the build succeeds using CocoaPods dependency resolution. +2. **Given** the updated podspec with new source file paths, **When** CocoaPods resolves the SDK, **Then** all Swift source files are found and compiled correctly. +3. **Given** a developer who was already using the SDK via CocoaPods, **When** they upgrade to this version, **Then** `pod install` succeeds and the app builds without changes to their Podfile. --- -### User Story 3 - Clean Removal of CocoaPods Artifacts (Priority: P2) +### User Story 3 - Test App Works with SPM (Priority: P2) -All CocoaPods-specific configuration files (Podfile, Podfile.lock, .podspec, Pods directory references) are removed from both the SDK and test app repositories. No orphaned CocoaPods configuration remains that could confuse developers or tooling. +The `optimizely-flutter-testapp` project's iOS target builds and runs using SPM. Integration tests and CI pipelines that depend on the test app continue to pass when using the SPM-based dependency path. -**Why this priority**: Leaving CocoaPods artifacts creates confusion and maintenance burden. However, the migration must work first (P1) before cleanup matters. +**Why this priority**: The test app is the SDK's validation layer. CI integration tests (`integration_ios_tests`) depend on it. The test app should validate the SPM path to ensure it works end-to-end. -**Independent Test**: Can be tested by searching both repositories for CocoaPods references (Podfile, .podspec, pod commands) and verifying none remain, then building successfully. +**Independent Test**: Build and run the test app's iOS target from the `optimizely-flutter-testapp` repo using SPM, and verify all integration tests pass. **Acceptance Scenarios**: -1. **Given** the SDK repository after migration, **When** searching for CocoaPods artifacts (`Podfile`, `*.podspec`, `Pods/` references), **Then** none are found. -2. **Given** the test app repository after migration, **When** searching for CocoaPods artifacts, **Then** none are found (Podfile, Podfile.lock, Pods directory are removed). -3. **Given** the SDK's CLAUDE.md and documentation, **When** reviewing setup instructions, **Then** all references to `pod install` are removed or replaced with SPM equivalents. +1. **Given** the test app repository with SPM-enabled Flutter, **When** a developer runs `flutter build ios` targeting iOS, **Then** the build succeeds using SPM for native dependency resolution. +2. **Given** the test app's CI pipeline configured for SPM, **When** the `integration_ios_tests` workflow runs, **Then** it completes successfully. +3. **Given** the test app's iOS project with SPM configuration added, **When** the SDK's native dependency is resolved, **Then** OptimizelySwiftSDK is fetched via SPM and the test app compiles and runs all integration tests. --- -### User Story 4 - CI Pipeline Adapted for SPM (Priority: P2) +### User Story 4 - CI Pipeline Validates Both Dependency Paths (Priority: P2) -CI workflows (`build_test_ios`, `integration_ios_tests`) are updated to work without CocoaPods. Build steps that previously ran `pod install` are removed or replaced. SPM dependency caching is configured for reasonable build times. +CI workflows validate that the SDK works with both CocoaPods and SPM. The existing CocoaPods-based CI continues to work, and SPM-based builds are also verified. -**Why this priority**: CI must pass for any PR to merge. This is essential but follows from the migration itself being complete. +**Why this priority**: CI must validate both paths to ensure neither is broken. This is essential for maintaining dual support confidence. -**Independent Test**: Can be tested by triggering the CI pipeline on a branch with SPM changes and verifying all iOS-related jobs pass. +**Independent Test**: Trigger CI and verify all five jobs pass, with iOS builds validating SPM resolution. **Acceptance Scenarios**: -1. **Given** the CI workflow for `build_test_ios`, **When** the job runs, **Then** it builds successfully without any `pod install` step. -2. **Given** the CI workflow for `integration_ios_tests`, **When** the job runs against the migrated test app, **Then** integration tests pass with SPM dependency resolution. +1. **Given** the CI workflow for `build_test_ios`, **When** the job runs, **Then** it builds successfully. +2. **Given** the CI workflow for `integration_ios_tests`, **When** the job runs against the test app, **Then** integration tests pass. +3. **Given** both CocoaPods and SPM configurations exist, **When** CI runs, **Then** all five pipeline jobs pass without conflicts between the two dependency mechanisms. --- ### Edge Cases -- What happens when a developer has an existing project with cached CocoaPods artifacts (Pods/ directory, Podfile.lock) from a prior SDK version and upgrades? -- How does the migration behave when the developer's Xcode version does not support the minimum SPM features required? -- What happens if the OptimizelySwiftSDK SPM package URL or version tag format differs from the CocoaPods version specifier? -- How does the SDK handle a scenario where Flutter's own SPM migration tooling is partially adopted (mixed CocoaPods/SPM state during Flutter's transition period)? +- **Mixed CocoaPods/SPM state (Podfile + SPM-capable Flutter)**: Flutter's tooling handles resolution automatically. The SDK provides both Package.swift and podspec; Flutter picks the appropriate mechanism based on version and project configuration. No SDK-level detection or intervention is needed. +- **SPM package URL or version tag format differs from CocoaPods**: Mitigated by FR-006 — both manifests pin the same OptimizelySwiftSDK version. The SPM repository URL must match the official OptimizelySwiftSDK SPM-compatible repository. +- **Podspec source file paths after directory restructuring**: The podspec's `source_files` declaration is updated to point to the new SPM-compatible directory structure (FR-008). Existing CocoaPods users running `pod install` will pick up the new paths automatically. +- **Package.swift and podspec declare different OptimizelySwiftSDK versions**: Prevented by FR-006 and enforced by FR-014 — a CI validation check fails the build if versions diverge. ## Requirements *(mandatory)* ### Functional Requirements -- **FR-001**: The SDK MUST declare its iOS native dependency (OptimizelySwiftSDK) using Swift Package Manager configuration instead of a CocoaPods podspec. +- **FR-001**: The SDK MUST declare its iOS native dependency (OptimizelySwiftSDK) using Swift Package Manager via a Package.swift file. - **FR-002**: The SDK MUST use Flutter's official SPM integration mechanism (Package.swift in the plugin's iOS directory) to declare the native dependency. -- **FR-003**: The SDK MUST specify the OptimizelySwiftSDK version using exact pinning (`.exact("5.4.2")`) in the SPM package dependency, matching the version previously pinned in the podspec. -- **FR-004**: The SDK MUST remove the `optimizely_flutter_sdk.podspec` file from the `ios/` directory. This is a clean cut — no dual CocoaPods/SPM support period. +- **FR-003**: The SDK MUST specify the OptimizelySwiftSDK version using exact pinning (`.exact("5.4.2")`) in the SPM package dependency, matching the version pinned in the podspec. +- **FR-004**: The SDK MUST retain the `optimizely_flutter_sdk.podspec` file with updated source file paths pointing to the new SPM-compatible directory structure. Both Package.swift and podspec MUST coexist. - **FR-005**: The SDK MUST maintain all existing iOS functionality (MethodChannel handlers, logger bridge, type encoding) without any behavioral changes. -- **FR-006**: The test app MUST remove its Podfile, Podfile.lock, and Pods directory and rely solely on SPM for iOS dependency resolution. -- **FR-007**: The SDK's `pubspec.yaml` plugin declaration for iOS MUST remain compatible with Flutter's SPM plugin system. -- **FR-008**: All CI workflows involving iOS builds MUST be updated to remove CocoaPods steps and work with SPM. -- **FR-009**: The SDK documentation (README.md, CLAUDE.md) MUST be updated to remove CocoaPods references and reflect SPM-based setup instructions. +- **FR-006**: The podspec and Package.swift MUST declare the same OptimizelySwiftSDK version to prevent version drift. +- **FR-007**: The SDK's `pubspec.yaml` plugin declaration for iOS MUST remain compatible with both Flutter's SPM plugin system and CocoaPods. +- **FR-008**: Source files MUST be organized in a directory structure that is accessible from both the SPM Package.swift target and the podspec `source_files` declaration. +- **FR-009**: The SDK documentation (README.md, CLAUDE.md) MUST be updated to document both CocoaPods and SPM setup paths. - **FR-010**: The SDK MUST retain iOS 10.0 as its minimum deployment target. The test app maintains its own minimum of iOS 13.0. - -- **FR-011**: All existing SDK unit tests (`flutter test`) MUST pass with zero failures after the migration — no test regressions are acceptable. -- **FR-012**: All test app end-to-end test cases MUST pass with zero failures after the migration on both Android and iOS platforms. -- **FR-013**: All five CI pipeline jobs MUST pass on the migration PR: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. The migration MUST NOT break any existing CI workflow. +- **FR-011**: All existing SDK unit tests (`flutter test`) MUST pass with zero failures after the changes — no test regressions are acceptable. +- **FR-012**: All test app end-to-end test cases MUST pass with zero failures on both Android and iOS platforms. +- **FR-013**: All five CI pipeline jobs MUST pass: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. The changes MUST NOT break any existing CI workflow. +- **FR-014**: CI MUST include a validation check that verifies the OptimizelySwiftSDK version declared in Package.swift matches the version declared in the podspec. The build MUST fail if the versions diverge. ### Key Entities -- **Package.swift**: SPM package manifest that replaces the podspec, declaring the OptimizelySwiftSDK dependency and linking the plugin's Swift source files. -- **Plugin Registration**: The mechanism by which Flutter discovers and loads the native iOS plugin, which must work through SPM instead of CocoaPods. +- **Package.swift**: SPM package manifest declaring the OptimizelySwiftSDK dependency and linking the plugin's Swift source files. Coexists with the podspec. +- **optimizely_flutter_sdk.podspec**: Updated CocoaPods spec with source file paths pointing to the new SPM-compatible directory layout. +- **Plugin Registration**: The mechanism by which Flutter discovers and loads the native iOS plugin, which must work through both SPM and CocoaPods. ## Success Criteria *(mandatory)* ### Measurable Outcomes -- **SC-001**: A fresh Flutter project depending on `optimizely_flutter_sdk` builds for iOS successfully without CocoaPods installed on the build machine. -- **SC-002**: All existing SDK unit tests (`flutter test`) pass with zero failures — no test regressions. -- **SC-003**: All five CI pipeline jobs pass on the migration PR: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. -- **SC-004**: All test app end-to-end test cases pass on both Android and iOS platforms with zero failures. -- **SC-005**: Zero CocoaPods-specific files remain in either the SDK or test app repositories after migration. -- **SC-006**: Developer setup time for iOS does not increase — no new manual steps are required beyond `flutter pub get` and `flutter build ios`. +- **SC-001**: A fresh Flutter project (3.44+) depending on `optimizely_flutter_sdk` builds for iOS successfully via SPM. +- **SC-002**: A fresh Flutter project (pre-3.44 or CocoaPods-configured) depending on `optimizely_flutter_sdk` builds for iOS successfully via CocoaPods. +- **SC-003**: All existing SDK unit tests (`flutter test`) pass with zero failures — no test regressions. +- **SC-004**: All five CI pipeline jobs pass: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, and `integration_ios_tests`. +- **SC-005**: All test app end-to-end test cases pass on both Android and iOS platforms with zero failures. +- **SC-006**: Developer setup time for iOS does not increase — no new manual steps required beyond `flutter pub get` and `flutter build ios` for either dependency mechanism. +- **SC-007**: The podspec and Package.swift both reference the same OptimizelySwiftSDK version (no version drift). ## Clarifications ### Session 2026-08-20 - Q: What should the minimum iOS deployment target be after the SPM migration? → A: SDK keeps iOS 10.0; test app keeps iOS 13.0 (independent targets). -- Q: Should the SDK support both CocoaPods and SPM during a transition period? → A: Clean cut — CocoaPods removed entirely in this release, no dual-support period. +- Q: Should the SDK support both CocoaPods and SPM during a transition period? → A: ~~Clean cut~~ **Revised**: Support both CocoaPods and SPM simultaneously. Keep the podspec with updated paths alongside the new Package.swift. - Q: How should the OptimizelySwiftSDK version be pinned in SPM Package.swift? → A: Exact version pinning (`.exact("5.4.2")`), matching current podspec behavior. +- Q: What specific changes are needed in the `optimizely-flutter-testapp` repo? → A: CI workflow updates + add SPM configuration to the test app's iOS project. +- Q: For mixed CocoaPods/SPM states, does the SDK need intervention logic? → A: No. Flutter's tooling handles resolution automatically; the SDK just provides both manifests. +- Q: How should version parity between Package.swift and podspec (FR-006) be enforced? → A: CI validation check that fails the build if versions diverge. +- Q: Should this release include deprecation notices or migration guidance for CocoaPods users? → A: No — premature until Flutter officially drops CocoaPods. ## Assumptions - The OptimizelySwiftSDK already publishes SPM-compatible releases (it does — the library supports SPM via its Package.swift). - Flutter's SPM integration for plugins is stable enough for production use (Flutter has been shipping SPM support and is actively deprecating CocoaPods). - The SDK retains iOS 10.0 as its minimum deployment target; the test app uses iOS 13.0. These are independent and intentional. -- The example app (`example/`) within the SDK repo, if it has iOS-specific CocoaPods setup, will also be migrated as part of this effort. -- macOS plugin platform (declared but experimental in pubspec.yaml) is out of scope for this migration unless it shares the same CocoaPods configuration. -- The `optimizely-flutter-testapp` repository is accessible and modifiable as part of this migration effort. +- Source files can be organized in a single directory structure (SPM layout) that both Package.swift and podspec reference via their respective path declarations. +- The example app (`example/`) within the SDK repo will work with whichever dependency mechanism the local Flutter version prefers. +- macOS plugin platform (declared but experimental in pubspec.yaml) is out of scope for this effort. +- The `optimizely-flutter-testapp` repository is accessible and modifiable as part of this effort. Changes include CI workflow updates and adding SPM configuration to the test app's iOS project. +- CocoaPods support will be maintained for as long as Flutter officially supports it, then can be dropped in a future release. No deprecation notices or migration guides are included in this release — that will be addressed closer to Flutter's actual CocoaPods removal. diff --git a/specs/001-cocoapods-to-spm-migration/tasks.md b/specs/001-cocoapods-to-spm-migration/tasks.md new file mode 100644 index 0000000..64901bf --- /dev/null +++ b/specs/001-cocoapods-to-spm-migration/tasks.md @@ -0,0 +1,220 @@ +# Tasks: Add SPM Support with CocoaPods Backward Compatibility + +**Input**: Design documents from `specs/001-cocoapods-to-spm-migration/` + +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, quickstart.md + +**Tests**: Not explicitly requested — test tasks omitted. Existing tests must pass (verified in validation tasks). + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Phase 1: Setup + +**Purpose**: Verify prerequisites and prepare for migration + +- [ ] T001 Verify OptimizelySwiftSDK git tag format for version 5.4.2 on https://github.com/optimizely/swift-sdk (confirm tag is `5.4.2` vs `v5.4.2` and note the exact string for Package.swift) +- [ ] T002 Create SPM directory structure: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/` + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Move source files to SPM layout and create Package.swift — MUST complete before any user story work + +**CRITICAL**: No user story work can begin until this phase is complete + +- [ ] T003 Move `ios/Classes/OptimizelyFlutterSdkPlugin.h` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h` +- [ ] T004 Move `ios/Classes/OptimizelyFlutterSdkPlugin.m` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m` and update its import path from `#import "OptimizelyFlutterSdkPlugin.h"` to `#import "./include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h"` +- [ ] T005 [P] Move `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift` +- [ ] T006 [P] Move `ios/Classes/OptimizelyFlutterLogger.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift` +- [ ] T007 [P] Move `ios/Classes/HelperClasses/Constants.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Constants.swift` +- [ ] T008 [P] Move `ios/Classes/HelperClasses/OptimizelyConfig+Extension.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift` +- [ ] T009 [P] Move `ios/Classes/HelperClasses/Utils.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Utils.swift` +- [ ] T010 Remove empty `ios/Classes/` directory (including `HelperClasses/` subdirectory) +- [ ] T011 Remove empty `ios/Assets/` directory +- [ ] T012 Create `ios/optimizely_flutter_sdk/Package.swift` with: swift-tools-version 5.9, iOS platform 10.0, FlutterFramework dependency, OptimizelySwiftSDK via `https://github.com/optimizely/swift-sdk.git` with exact version pinning, and `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` in target +- [ ] T013 Verify `flutter pub get` succeeds from SDK repo root after file restructuring + +**Checkpoint**: SPM package structure is in place, all source files are in their new locations, Package.swift is created + +--- + +## Phase 3: User Story 1 — SDK Consumers Build iOS via SPM (Priority: P1) MVP + +**Goal**: The SDK plugin builds for iOS via SPM. Consumers on Flutter 3.44+ can run `flutter build ios` with SPM dependency resolution. + +**Independent Test**: Enable SPM (`flutter config --enable-swift-package-manager`), run `flutter build ios --simulator --no-codesign` from `example/` directory, and verify it succeeds with SPM resolving OptimizelySwiftSDK. + +### Implementation for User Story 1 + +- [ ] T014 [US1] Run `flutter config --enable-swift-package-manager` to enable SPM for testing +- [ ] T015 [US1] Run `flutter clean && flutter pub get` from `example/` directory +- [ ] T016 [US1] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory with SPM resolving the swift-sdk package +- [ ] T017 [US1] Run `flutter test` from SDK repo root and verify all existing unit tests pass with zero failures + +**Checkpoint**: SDK plugin builds for iOS via SPM. Example app compiles via SPM. All unit tests pass. + +--- + +## Phase 4: User Story 2 — SDK Consumers Continue Building via CocoaPods (Priority: P1) + +**Goal**: The existing podspec still works after source files moved to the new directory layout. Consumers on older Flutter can continue using CocoaPods. + +**Independent Test**: Disable SPM (`flutter config --no-enable-swift-package-manager`), run `flutter build ios --simulator --no-codesign` from `example/`, and verify it succeeds via CocoaPods with updated source paths. + +### Implementation for User Story 2 + +- [ ] T018 [US2] Update `ios/optimizely_flutter_sdk.podspec`: change `s.source_files` from `'Classes/**/*'` to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*'` +- [ ] T019 [US2] Run `pod lib lint ios/optimizely_flutter_sdk.podspec --allow-warnings` to verify podspec validity with new source paths +- [ ] T020 [US2] Run `flutter config --no-enable-swift-package-manager` to test CocoaPods path +- [ ] T021 [US2] Run `flutter clean && flutter pub get` from `example/` directory +- [ ] T022 [US2] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory via CocoaPods +- [ ] T023 [US2] Run `flutter test` from SDK repo root and verify all existing unit tests still pass + +**Checkpoint**: SDK plugin builds for iOS via CocoaPods with updated source paths. Podspec lints clean. All unit tests pass. + +--- + +## Phase 5: User Story 3 — Test App Works with SPM (Priority: P2) + +**Goal**: The `optimizely-flutter-testapp` builds and runs on iOS via SPM. Integration tests pass. + +**Independent Test**: Build the test app with SPM enabled and run integration tests on iOS simulator. + +### Implementation for User Story 3 + +- [ ] T024 [US3] Ensure test app's dependency on `optimizely_flutter_sdk` resolves the SPM-enabled version (check `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/pubspec.yaml` path dependency) +- [ ] T025 [US3] Update test app CI workflow at `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/.github/workflows/ios.yml`: add `flutter config --enable-swift-package-manager` step before the build step; keep existing `brew install cocoapods` + `pod repo update` steps as fallback for CocoaPods backward compatibility +- [ ] T026 [US3] Run `flutter config --enable-swift-package-manager && flutter clean && flutter pub get` from test app repo root +- [ ] T027 [US3] Verify `flutter build ios --simulator --no-codesign` succeeds from test app repo with SPM resolving dependencies +- [ ] T028 [US3] Run integration tests on iOS simulator and verify all e2e test cases pass with zero failures + +**Checkpoint**: Test app builds and runs on iOS via SPM. CI workflow updated. All integration tests pass. + +--- + +## Phase 6: User Story 4 — CI Pipeline Validates Both Paths (Priority: P2) + +**Goal**: All CI workflows pass. Both CocoaPods and SPM build paths are validated. + +**Independent Test**: Push branch, create PR, and verify all five CI jobs pass. + +### Implementation for User Story 4 + +- [ ] T029 [US4] Create version drift check script at `.github/scripts/check-version-drift.sh` (FR-014) that extracts OptimizelySwiftSDK version from both `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift`, compares them, and exits non-zero if they diverge +- [ ] T030 [US4] Integrate version drift check into SDK CI workflow `.github/workflows/flutter.yml` as a job step that runs before build jobs +- [ ] T031 [US4] Review and confirm SDK CI `.github/workflows/flutter.yml` needs no other changes (no explicit pod commands exist) +- [ ] T032 [US4] Push branch and create PR to trigger all five CI jobs: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, `integration_ios_tests` +- [ ] T033 [US4] Verify all five CI jobs pass green, including the version drift check + +**Checkpoint**: All CI pipelines pass. Version drift is enforced. Both dependency paths validated. + +--- + +## Phase 7: Polish & Cross-Cutting Concerns + +**Purpose**: Documentation updates, version sync, and final validation + +- [ ] T034 [P] Update `CLAUDE.md`: remove `cd ios && pod install` from setup commands (no longer needed), add note about dual SPM/CocoaPods support, update iOS setup instructions, add `ios/optimizely_flutter_sdk/Package.swift` to version pinning source-of-truth list in the Native SDK Version Pinning section +- [ ] T035 [P] Update `README.md`: add note about SPM support alongside existing CocoaPods instructions if applicable (FR-009) +- [ ] T036 Verify version consistency: confirm `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift` both declare OptimizelySwiftSDK 5.4.2 +- [ ] T037 Run full `flutter test` from SDK repo root — verify zero failures (final regression check) +- [ ] T038 Run full `flutter analyze` from SDK repo root — verify zero issues +- [ ] T039 Run quickstart.md validation scenarios end-to-end (both SPM and CocoaPods paths, including version drift check) + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies — can start immediately +- **Foundational (Phase 2)**: Depends on Setup (T001 tag verification informs T012 Package.swift version string) +- **User Story 1 (Phase 3)**: Depends on Foundational phase completion (Package.swift must exist) +- **User Story 2 (Phase 4)**: Depends on Foundational phase completion (files must be moved before podspec path update) +- **User Story 3 (Phase 5)**: Depends on US1 completion (SDK must have working SPM support) +- **User Story 4 (Phase 6)**: Depends on US1 and US2 completion (both paths must work before CI validation) +- **Polish (Phase 7)**: Depends on all user stories being complete + +### User Story Dependencies + +- **User Story 1 (P1)**: Can start after Foundational (Phase 2) — no dependencies on other stories +- **User Story 2 (P1)**: Can start after Foundational (Phase 2) — can run in parallel with US1 +- **User Story 3 (P2)**: Depends on US1 — test app needs the SDK's SPM support to be working +- **User Story 4 (P2)**: Depends on US1 and US2 — CI validates both paths + +### Within Each User Story + +- Configuration before build verification +- Build verification before test verification +- Story complete before moving to next priority + +### Parallel Opportunities + +- T005, T006, T007, T008, T009 can all run in parallel (independent file moves) +- US1 and US2 can be worked on in parallel after Foundational phase completes +- T034 and T035 can run in parallel (independent documentation updates) + +--- + +## Parallel Example: Foundational Phase + +```bash +# Launch all Swift source file moves together (after T003/T004 ObjC handling): +Task: "Move SwiftOptimizelyFlutterSdkPlugin.swift to SPM sources" +Task: "Move OptimizelyFlutterLogger.swift to SPM sources" +Task: "Move Constants.swift to SPM sources" +Task: "Move OptimizelyConfig+Extension.swift to SPM sources" +Task: "Move Utils.swift to SPM sources" +``` + +## Parallel Example: User Story 1 + User Story 2 + +```bash +# After Foundational phase, both can start simultaneously: +# Developer A: User Story 1 (verify SPM path) +Task: "Enable SPM, build example app via SPM, verify tests pass" + +# Developer B: User Story 2 (verify CocoaPods path) +Task: "Update podspec paths, lint podspec, build example app via CocoaPods, verify tests pass" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup (verify SPM tag) +2. Complete Phase 2: Foundational (move files + create Package.swift) +3. Complete Phase 3: User Story 1 (SDK builds via SPM) +4. **STOP and VALIDATE**: Build example app via SPM, run unit tests +5. This alone proves SPM support works + +### Incremental Delivery + +1. Setup + Foundational → Files moved, Package.swift created +2. Add User Story 1 → SDK builds via SPM (MVP!) +3. Add User Story 2 → SDK also builds via CocoaPods (backward compat confirmed) +4. Add User Story 3 → Test app works via SPM +5. Add User Story 4 → CI passes on both paths +6. Polish → Documentation updated, version sync verified + +--- + +## Notes + +- [P] tasks = different files, no dependencies +- [Story] label maps task to specific user story for traceability +- Each user story should be independently completable and testable +- Commit after each phase or logical group +- Stop at any checkpoint to validate story independently +- The ObjC header placement (T003) and import path update (T004) should be done before Swift file moves to avoid build issues +- Version consistency (T036) is critical — both podspec and Package.swift must always declare the same OptimizelySwiftSDK version +- FR-014 version drift CI check is implemented in US4 (T029-T030) From 70607cb94aa1fa9d8f945723c7924e71f871337f Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 20 Aug 2026 23:37:24 +0600 Subject: [PATCH 04/10] feat: add SPM support with CocoaPods backward compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- example/ios/.gitignore | 2 + example/ios/Podfile | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 92 ++++++++++--------- .../xcshareddata/xcschemes/Runner.xcscheme | 18 ++++ ios/.gitignore | 4 +- ios/Assets/.gitkeep | 0 ios/optimizely-flutter-sdk/Package.swift | 35 +++++++ .../optimizely_flutter_sdk}/Constants.swift | 0 .../OptimizelyConfig+Extension.swift | 0 .../OptimizelyFlutterLogger.swift | 0 .../OptimizelyFlutterSdkPlugin.m | 2 +- .../SwiftOptimizelyFlutterSdkPlugin.swift | 0 .../optimizely_flutter_sdk}/Utils.swift | 0 .../OptimizelyFlutterSdkPlugin.h | 0 ios/optimizely_flutter_sdk | 1 + ios/optimizely_flutter_sdk.podspec | 2 +- pubspec.yaml | 2 +- specs/001-cocoapods-to-spm-migration/tasks.md | 26 +++--- 18 files changed, 127 insertions(+), 59 deletions(-) delete mode 100644 ios/Assets/.gitkeep create mode 100644 ios/optimizely-flutter-sdk/Package.swift rename ios/{Classes/HelperClasses => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/Constants.swift (100%) rename ios/{Classes/HelperClasses => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/OptimizelyConfig+Extension.swift (100%) rename ios/{Classes => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/OptimizelyFlutterLogger.swift (100%) rename ios/{Classes => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/OptimizelyFlutterSdkPlugin.m (96%) rename ios/{Classes => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/SwiftOptimizelyFlutterSdkPlugin.swift (100%) rename ios/{Classes/HelperClasses => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk}/Utils.swift (100%) rename ios/{Classes => optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk}/OptimizelyFlutterSdkPlugin.h (100%) create mode 120000 ios/optimizely_flutter_sdk diff --git a/example/ios/.gitignore b/example/ios/.gitignore index 7a7f987..ecbb615 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -27,6 +27,8 @@ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* +**/swiftpm/ + # Exceptions to above rules. !default.mode1v3 !default.mode2v3 diff --git a/example/ios/Podfile b/example/ios/Podfile index 313ea4a..f17bddc 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '11.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 56af059..7a54079 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,11 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - E567413E4441EFD8E182B8E3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFCE984DC7CBCFB140E88864 /* Pods_Runner.framework */; }; + E7471FE6544841B0C836D129 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1798EC3215B94C8A840B51 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -30,15 +31,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0199F036B95C7315646FF6B8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2407820457C3954D6394333D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3D86A8B550CB0FBA7A8F2A03 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; }; + 78DABEA22ED26510000E7860 /* optimizely_flutter_sdk */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = optimizely_flutter_sdk; path = ../../ios/optimizely_flutter_sdk; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8E60C66DA76D705E5A9DCACA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -46,7 +48,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CFCE984DC7CBCFB140E88864 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + ACF0C93900CB1B99B5E9C0CE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D74274CA5486F97165BE309E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + DC1798EC3215B94C8A840B51 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,7 +58,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E567413E4441EFD8E182B8E3 /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + E7471FE6544841B0C836D129 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -64,17 +69,17 @@ 3B54408533A9D1A272B285B5 /* Pods */ = { isa = PBXGroup; children = ( - 0199F036B95C7315646FF6B8 /* Pods-Runner.debug.xcconfig */, - 8E60C66DA76D705E5A9DCACA /* Pods-Runner.release.xcconfig */, - 3D86A8B550CB0FBA7A8F2A03 /* Pods-Runner.profile.xcconfig */, + D74274CA5486F97165BE309E /* Pods-Runner.debug.xcconfig */, + 2407820457C3954D6394333D /* Pods-Runner.release.xcconfig */, + ACF0C93900CB1B99B5E9C0CE /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; - 812DB522D2DCFB756795F343 /* Frameworks */ = { + 5922E7F1B4E66D8046FD9D1E /* Frameworks */ = { isa = PBXGroup; children = ( - CFCE984DC7CBCFB140E88864 /* Pods_Runner.framework */, + DC1798EC3215B94C8A840B51 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -82,6 +87,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78DABEA22ED26510000E7860 /* optimizely_flutter_sdk */, + 784666492D4C4C64000A1A5F /* FlutterFramework */, + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -97,7 +105,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 3B54408533A9D1A272B285B5 /* Pods */, - 812DB522D2DCFB756795F343 /* Frameworks */, + 5922E7F1B4E66D8046FD9D1E /* Frameworks */, ); sourceTree = ""; }; @@ -131,20 +139,22 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - CC1B24FA947A559E173BFFB3 /* [CP] Check Pods Manifest.lock */, + 73C63109C125F11478860EB1 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 8AD3511808907367925A4750 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -173,6 +183,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -213,21 +226,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 8AD3511808907367925A4750 /* [CP] Embed Pods Frameworks */ = { + 73C63109C125F11478860EB1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -245,28 +263,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - CC1B24FA947A559E173BFFB3 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -552,6 +548,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9c12df5..5db441f 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + ) #import #else diff --git a/ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift similarity index 100% rename from ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift rename to ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift diff --git a/ios/Classes/HelperClasses/Utils.swift b/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Utils.swift similarity index 100% rename from ios/Classes/HelperClasses/Utils.swift rename to ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Utils.swift diff --git a/ios/Classes/OptimizelyFlutterSdkPlugin.h b/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h similarity index 100% rename from ios/Classes/OptimizelyFlutterSdkPlugin.h rename to ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h diff --git a/ios/optimizely_flutter_sdk b/ios/optimizely_flutter_sdk new file mode 120000 index 0000000..ddde28f --- /dev/null +++ b/ios/optimizely_flutter_sdk @@ -0,0 +1 @@ +optimizely-flutter-sdk \ No newline at end of file diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index 393e374..f2403dd 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.license = { :type => "Apache License, Version 2.0", :file => "../LICENSE" } s.author = { "Optimizely" => "support@optimizely.com" } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/**/*' s.dependency 'Flutter' s.dependency 'OptimizelySwiftSDK', '5.4.2' s.platform = :ios, '10.0' diff --git a/pubspec.yaml b/pubspec.yaml index 7aa3fb7..d4c9ea2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ flutter: package: com.optimizely.optimizely_flutter_sdk pluginClass: OptimizelyFlutterSdkPlugin ios: - pluginClass: OptimizelyFlutterSdkPlugin + pluginClass: SwiftOptimizelyFlutterSdkPlugin macos: pluginClass: OptimizelyFlutterSdkPlugin windows: diff --git a/specs/001-cocoapods-to-spm-migration/tasks.md b/specs/001-cocoapods-to-spm-migration/tasks.md index 64901bf..a8de5c5 100644 --- a/specs/001-cocoapods-to-spm-migration/tasks.md +++ b/specs/001-cocoapods-to-spm-migration/tasks.md @@ -18,8 +18,8 @@ **Purpose**: Verify prerequisites and prepare for migration -- [ ] T001 Verify OptimizelySwiftSDK git tag format for version 5.4.2 on https://github.com/optimizely/swift-sdk (confirm tag is `5.4.2` vs `v5.4.2` and note the exact string for Package.swift) -- [ ] T002 Create SPM directory structure: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/` +- [x] T001 Verify OptimizelySwiftSDK git tag format for version 5.4.2 on https://github.com/optimizely/swift-sdk (confirm tag is `5.4.2` vs `v5.4.2` and note the exact string for Package.swift) +- [x] T002 Create SPM directory structure: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/` --- @@ -29,17 +29,17 @@ **CRITICAL**: No user story work can begin until this phase is complete -- [ ] T003 Move `ios/Classes/OptimizelyFlutterSdkPlugin.h` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h` -- [ ] T004 Move `ios/Classes/OptimizelyFlutterSdkPlugin.m` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m` and update its import path from `#import "OptimizelyFlutterSdkPlugin.h"` to `#import "./include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h"` -- [ ] T005 [P] Move `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift` -- [ ] T006 [P] Move `ios/Classes/OptimizelyFlutterLogger.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift` -- [ ] T007 [P] Move `ios/Classes/HelperClasses/Constants.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Constants.swift` -- [ ] T008 [P] Move `ios/Classes/HelperClasses/OptimizelyConfig+Extension.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift` -- [ ] T009 [P] Move `ios/Classes/HelperClasses/Utils.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Utils.swift` -- [ ] T010 Remove empty `ios/Classes/` directory (including `HelperClasses/` subdirectory) -- [ ] T011 Remove empty `ios/Assets/` directory -- [ ] T012 Create `ios/optimizely_flutter_sdk/Package.swift` with: swift-tools-version 5.9, iOS platform 10.0, FlutterFramework dependency, OptimizelySwiftSDK via `https://github.com/optimizely/swift-sdk.git` with exact version pinning, and `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` in target -- [ ] T013 Verify `flutter pub get` succeeds from SDK repo root after file restructuring +- [x] T003 Move `ios/Classes/OptimizelyFlutterSdkPlugin.h` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h` +- [x] T004 Move `ios/Classes/OptimizelyFlutterSdkPlugin.m` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m` and update its import path from `#import "OptimizelyFlutterSdkPlugin.h"` to `#import "./include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h"` +- [x] T005 [P] Move `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift` +- [x] T006 [P] Move `ios/Classes/OptimizelyFlutterLogger.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift` +- [x] T007 [P] Move `ios/Classes/HelperClasses/Constants.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Constants.swift` +- [x] T008 [P] Move `ios/Classes/HelperClasses/OptimizelyConfig+Extension.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift` +- [x] T009 [P] Move `ios/Classes/HelperClasses/Utils.swift` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Utils.swift` +- [x] T010 Remove empty `ios/Classes/` directory (including `HelperClasses/` subdirectory) +- [x] T011 Remove empty `ios/Assets/` directory +- [x] T012 Create `ios/optimizely_flutter_sdk/Package.swift` with: swift-tools-version 5.9, iOS platform 10.0, FlutterFramework dependency, OptimizelySwiftSDK via `https://github.com/optimizely/swift-sdk.git` with exact version pinning, and `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` in target +- [x] T013 Verify `flutter pub get` succeeds from SDK repo root after file restructuring **Checkpoint**: SPM package structure is in place, all source files are in their new locations, Package.swift is created From 68e872983ee4453af9e920ce2e0ec9413c39674a Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Fri, 21 Aug 2026 00:06:45 +0600 Subject: [PATCH 05/10] chore: simplify iOS directory layout and add CI version drift check - 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) --- .github/scripts/check-version-drift.sh | 27 +++++++++++++ .github/workflows/flutter.yml | 10 ++++- CLAUDE.md | 17 +++++--- README.md | 2 +- analysis_options.yaml | 7 ++++ .../Constants.swift | 0 .../OptimizelyConfig+Extension.swift | 0 .../OptimizelyFlutterLogger.swift | 0 .../OptimizelyFlutterSdkPlugin.m | 0 .../SwiftOptimizelyFlutterSdkPlugin.swift | 0 .../Utils.swift | 0 .../OptimizelyFlutterSdkPlugin.h | 0 ios/optimizely-flutter-sdk/Package.swift | 1 + ios/optimizely_flutter_sdk.podspec | 2 +- specs/001-cocoapods-to-spm-migration/tasks.md | 40 +++++++++---------- 15 files changed, 78 insertions(+), 28 deletions(-) create mode 100755 .github/scripts/check-version-drift.sh rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/Constants.swift (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/OptimizelyConfig+Extension.swift (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/OptimizelyFlutterLogger.swift (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/OptimizelyFlutterSdkPlugin.m (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/SwiftOptimizelyFlutterSdkPlugin.swift (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/Utils.swift (100%) rename ios/optimizely-flutter-sdk/{Sources/optimizely_flutter_sdk => Classes}/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h (100%) diff --git a/.github/scripts/check-version-drift.sh b/.github/scripts/check-version-drift.sh new file mode 100755 index 0000000..d70ca3a --- /dev/null +++ b/.github/scripts/check-version-drift.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Verify OptimizelySwiftSDK version is the same in both podspec and Package.swift. +# Exits non-zero if versions diverge (FR-014). + +set -euo pipefail + +PODSPEC="ios/optimizely_flutter_sdk.podspec" +PACKAGE_SWIFT="ios/optimizely-flutter-sdk/Package.swift" + +PODSPEC_VER=$(grep "OptimizelySwiftSDK" "$PODSPEC" | grep -oE "'[0-9]+\.[0-9]+\.[0-9]+'" | tr -d "'") +SPM_VER=$(grep 'exact' "$PACKAGE_SWIFT" | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"') + +if [ -z "$PODSPEC_VER" ] || [ -z "$SPM_VER" ]; then + echo "FAIL: Could not extract version from one or both files" + echo " podspec: ${PODSPEC_VER:-}" + echo " Package.swift: ${SPM_VER:-}" + exit 1 +fi + +if [ "$PODSPEC_VER" != "$SPM_VER" ]; then + echo "FAIL: OptimizelySwiftSDK version mismatch" + echo " podspec: $PODSPEC_VER" + echo " Package.swift: $SPM_VER" + exit 1 +fi + +echo "OK: OptimizelySwiftSDK version matches ($PODSPEC_VER)" diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 4aa2445..e813e8f 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -17,9 +17,17 @@ on: # default: "master" jobs: + version_drift_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check OptimizelySwiftSDK version parity + run: bash .github/scripts/check-version-drift.sh + unit_test_coverage: runs-on: macos-latest - + needs: version_drift_check + steps: - uses: actions/checkout@v3 - name: Install Flutter diff --git a/CLAUDE.md b/CLAUDE.md index 7d68689..f1da8f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,6 @@ Optimizely Flutter SDK - Cross-platform plugin wrapping native Optimizely SDKs ( ```bash # Setup flutter pub get -cd ios && pod install # iOS dependencies # Testing flutter test # All tests @@ -74,11 +73,17 @@ Native Optimizely SDKs (check podspec/build.gradle for current versions) ## Version Management -**Three locations must stay synchronized:** +**Three locations must stay synchronized (SDK version):** 1. `pubspec.yaml` → `version: X.Y.Z` 2. `lib/package_info.dart` → `version = 'X.Y.Z'` 3. `README.md` → Installation example `^X.Y.Z` +**Two locations must stay synchronized (native iOS SDK version):** +1. `ios/optimizely_flutter_sdk.podspec` → `s.dependency 'OptimizelySwiftSDK', 'A.B.C'` +2. `ios/optimizely-flutter-sdk/Package.swift` → `.package(url: "...swift-sdk.git", exact: "A.B.C")` + +A CI check (`.github/scripts/check-version-drift.sh`) enforces parity between these two files. + ## Release Workflow ### With Claude Code (Recommended) @@ -187,7 +192,7 @@ Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTR **iOS:** - Minimum: iOS 10.0 - Swift: 5.0 -- Native SDK: OptimizelySwiftSDK (see ios/optimizely_flutter_sdk.podspec for current version) +- Native SDK: OptimizelySwiftSDK (see ios/optimizely_flutter_sdk.podspec and ios/optimizely-flutter-sdk/Package.swift for current version) ## Key Implementation Files @@ -203,9 +208,11 @@ Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTR - `android/build.gradle` - Dependencies & SDK versions **iOS Layer:** -- `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` - Plugin implementation (786 LOC) -- `ios/Classes/OptimizelyFlutterLogger.swift` - Logger bridge (main-thread dispatch) +- `ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` - Plugin implementation (786 LOC) +- `ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift` - Logger bridge (main-thread dispatch) +- `ios/optimizely-flutter-sdk/Package.swift` - SPM dependencies - `ios/optimizely_flutter_sdk.podspec` - CocoaPods dependencies +- `ios/optimizely_flutter_sdk` - Symlink to `optimizely-flutter-sdk/` (required for Flutter SPM detection) For additional context about technologies to be used, project structure, diff --git a/README.md b/README.md index 744388c..bc688b0 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ See the [pubspec.yaml](https://github.com/optimizely/optimizely-flutter-sdk/blob On the Android platform, the SDK requires a minimum SDK version of 21 or higher and compile SDK version of 32. -On the iOS platform, the SDK requires a minimum version of 10.0. +On the iOS platform, the SDK requires a minimum version of 10.0. The SDK supports both Swift Package Manager (SPM) and CocoaPods for iOS dependency resolution. Flutter 3.44+ uses SPM by default; older versions use CocoaPods. Other Flutter platforms are not currently supported by this SDK. diff --git a/analysis_options.yaml b/analysis_options.yaml index 67d44e7..2a7f74d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -4,6 +4,13 @@ analyzer: exclude: - example/** - test/** + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Constants.swift b/ios/optimizely-flutter-sdk/Classes/Constants.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Constants.swift rename to ios/optimizely-flutter-sdk/Classes/Constants.swift diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift b/ios/optimizely-flutter-sdk/Classes/OptimizelyConfig+Extension.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift rename to ios/optimizely-flutter-sdk/Classes/OptimizelyConfig+Extension.swift diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift b/ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift rename to ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m b/ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterSdkPlugin.m similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m rename to ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterSdkPlugin.m diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift rename to ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Utils.swift b/ios/optimizely-flutter-sdk/Classes/Utils.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/Utils.swift rename to ios/optimizely-flutter-sdk/Classes/Utils.swift diff --git a/ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h b/ios/optimizely-flutter-sdk/Classes/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h similarity index 100% rename from ios/optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h rename to ios/optimizely-flutter-sdk/Classes/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h diff --git a/ios/optimizely-flutter-sdk/Package.swift b/ios/optimizely-flutter-sdk/Package.swift index 44e8293..ea70b2f 100644 --- a/ios/optimizely-flutter-sdk/Package.swift +++ b/ios/optimizely-flutter-sdk/Package.swift @@ -29,6 +29,7 @@ let package = Package( .product(name: "FlutterFramework", package: "FlutterFramework"), .product(name: "Optimizely", package: "swift-sdk") ], + path: "Classes", exclude: ["OptimizelyFlutterSdkPlugin.m", "include"] ) ] diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index f2403dd..fb346a4 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.license = { :type => "Apache License, Version 2.0", :file => "../LICENSE" } s.author = { "Optimizely" => "support@optimizely.com" } s.source = { :path => '.' } - s.source_files = 'optimizely-flutter-sdk/Sources/optimizely_flutter_sdk/**/*' + s.source_files = 'optimizely-flutter-sdk/Classes/**/*' s.dependency 'Flutter' s.dependency 'OptimizelySwiftSDK', '5.4.2' s.platform = :ios, '10.0' diff --git a/specs/001-cocoapods-to-spm-migration/tasks.md b/specs/001-cocoapods-to-spm-migration/tasks.md index a8de5c5..25deac0 100644 --- a/specs/001-cocoapods-to-spm-migration/tasks.md +++ b/specs/001-cocoapods-to-spm-migration/tasks.md @@ -53,10 +53,10 @@ ### Implementation for User Story 1 -- [ ] T014 [US1] Run `flutter config --enable-swift-package-manager` to enable SPM for testing -- [ ] T015 [US1] Run `flutter clean && flutter pub get` from `example/` directory -- [ ] T016 [US1] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory with SPM resolving the swift-sdk package -- [ ] T017 [US1] Run `flutter test` from SDK repo root and verify all existing unit tests pass with zero failures +- [x] T014 [US1] Run `flutter config --enable-swift-package-manager` to enable SPM for testing +- [x] T015 [US1] Run `flutter clean && flutter pub get` from `example/` directory +- [x] T016 [US1] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory with SPM resolving the swift-sdk package +- [x] T017 [US1] Run `flutter test` from SDK repo root and verify all existing unit tests pass with zero failures **Checkpoint**: SDK plugin builds for iOS via SPM. Example app compiles via SPM. All unit tests pass. @@ -70,12 +70,12 @@ ### Implementation for User Story 2 -- [ ] T018 [US2] Update `ios/optimizely_flutter_sdk.podspec`: change `s.source_files` from `'Classes/**/*'` to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*'` -- [ ] T019 [US2] Run `pod lib lint ios/optimizely_flutter_sdk.podspec --allow-warnings` to verify podspec validity with new source paths -- [ ] T020 [US2] Run `flutter config --no-enable-swift-package-manager` to test CocoaPods path -- [ ] T021 [US2] Run `flutter clean && flutter pub get` from `example/` directory -- [ ] T022 [US2] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory via CocoaPods -- [ ] T023 [US2] Run `flutter test` from SDK repo root and verify all existing unit tests still pass +- [x] T018 [US2] Update `ios/optimizely_flutter_sdk.podspec`: change `s.source_files` from `'Classes/**/*'` to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*'` +- [x] T019 [US2] Run `pod lib lint ios/optimizely_flutter_sdk.podspec --allow-warnings` to verify podspec validity with new source paths +- [x] T020 [US2] Run `flutter config --no-enable-swift-package-manager` to test CocoaPods path +- [x] T021 [US2] Run `flutter clean && flutter pub get` from `example/` directory +- [x] T022 [US2] Verify `flutter build ios --simulator --no-codesign` succeeds from `example/` directory via CocoaPods +- [x] T023 [US2] Run `flutter test` from SDK repo root and verify all existing unit tests still pass **Checkpoint**: SDK plugin builds for iOS via CocoaPods with updated source paths. Podspec lints clean. All unit tests pass. @@ -89,8 +89,8 @@ ### Implementation for User Story 3 -- [ ] T024 [US3] Ensure test app's dependency on `optimizely_flutter_sdk` resolves the SPM-enabled version (check `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/pubspec.yaml` path dependency) -- [ ] T025 [US3] Update test app CI workflow at `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/.github/workflows/ios.yml`: add `flutter config --enable-swift-package-manager` step before the build step; keep existing `brew install cocoapods` + `pod repo update` steps as fallback for CocoaPods backward compatibility +- [x] T024 [US3] Ensure test app's dependency on `optimizely_flutter_sdk` resolves the SPM-enabled version (check `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/pubspec.yaml` path dependency) +- [x] T025 [US3] Update test app CI workflow at `/Users/muzahidul.islam/workspace/optimizely-flutter-testapp/.github/workflows/ios.yml`: add `flutter config --enable-swift-package-manager` step before the build step; keep existing `brew install cocoapods` + `pod repo update` steps as fallback for CocoaPods backward compatibility - [ ] T026 [US3] Run `flutter config --enable-swift-package-manager && flutter clean && flutter pub get` from test app repo root - [ ] T027 [US3] Verify `flutter build ios --simulator --no-codesign` succeeds from test app repo with SPM resolving dependencies - [ ] T028 [US3] Run integration tests on iOS simulator and verify all e2e test cases pass with zero failures @@ -107,9 +107,9 @@ ### Implementation for User Story 4 -- [ ] T029 [US4] Create version drift check script at `.github/scripts/check-version-drift.sh` (FR-014) that extracts OptimizelySwiftSDK version from both `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift`, compares them, and exits non-zero if they diverge -- [ ] T030 [US4] Integrate version drift check into SDK CI workflow `.github/workflows/flutter.yml` as a job step that runs before build jobs -- [ ] T031 [US4] Review and confirm SDK CI `.github/workflows/flutter.yml` needs no other changes (no explicit pod commands exist) +- [x] T029 [US4] Create version drift check script at `.github/scripts/check-version-drift.sh` (FR-014) that extracts OptimizelySwiftSDK version from both `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift`, compares them, and exits non-zero if they diverge +- [x] T030 [US4] Integrate version drift check into SDK CI workflow `.github/workflows/flutter.yml` as a job step that runs before build jobs +- [x] T031 [US4] Review and confirm SDK CI `.github/workflows/flutter.yml` needs no other changes (no explicit pod commands exist) - [ ] T032 [US4] Push branch and create PR to trigger all five CI jobs: `unit_test_coverage`, `build_test_android`, `build_test_ios`, `integration_android_tests`, `integration_ios_tests` - [ ] T033 [US4] Verify all five CI jobs pass green, including the version drift check @@ -121,11 +121,11 @@ **Purpose**: Documentation updates, version sync, and final validation -- [ ] T034 [P] Update `CLAUDE.md`: remove `cd ios && pod install` from setup commands (no longer needed), add note about dual SPM/CocoaPods support, update iOS setup instructions, add `ios/optimizely_flutter_sdk/Package.swift` to version pinning source-of-truth list in the Native SDK Version Pinning section -- [ ] T035 [P] Update `README.md`: add note about SPM support alongside existing CocoaPods instructions if applicable (FR-009) -- [ ] T036 Verify version consistency: confirm `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift` both declare OptimizelySwiftSDK 5.4.2 -- [ ] T037 Run full `flutter test` from SDK repo root — verify zero failures (final regression check) -- [ ] T038 Run full `flutter analyze` from SDK repo root — verify zero issues +- [x] T034 [P] Update `CLAUDE.md`: remove `cd ios && pod install` from setup commands (no longer needed), add note about dual SPM/CocoaPods support, update iOS setup instructions, add `ios/optimizely_flutter_sdk/Package.swift` to version pinning source-of-truth list in the Native SDK Version Pinning section +- [x] T035 [P] Update `README.md`: add note about SPM support alongside existing CocoaPods instructions if applicable (FR-009) +- [x] T036 Verify version consistency: confirm `ios/optimizely_flutter_sdk.podspec` and `ios/optimizely_flutter_sdk/Package.swift` both declare OptimizelySwiftSDK 5.4.2 +- [x] T037 Run full `flutter test` from SDK repo root — verify zero failures (final regression check) +- [x] T038 Run full `flutter analyze` from SDK repo root — verify zero issues - [ ] T039 Run quickstart.md validation scenarios end-to-end (both SPM and CocoaPods paths, including version drift check) --- From 2fb3c0de602ca2ef3fa15b3a76f10d4dae6dc458 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Fri, 21 Aug 2026 18:26:26 +0600 Subject: [PATCH 06/10] ci: pin Flutter 3.44.0 across all jobs and enable SPM for iOS build - 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) --- .github/workflows/flutter.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index e813e8f..e8399b2 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -33,6 +33,7 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2 with: + flutter-version: '3.44.0' channel: 'stable' architecture: x64 - name: Install app dependencies @@ -135,32 +136,32 @@ jobs: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - channel: 'stable' # or: 'beta' or 'master' + flutter-version: '3.44.0' + channel: 'stable' cache: true - cache-key: 'flutter-:os:-:arch:-:channel:-:version:-:hash:' # optional, change this to force refresh cache - cache-path: ${{ runner.tool_cache }}/flutter # optional, change this to specify the cache path - architecture: x64 # optional, x64 or arm64 - - run: flutter --version - + cache-key: 'flutter-:os:-:arch:-:channel:-:version:-:hash:' + cache-path: ${{ runner.tool_cache }}/flutter + architecture: x64 - name: Setup java uses: actions/setup-java@v2 with: distribution: 'zulu' java-version: '11' - - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.0.5' - run: flutter pub get - run: flutter test build_test_ios: runs-on: macos-latest - + needs: version_drift_check + steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: + flutter-version: '3.44.0' channel: 'stable' architecture: x64 + - name: Enable Swift Package Manager + run: flutter config --enable-swift-package-manager - run: flutter pub get - run: flutter test From fd67395c193d7954605e6b5b58d4f1a58bab25da Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Fri, 21 Aug 2026 18:27:17 +0600 Subject: [PATCH 07/10] chore: lower example app iOS platform to 11.0 Co-Authored-By: Claude Opus 4.6 (1M context) --- example/ios/Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ios/Podfile b/example/ios/Podfile index f17bddc..313ea4a 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '15.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' From 6ad7607bfa22ee09ce86b5ed61d59a64186bb656 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Fri, 21 Aug 2026 18:34:53 +0600 Subject: [PATCH 08/10] docs: update spec artifacts to reflect actual SPM implementation - 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) --- specs/001-cocoapods-to-spm-migration/plan.md | 59 ++++++++-------- .../research.md | 68 +++++++++++-------- specs/001-cocoapods-to-spm-migration/spec.md | 17 +++-- 3 files changed, 77 insertions(+), 67 deletions(-) diff --git a/specs/001-cocoapods-to-spm-migration/plan.md b/specs/001-cocoapods-to-spm-migration/plan.md index 86af321..36edbdd 100644 --- a/specs/001-cocoapods-to-spm-migration/plan.md +++ b/specs/001-cocoapods-to-spm-migration/plan.md @@ -6,7 +6,7 @@ ## Summary -Add Swift Package Manager support to the Optimizely Flutter SDK's iOS plugin while retaining CocoaPods backward compatibility. Source files move from `ios/Classes/` to the SPM-standard layout at `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. Both the new Package.swift and the existing podspec (with updated source paths) reference the same files. Flutter selects the dependency mechanism based on version and configuration. The Dart and Android layers are unaffected. +Add Swift Package Manager support to the Optimizely Flutter SDK's iOS plugin while retaining CocoaPods backward compatibility. Source files move from `ios/Classes/` to `ios/optimizely-flutter-sdk/Classes/`. The hyphenated directory name is required because SPM normalizes package identity by replacing underscores with hyphens (SE-0292); a symlink `ios/optimizely_flutter_sdk` → `ios/optimizely-flutter-sdk/` lets Flutter discover the Package.swift. Both the new Package.swift (using `path: "Classes"` override) and the existing podspec (with updated source paths) reference the same files. ObjC files are excluded from the SPM target due to mixed-language limitations but remain for CocoaPods. Flutter selects the dependency mechanism based on version and configuration. The Dart and Android layers are unaffected. ## Technical Context @@ -69,48 +69,44 @@ specs/001-cocoapods-to-spm-migration/ ```text # SDK repo — iOS layer changes ios/ -├── optimizely_flutter_sdk.podspec # UPDATED: source_files path changed -├── optimizely_flutter_sdk/ # NEW: SPM package directory -│ ├── Package.swift # NEW: SPM manifest -│ └── Sources/ -│ └── optimizely_flutter_sdk/ # NEW: SPM sources -│ ├── include/ -│ │ └── optimizely_flutter_sdk/ -│ │ └── OptimizelyFlutterSdkPlugin.h # MOVED from Classes/ -│ ├── OptimizelyFlutterSdkPlugin.m # MOVED from Classes/ (import path updated) -│ ├── SwiftOptimizelyFlutterSdkPlugin.swift # MOVED from Classes/ -│ ├── OptimizelyFlutterLogger.swift # MOVED from Classes/ -│ ├── Constants.swift # MOVED from Classes/HelperClasses/ -│ ├── OptimizelyConfig+Extension.swift # MOVED from Classes/HelperClasses/ -│ └── Utils.swift # MOVED from Classes/HelperClasses/ -├── Classes/ # REMOVED (files moved to SPM layout) +├── optimizely_flutter_sdk.podspec # UPDATED: source_files → 'optimizely-flutter-sdk/Classes/**/*' +├── optimizely-flutter-sdk/ # NEW: SPM package directory (hyphens — matches SPM identity) +│ ├── Package.swift # NEW: SPM manifest (path: "Classes" override) +│ └── Classes/ # Source files (shared by SPM and CocoaPods) +│ ├── include/ +│ │ └── optimizely_flutter_sdk/ +│ │ └── OptimizelyFlutterSdkPlugin.h # MOVED (excluded from SPM, used by CocoaPods) +│ ├── OptimizelyFlutterSdkPlugin.m # MOVED (excluded from SPM, used by CocoaPods) +│ ├── SwiftOptimizelyFlutterSdkPlugin.swift # MOVED from Classes/ +│ ├── OptimizelyFlutterLogger.swift # MOVED from Classes/ +│ ├── Constants.swift # MOVED from Classes/HelperClasses/ +│ ├── OptimizelyConfig+Extension.swift # MOVED from Classes/HelperClasses/ +│ └── Utils.swift # MOVED from Classes/HelperClasses/ +├── optimizely_flutter_sdk -> optimizely-flutter-sdk/ # SYMLINK (underscores — Flutter discovery) +├── Classes/ # REMOVED (files moved) └── Assets/ # REMOVED (empty) +# pubspec.yaml — pluginClass changed to SwiftOptimizelyFlutterSdkPlugin + # Example app — NO CHANGES (keeps Podfile for CocoaPods backward compat) example/ios/ ├── Podfile # KEPT ├── Podfile.lock # KEPT (regenerated on next pod install) └── Pods/ # KEPT -# Test app — CI updates + SPM configuration added -optimizely-flutter-testapp/ios/ -├── Podfile # KEPT for CocoaPods fallback -├── Podfile.lock # KEPT -└── Pods/ # KEPT - -# Test app CI +# Test app — CI pinned to Flutter 3.16.0 for CocoaPods validation optimizely-flutter-testapp/.github/workflows/ -└── ios.yml # UPDATED: SPM-compatible build steps +└── ios.yml # UPDATED: Flutter 3.16.0, SPM step removed # SDK CI .github/workflows/ -└── flutter.yml # UPDATED: version drift check step added (FR-014) +└── flutter.yml # UPDATED: Flutter 3.44.0, SPM enabled, version drift check (FR-014) # Dart layer — NO CHANGES # Android layer — NO CHANGES ``` -**Structure Decision**: Source files move from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/` (SPM-standard layout). The podspec's `source_files` is updated to point to the new location. Both dependency mechanisms share the same physical files. ObjC header goes into an `include/` subdirectory per SPM convention. +**Structure Decision**: Source files move from `ios/Classes/` to `ios/optimizely-flutter-sdk/Classes/`. The hyphenated directory name is required by SPM identity normalization (SE-0292). A symlink `ios/optimizely_flutter_sdk` → `ios/optimizely-flutter-sdk/` allows Flutter's plugin discovery to find the Package.swift. Package.swift uses `path: "Classes"` to override the default `Sources//` convention. ObjC files are excluded from the SPM target (mixed-language limitation) but kept for CocoaPods. Both dependency mechanisms share the same physical Swift source files. ## Complexity Tracking @@ -132,8 +128,9 @@ See [research.md](research.md) for detailed findings on: ## Key Risks -1. **SPM version tag**: The podspec pins OptimizelySwiftSDK 5.4.2, but the git tag format on `swift-sdk` repo needs verification (could be `5.4.2` or `v5.4.2`). -2. **ObjC header search paths**: SPM requires headers in an `include/` subdirectory with `cSettings`. This must be validated against both SPM and CocoaPods builds. -3. **Podspec source path**: After moving files, `pod lib lint` must pass to confirm CocoaPods can still find all sources. Note: the current glob is `'Classes/**/*'` (all types), so the updated path must preserve this pattern. -4. **Version drift**: Two files now declare the native SDK version. **Mitigated by FR-014**: a CI validation check MUST fail the build if versions diverge. -5. **Mixed CocoaPods/SPM state**: Resolved — Flutter's tooling handles resolution automatically. No SDK-level detection or intervention needed. +1. **SPM version tag**: Verified — git tag `5.4.2` exists on the `swift-sdk` repo (no `v` prefix). +2. **SPM identity normalization**: SPM 5.6+ normalizes underscores to hyphens in package identity (SE-0292). **Mitigated**: directory uses hyphens (`optimizely-flutter-sdk/`), symlink uses underscores (`optimizely_flutter_sdk`) for Flutter discovery. +3. **Mixed Swift/ObjC**: SPM cannot compile Swift and ObjC in the same target. **Mitigated**: ObjC files excluded from SPM target via `exclude`, kept for CocoaPods. +4. **Podspec source path**: After moving files, the glob `'optimizely-flutter-sdk/Classes/**/*'` includes both Swift and ObjC files for CocoaPods. +5. **Version drift**: Two files now declare the native SDK version. **Mitigated by FR-014**: a CI validation check MUST fail the build if versions diverge. +6. **Mixed CocoaPods/SPM state**: Resolved — Flutter's tooling handles resolution automatically. No SDK-level detection or intervention needed. diff --git a/specs/001-cocoapods-to-spm-migration/research.md b/specs/001-cocoapods-to-spm-migration/research.md index 23808e8..5167d92 100644 --- a/specs/001-cocoapods-to-spm-migration/research.md +++ b/specs/001-cocoapods-to-spm-migration/research.md @@ -10,15 +10,19 @@ **Key Findings**: -- **Package.swift location**: `ios/optimizely_flutter_sdk/Package.swift` -- **Source files location**: `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/` -- Source files move from `ios/Classes/` to the new SPM sources directory +- **Package.swift location**: `ios/optimizely-flutter-sdk/Package.swift` (hyphens — matches SPM normalized identity) +- **Source files location**: `ios/optimizely-flutter-sdk/Classes/` (simplified from SPM default `Sources//` using `path: "Classes"` override) +- **Symlink**: `ios/optimizely_flutter_sdk` → `ios/optimizely-flutter-sdk/` (underscores → hyphens, required for Flutter's plugin discovery) +- Source files move from `ios/Classes/` to `ios/optimizely-flutter-sdk/Classes/` - Library product name replaces underscores with hyphens: `optimizely-flutter-sdk` -- `FlutterFramework` dependency with `path: "../FlutterFramework"` is mandatory — Flutter tooling provides this at build time -- **Podspec stays**: Update `s.source_files` from `'Classes/**/*.swift'` to `'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*.swift'` +- `FlutterFramework` dependency with `path: "../FlutterFramework"` is mandatory — Flutter tooling provides this at build time (same as CocoaPods' `s.dependency 'Flutter'`) +- **Podspec stays**: Update `s.source_files` from `'Classes/**/*'` to `'optimizely-flutter-sdk/Classes/**/*'` - Both Package.swift and podspec reference the same files in the new location +- **SPM identity normalization** (SE-0292): SPM 5.6+ normalizes underscores to hyphens in package identity. Directory must use hyphens to match. +- **Mixed Swift/ObjC limitation**: SPM cannot have Swift and ObjC in the same target. ObjC files are excluded from the SPM target via `exclude` but kept on disk for CocoaPods. +- **pluginClass change**: Changed from `OptimizelyFlutterSdkPlugin` (ObjC) to `SwiftOptimizelyFlutterSdkPlugin` (Swift) in `pubspec.yaml` for direct SPM registration. -**Package.swift Template**: +**Package.swift (actual)**: ```swift // swift-tools-version: 5.9 import PackageDescription @@ -40,9 +44,8 @@ let package = Package( .product(name: "FlutterFramework", package: "FlutterFramework"), .product(name: "Optimizely", package: "swift-sdk") ], - cSettings: [ - .headerSearchPath("include/optimizely_flutter_sdk") - ] + path: "Classes", + exclude: ["OptimizelyFlutterSdkPlugin.m", "include"] ) ] ) @@ -52,12 +55,13 @@ let package = Package( ```ruby # Before: s.source_files = 'Classes/**/*' # After: -s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' +s.source_files = 'optimizely-flutter-sdk/Classes/**/*' ``` **Alternatives Considered**: - Clean cut (remove CocoaPods entirely): Rejected by user — backward compatibility required - Keep source files in `ios/Classes/` and symlink for SPM: Rejected — fragile, not recommended by Flutter +- Use `Sources/optimizely_flutter_sdk/` (SPM default layout): Rejected — `Classes/` is simpler and SPM's `path` parameter officially supports it (Apple docs: `Target.path`) ## 2. OptimizelySwiftSDK SPM Compatibility @@ -76,11 +80,11 @@ s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' ## 3. Source File Migration (Dual Support Layout) -**Decision**: Move all source files from `ios/Classes/` to `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/`. ObjC headers go into an `include/` subdirectory. Update podspec paths to match. +**Decision**: Move all source files from `ios/Classes/` to `ios/optimizely-flutter-sdk/Classes/`. ObjC files are excluded from the SPM target but kept on disk for CocoaPods. Update podspec paths to match. -**Rationale**: This is the canonical Flutter dual-support layout. Both Package.swift and podspec reference the same physical files, just via different path declarations. +**Rationale**: The `Classes/` directory name is simpler than SPM's default `Sources//` convention. SPM's `path` parameter officially supports custom source paths. The hyphenated directory name is required because SPM normalizes package identity by replacing underscores with hyphens (SE-0292). -**Current Files**: +**Current Files** (before migration): - `ios/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` — main plugin - `ios/Classes/OptimizelyFlutterLogger.swift` — logger bridge - `ios/Classes/HelperClasses/Constants.swift` — constants @@ -91,23 +95,25 @@ s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' **New Layout**: ``` -ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/ -├── include/ -│ └── optimizely_flutter_sdk/ -│ └── OptimizelyFlutterSdkPlugin.h # ObjC public header -├── OptimizelyFlutterSdkPlugin.m # ObjC bridge implementation -├── SwiftOptimizelyFlutterSdkPlugin.swift -├── OptimizelyFlutterLogger.swift -├── Constants.swift -├── OptimizelyConfig+Extension.swift -└── Utils.swift +ios/optimizely-flutter-sdk/ # Real directory (hyphens — matches SPM identity) +├── Package.swift # SPM manifest +└── Classes/ # Source files (path override in Package.swift) + ├── include/ + │ └── optimizely_flutter_sdk/ + │ └── OptimizelyFlutterSdkPlugin.h # ObjC header (excluded from SPM, used by CocoaPods) + ├── OptimizelyFlutterSdkPlugin.m # ObjC bridge (excluded from SPM, used by CocoaPods) + ├── SwiftOptimizelyFlutterSdkPlugin.swift + ├── OptimizelyFlutterLogger.swift + ├── Constants.swift + ├── OptimizelyConfig+Extension.swift + └── Utils.swift +ios/optimizely_flutter_sdk -> optimizely-flutter-sdk/ # Symlink (underscores — Flutter discovery) ``` **ObjC Bridging Files**: -- Public header (`.h`) goes in `include/optimizely_flutter_sdk/` subdirectory -- Implementation (`.m`) stays in the main sources directory -- Import path in `.m` must be updated: `#import "OptimizelyFlutterSdkPlugin.h"` → `#import "./include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h"` -- Package.swift needs `cSettings: [.headerSearchPath("include/optimizely_flutter_sdk")]` +- ObjC files (`.m`, `.h`) are excluded from the SPM target because SPM does not support mixed Swift/ObjC in a single target +- They remain on disk for CocoaPods builds, which still compile them +- Package.swift uses `exclude: ["OptimizelyFlutterSdkPlugin.m", "include"]` to skip them ## 4. Podspec Updates @@ -119,7 +125,7 @@ ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/ s.source_files = 'Classes/**/*' # After -s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' +s.source_files = 'optimizely-flutter-sdk/Classes/**/*' ``` **Verification**: Run `pod lib lint optimizely_flutter_sdk.podspec` after changes to ensure CocoaPods can still find and compile all source files. @@ -138,9 +144,11 @@ s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' ## 7. SDK CI Impact -**Decision**: SDK CI workflows (`flutter.yml`) require no changes — they don't have explicit `pod install` calls. The test app CI (`ios.yml`) may need adjustment if it currently uses `brew install cocoapods` and the CI Flutter version defaults to SPM. +**Decision**: SDK CI workflows (`flutter.yml`) are updated to pin Flutter 3.44.0 across all jobs and enable SPM for the `build_test_ios` job. The `build_test_ios` job depends on `version_drift_check` to ensure podspec/Package.swift version parity before building. The test app CI (`ios.yml`) is pinned to Flutter 3.16.0 (pre-SPM) to validate the CocoaPods path. -**Note**: The `brew install cocoapods` + `pod repo update` step in test app CI can remain (for CocoaPods fallback) or be removed (if CI Flutter version uses SPM by default). The test app CI should be reviewed but not necessarily changed if it works. +**Changes**: +- SDK CI: All jobs pin Flutter 3.44.0 for consistent caching. `build_test_ios` enables SPM and depends on `version_drift_check`. +- Test app CI: Pinned to Flutter 3.16.0, SPM enable step removed, CocoaPods steps (`brew install cocoapods`, `pod repo update`) remain. ## 8. Version Synchronization (Critical) diff --git a/specs/001-cocoapods-to-spm-migration/spec.md b/specs/001-cocoapods-to-spm-migration/spec.md index f3aaecc..956d47b 100644 --- a/specs/001-cocoapods-to-spm-migration/spec.md +++ b/specs/001-cocoapods-to-spm-migration/spec.md @@ -77,8 +77,11 @@ CI workflows validate that the SDK works with both CocoaPods and SPM. The existi - **Mixed CocoaPods/SPM state (Podfile + SPM-capable Flutter)**: Flutter's tooling handles resolution automatically. The SDK provides both Package.swift and podspec; Flutter picks the appropriate mechanism based on version and project configuration. No SDK-level detection or intervention is needed. - **SPM package URL or version tag format differs from CocoaPods**: Mitigated by FR-006 — both manifests pin the same OptimizelySwiftSDK version. The SPM repository URL must match the official OptimizelySwiftSDK SPM-compatible repository. -- **Podspec source file paths after directory restructuring**: The podspec's `source_files` declaration is updated to point to the new SPM-compatible directory structure (FR-008). Existing CocoaPods users running `pod install` will pick up the new paths automatically. +- **Podspec source file paths after directory restructuring**: The podspec's `source_files` declaration is updated to point to the new directory structure (FR-008). Existing CocoaPods users running `pod install` will pick up the new paths automatically. - **Package.swift and podspec declare different OptimizelySwiftSDK versions**: Prevented by FR-006 and enforced by FR-014 — a CI validation check fails the build if versions diverge. +- **SPM identity normalization (underscore vs hyphen)**: SPM 5.6+ normalizes package identities by replacing underscores with hyphens (SE-0292). The plugin name `optimizely_flutter_sdk` becomes `optimizely-flutter-sdk` in SPM. The real directory uses hyphens (`ios/optimizely-flutter-sdk/`) to match SPM's normalized identity, and a symlink with underscores (`ios/optimizely_flutter_sdk` → `optimizely-flutter-sdk/`) allows Flutter's tooling to discover the Package.swift at the expected path. Both paths are required — without the hyphenated directory SPM resolution fails, without the underscore symlink Flutter won't detect SPM support. +- **Mixed Swift/ObjC in SPM target**: SPM does not support mixed Swift and Objective-C in a single target. The ObjC files (`OptimizelyFlutterSdkPlugin.m`, `OptimizelyFlutterSdkPlugin.h`) are excluded from the SPM target via the `exclude` parameter but remain on disk for CocoaPods builds, which still use them. +- **Plugin registration class change**: The `pluginClass` in `pubspec.yaml` is changed from the ObjC class `OptimizelyFlutterSdkPlugin` to the Swift class `SwiftOptimizelyFlutterSdkPlugin` for direct SPM registration (since SPM cannot compile the ObjC registration shim). ## Requirements *(mandatory)* @@ -91,7 +94,7 @@ CI workflows validate that the SDK works with both CocoaPods and SPM. The existi - **FR-005**: The SDK MUST maintain all existing iOS functionality (MethodChannel handlers, logger bridge, type encoding) without any behavioral changes. - **FR-006**: The podspec and Package.swift MUST declare the same OptimizelySwiftSDK version to prevent version drift. - **FR-007**: The SDK's `pubspec.yaml` plugin declaration for iOS MUST remain compatible with both Flutter's SPM plugin system and CocoaPods. -- **FR-008**: Source files MUST be organized in a directory structure that is accessible from both the SPM Package.swift target and the podspec `source_files` declaration. +- **FR-008**: Source files MUST be organized in a directory structure that is accessible from both the SPM Package.swift target and the podspec `source_files` declaration. The actual layout uses `ios/optimizely-flutter-sdk/Classes/` with SPM's `path: "Classes"` override, and ObjC files excluded from the SPM target via `exclude`. - **FR-009**: The SDK documentation (README.md, CLAUDE.md) MUST be updated to document both CocoaPods and SPM setup paths. - **FR-010**: The SDK MUST retain iOS 10.0 as its minimum deployment target. The test app maintains its own minimum of iOS 13.0. - **FR-011**: All existing SDK unit tests (`flutter test`) MUST pass with zero failures after the changes — no test regressions are acceptable. @@ -101,9 +104,10 @@ CI workflows validate that the SDK works with both CocoaPods and SPM. The existi ### Key Entities -- **Package.swift**: SPM package manifest declaring the OptimizelySwiftSDK dependency and linking the plugin's Swift source files. Coexists with the podspec. -- **optimizely_flutter_sdk.podspec**: Updated CocoaPods spec with source file paths pointing to the new SPM-compatible directory layout. -- **Plugin Registration**: The mechanism by which Flutter discovers and loads the native iOS plugin, which must work through both SPM and CocoaPods. +- **Package.swift**: SPM package manifest at `ios/optimizely-flutter-sdk/Package.swift`, declaring the OptimizelySwiftSDK dependency and linking the plugin's Swift source files. Uses `path: "Classes"` to override the default SPM `Sources//` convention. Coexists with the podspec. +- **optimizely_flutter_sdk.podspec**: Updated CocoaPods spec with `source_files` pointing to `optimizely-flutter-sdk/Classes/**/*`. +- **Plugin Registration**: The `pluginClass` in `pubspec.yaml` is set to `SwiftOptimizelyFlutterSdkPlugin` (Swift class) for SPM compatibility. CocoaPods builds still compile the ObjC shim but Flutter routes registration through the Swift class. +- **Symlink**: `ios/optimizely_flutter_sdk` → `ios/optimizely-flutter-sdk/` — bridges Flutter's underscore-based plugin discovery with SPM's hyphen-normalized package identity. ## Success Criteria *(mandatory)* @@ -134,7 +138,8 @@ CI workflows validate that the SDK works with both CocoaPods and SPM. The existi - The OptimizelySwiftSDK already publishes SPM-compatible releases (it does — the library supports SPM via its Package.swift). - Flutter's SPM integration for plugins is stable enough for production use (Flutter has been shipping SPM support and is actively deprecating CocoaPods). - The SDK retains iOS 10.0 as its minimum deployment target; the test app uses iOS 13.0. These are independent and intentional. -- Source files can be organized in a single directory structure (SPM layout) that both Package.swift and podspec reference via their respective path declarations. +- Source files are organized in `ios/optimizely-flutter-sdk/Classes/`. Both Package.swift (via `path: "Classes"`) and podspec (via `source_files: 'optimizely-flutter-sdk/Classes/**/*'`) reference the same files. +- SPM 5.6+ normalizes package identities by replacing underscores with hyphens (SE-0292). A symlink `ios/optimizely_flutter_sdk` → `ios/optimizely-flutter-sdk/` is required so Flutter can discover the Package.swift. - The example app (`example/`) within the SDK repo will work with whichever dependency mechanism the local Flutter version prefers. - macOS plugin platform (declared but experimental in pubspec.yaml) is out of scope for this effort. - The `optimizely-flutter-testapp` repository is accessible and modifiable as part of this effort. Changes include CI workflow updates and adding SPM configuration to the test app's iOS project. From 860178ade9836d3d7f3ac266cedf4fcd0876068c Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Sat, 22 Aug 2026 00:01:48 +0600 Subject: [PATCH 09/10] refactor: adopt canonical SPM directory layout and remove symlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .github/scripts/check-version-drift.sh | 2 +- CLAUDE.md | 11 +++++------ ios/.gitignore | 3 ++- ios/optimizely_flutter_sdk | 1 - ios/optimizely_flutter_sdk.podspec | 2 +- .../Package.swift | 8 -------- .../Sources/optimizely_flutter_sdk}/Constants.swift | 0 .../OptimizelyConfig+Extension.swift | 0 .../OptimizelyFlutterLogger.swift | 0 .../OptimizelyFlutterSdkPlugin.m | 0 .../SwiftOptimizelyFlutterSdkPlugin.swift | 0 .../Sources/optimizely_flutter_sdk}/Utils.swift | 0 .../OptimizelyFlutterSdkPlugin.h | 0 13 files changed, 9 insertions(+), 18 deletions(-) delete mode 120000 ios/optimizely_flutter_sdk rename ios/{optimizely-flutter-sdk => optimizely_flutter_sdk}/Package.swift (71%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/Constants.swift (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/OptimizelyConfig+Extension.swift (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/OptimizelyFlutterLogger.swift (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/OptimizelyFlutterSdkPlugin.m (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/SwiftOptimizelyFlutterSdkPlugin.swift (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/Utils.swift (100%) rename ios/{optimizely-flutter-sdk/Classes => optimizely_flutter_sdk/Sources/optimizely_flutter_sdk}/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h (100%) diff --git a/.github/scripts/check-version-drift.sh b/.github/scripts/check-version-drift.sh index d70ca3a..e3777f5 100755 --- a/.github/scripts/check-version-drift.sh +++ b/.github/scripts/check-version-drift.sh @@ -5,7 +5,7 @@ set -euo pipefail PODSPEC="ios/optimizely_flutter_sdk.podspec" -PACKAGE_SWIFT="ios/optimizely-flutter-sdk/Package.swift" +PACKAGE_SWIFT="ios/optimizely_flutter_sdk/Package.swift" PODSPEC_VER=$(grep "OptimizelySwiftSDK" "$PODSPEC" | grep -oE "'[0-9]+\.[0-9]+\.[0-9]+'" | tr -d "'") SPM_VER=$(grep 'exact' "$PACKAGE_SWIFT" | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"') diff --git a/CLAUDE.md b/CLAUDE.md index f1da8f6..4e2b952 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,7 @@ Native Optimizely SDKs (check podspec/build.gradle for current versions) **Two locations must stay synchronized (native iOS SDK version):** 1. `ios/optimizely_flutter_sdk.podspec` → `s.dependency 'OptimizelySwiftSDK', 'A.B.C'` -2. `ios/optimizely-flutter-sdk/Package.swift` → `.package(url: "...swift-sdk.git", exact: "A.B.C")` +2. `ios/optimizely_flutter_sdk/Package.swift` → `.package(url: "...swift-sdk.git", exact: "A.B.C")` A CI check (`.github/scripts/check-version-drift.sh`) enforces parity between these two files. @@ -192,7 +192,7 @@ Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTR **iOS:** - Minimum: iOS 10.0 - Swift: 5.0 -- Native SDK: OptimizelySwiftSDK (see ios/optimizely_flutter_sdk.podspec and ios/optimizely-flutter-sdk/Package.swift for current version) +- Native SDK: OptimizelySwiftSDK (see ios/optimizely_flutter_sdk.podspec and ios/optimizely_flutter_sdk/Package.swift for current version) ## Key Implementation Files @@ -208,11 +208,10 @@ Follow [Angular guidelines](https://github.com/angular/angular/blob/master/CONTR - `android/build.gradle` - Dependencies & SDK versions **iOS Layer:** -- `ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift` - Plugin implementation (786 LOC) -- `ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift` - Logger bridge (main-thread dispatch) -- `ios/optimizely-flutter-sdk/Package.swift` - SPM dependencies +- `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift` - Plugin implementation (786 LOC) +- `ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift` - Logger bridge (main-thread dispatch) +- `ios/optimizely_flutter_sdk/Package.swift` - SPM dependencies - `ios/optimizely_flutter_sdk.podspec` - CocoaPods dependencies -- `ios/optimizely_flutter_sdk` - Symlink to `optimizely-flutter-sdk/` (required for Flutter SPM detection) For additional context about technologies to be used, project structure, diff --git a/ios/.gitignore b/ios/.gitignore index d312c76..7b634ab 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -37,4 +37,5 @@ Icon? /Flutter/ephemeral/ /Flutter/flutter_export_environment.sh -.build/ \ No newline at end of file +.build/ +.swiftpm/ \ No newline at end of file diff --git a/ios/optimizely_flutter_sdk b/ios/optimizely_flutter_sdk deleted file mode 120000 index ddde28f..0000000 --- a/ios/optimizely_flutter_sdk +++ /dev/null @@ -1 +0,0 @@ -optimizely-flutter-sdk \ No newline at end of file diff --git a/ios/optimizely_flutter_sdk.podspec b/ios/optimizely_flutter_sdk.podspec index fb346a4..2bd9c5c 100644 --- a/ios/optimizely_flutter_sdk.podspec +++ b/ios/optimizely_flutter_sdk.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| s.license = { :type => "Apache License, Version 2.0", :file => "../LICENSE" } s.author = { "Optimizely" => "support@optimizely.com" } s.source = { :path => '.' } - s.source_files = 'optimizely-flutter-sdk/Classes/**/*' + s.source_files = 'optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/**/*' s.dependency 'Flutter' s.dependency 'OptimizelySwiftSDK', '5.4.2' s.platform = :ios, '10.0' diff --git a/ios/optimizely-flutter-sdk/Package.swift b/ios/optimizely_flutter_sdk/Package.swift similarity index 71% rename from ios/optimizely-flutter-sdk/Package.swift rename to ios/optimizely_flutter_sdk/Package.swift index ea70b2f..aa92218 100644 --- a/ios/optimizely-flutter-sdk/Package.swift +++ b/ios/optimizely_flutter_sdk/Package.swift @@ -1,11 +1,4 @@ // swift-tools-version: 5.9 -// -// NOTE: This package lives in ios/optimizely-flutter-sdk/ (hyphens). -// A symlink ios/optimizely_flutter_sdk (underscores) points here so that -// Flutter's SPM integration can discover the Package.swift. The hyphenated -// directory name is required because SPM normalizes package identity by -// replacing underscores with hyphens — the directory name must match. -// // ObjC files (OptimizelyFlutterSdkPlugin.m/.h) are excluded from the SPM // target because SPM does not support mixed Swift/ObjC in a single target. // They remain on disk for CocoaPods builds, which still use them. @@ -29,7 +22,6 @@ let package = Package( .product(name: "FlutterFramework", package: "FlutterFramework"), .product(name: "Optimizely", package: "swift-sdk") ], - path: "Classes", exclude: ["OptimizelyFlutterSdkPlugin.m", "include"] ) ] diff --git a/ios/optimizely-flutter-sdk/Classes/Constants.swift b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Constants.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/Constants.swift rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Constants.swift diff --git a/ios/optimizely-flutter-sdk/Classes/OptimizelyConfig+Extension.swift b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/OptimizelyConfig+Extension.swift rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyConfig+Extension.swift diff --git a/ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterLogger.swift rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterLogger.swift diff --git a/ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterSdkPlugin.m b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/OptimizelyFlutterSdkPlugin.m rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.m diff --git a/ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/SwiftOptimizelyFlutterSdkPlugin.swift rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/SwiftOptimizelyFlutterSdkPlugin.swift diff --git a/ios/optimizely-flutter-sdk/Classes/Utils.swift b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Utils.swift similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/Utils.swift rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/Utils.swift diff --git a/ios/optimizely-flutter-sdk/Classes/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h b/ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h similarity index 100% rename from ios/optimizely-flutter-sdk/Classes/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h rename to ios/optimizely_flutter_sdk/Sources/optimizely_flutter_sdk/include/optimizely_flutter_sdk/OptimizelyFlutterSdkPlugin.h From 953e5e5445cbc532be1c3bb07ff9f291424ab92b Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Sat, 22 Aug 2026 06:48:08 +0600 Subject: [PATCH 10/10] docs: add Package.swift to constitution Principle VIII Co-Authored-By: Claude Opus 4.6 (1M context) --- .specify/memory/constitution.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 1a0c996..b5f391e 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -141,7 +141,9 @@ The source-of-truth for current versions is always the build files themselves — not this document: - **iOS**: `OptimizelySwiftSDK` version in - `ios/optimizely_flutter_sdk.podspec`. + `ios/optimizely_flutter_sdk.podspec` (CocoaPods) and + `ios/optimizely_flutter_sdk/Package.swift` (SPM). Both files MUST + declare the same version. - **Android**: `com.optimizely.ab:android-sdk` version in `android/build.gradle`. @@ -343,4 +345,4 @@ All code changes MUST comply with the principles defined above. - Use `CLAUDE.md` for runtime development guidance that supplements (but does not override) this constitution. -**Version**: 1.1.0 | **Ratified**: 2022-06-07 | **Last Amended**: 2026-08-20 +**Version**: 1.1.1 | **Ratified**: 2022-06-07 | **Last Amended**: 2026-08-22