diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 6ebe9fe3..aa61f67f 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -28,6 +28,20 @@ jobs: example_ios: runs-on: macos-latest + strategy: + fail-fast: false + matrix: + dependency_manager: [cocoapods, spm] + include: + - dependency_manager: cocoapods + build_name: "CocoaPods" + flutter_config: "flutter config --no-enable-swift-package-manager" + cleanup_step: "echo 'Using CocoaPods - no cleanup needed'" + - dependency_manager: spm + build_name: "Swift Package Manager" + flutter_config: "flutter config --enable-swift-package-manager" + cleanup_step: "cd example/ios && rm -f Podfile Podfile.lock && rm -rf Pods" + name: iOS Example (${{ matrix.build_name }}) steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 @@ -35,8 +49,19 @@ jobs: channel: "stable" cache: true - - name: build + - name: Configure Flutter for ${{ matrix.build_name }} + run: ${{ matrix.flutter_config }} + + - name: Bootstrap project run: | dart pub global activate melos melos bootstrap - cd example && flutter build ios --debug --no-codesign + + - name: Cleanup for ${{ matrix.build_name }} + run: ${{ matrix.cleanup_step }} + + - name: Build iOS example with ${{ matrix.build_name }} + run: | + cd example + flutter clean + flutter build ios --debug --no-codesign diff --git a/SPM_MIGRATION_PLAN.md b/SPM_MIGRATION_PLAN.md new file mode 100644 index 00000000..f523c325 --- /dev/null +++ b/SPM_MIGRATION_PLAN.md @@ -0,0 +1,182 @@ +# Swift Package Manager Migration Plan + +## Overview +Migrate `workmanager_apple` plugin to support Swift Package Manager (SPM) while maintaining full CocoaPods backward compatibility. + +## Current Structure Analysis +``` +workmanager_apple/ios/ +├── Assets/ +├── Classes/ +│ ├── BackgroundTaskOperation.swift +│ ├── BackgroundWorker.swift +│ ├── Extensions.swift +│ ├── LoggingDebugHandler.swift +│ ├── NotificationDebugHandler.swift +│ ├── SimpleLogger.swift +│ ├── ThumbnailGenerator.swift +│ ├── UserDefaultsHelper.swift +│ ├── WMPError.swift +│ ├── WorkmanagerDebugHandler.swift +│ ├── WorkmanagerPlugin.swift +│ └── pigeon/ +│ └── WorkmanagerApi.g.swift +├── Resources/ +│ └── PrivacyInfo.xcprivacy +└── workmanager_apple.podspec +``` + +## Migration Strategy + +### Phase 1: SPM Structure Setup +1. Create `workmanager_apple/ios/Package.swift` +2. Create new directory structure: + ``` + workmanager_apple/ios/ + ├── Sources/ + │ └── workmanager_apple/ + │ ├── include/ + │ │ └── workmanager_apple-umbrella.h (if needed) + │ └── [all .swift files moved here] + └── Resources/ + └── PrivacyInfo.xcprivacy + ``` + +### Phase 2: File Migration +- **Move Swift files** from `Classes/` to `Sources/workmanager_apple/` +- **Preserve pigeon structure** as `Sources/workmanager_apple/pigeon/` +- **Update import statements** if needed +- **Handle resources** - PrivacyInfo.xcprivacy + +### Phase 3: Configuration Files +- **Create Package.swift** with proper target definitions +- **Update podspec** to reference new file locations +- **Maintain backward compatibility** for CocoaPods users + +### Phase 4: Testing Strategy +- **Dual build testing** in GitHub Actions +- **CocoaPods build**: Test existing workflow +- **SPM build**: New workflow for SPM validation +- **Example app testing**: Both dependency managers + +## Implementation Details + +### Package.swift Configuration +```swift +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "workmanager_apple", + platforms: [ + .iOS(.v14) + ], + products: [ + .library(name: "workmanager_apple", targets: ["workmanager_apple"]) + ], + targets: [ + .target( + name: "workmanager_apple", + resources: [.process("Resources")] + ) + ] +) +``` + +### GitHub Actions Strategy + +**Matrix Strategy using Flutter SPM configuration:** +- **CocoaPods Build**: `flutter config --no-enable-swift-package-manager` + build +- **SPM Build**: `flutter config --enable-swift-package-manager` + build + +**Key Features:** +1. **Flutter-native approach**: Use `flutter config` flags to switch dependency managers +2. **Simple validation**: Does example app build and run with both configurations? +3. **Matrix builds**: Test both `--enable-swift-package-manager` and `--no-enable-swift-package-manager` + +**GitHub Actions Matrix:** +```yaml +strategy: + matrix: + spm_enabled: [true, false] + include: + - spm_enabled: true + config_cmd: "flutter config --enable-swift-package-manager" + name: "SPM" + - spm_enabled: false + config_cmd: "flutter config --no-enable-swift-package-manager" + name: "CocoaPods" +``` + +## Risk Mitigation + +### Backward Compatibility +- **Keep CocoaPods support** indefinitely +- **Update podspec paths** to point to new locations +- **Test both build systems** in CI + +### File Organization +- **Maintain logical grouping** of Swift files +- **Preserve pigeon integration** with generated files +- **Handle resources properly** in both systems + +### Dependencies +- **No external Swift dependencies** currently - simplifies migration +- **Flutter framework dependency** handled by both systems + +## Testing Requirements + +### Pre-Migration Tests +- [ ] Current CocoaPods build works +- [ ] Example app builds and runs +- [ ] All functionality works on physical device + +### Verification Strategy +**Simple test**: Does the example app build and run with both dependency managers? + +**CocoaPods Build:** +```bash +flutter config --no-enable-swift-package-manager +cd example && flutter build ios --debug --no-codesign +``` + +**SPM Build:** +```bash +flutter config --enable-swift-package-manager +cd example && flutter build ios --debug --no-codesign +``` + +**Flutter Requirements:** +- Flutter 3.24+ required for SPM support +- SPM is off by default, must be explicitly enabled + +### CI/CD Integration +- Use Flutter's built-in SPM configuration flags +- Test both dependency managers via matrix builds +- No separate long-lived branches needed + +## Implementation Phases + +### Phase 1: Directory Restructure (First Commit) +1. Create SPM-compliant directory structure +2. Move all Swift files to `Sources/workmanager_apple/` +3. Update podspec to reference new locations +4. Ensure CocoaPods + Pigeon still work +5. **Verification**: Example app builds and runs with CocoaPods + +### Phase 2: SPM Configuration (Second Commit) +1. Add `Package.swift` with proper configuration +2. Handle resources (PrivacyInfo.xcprivacy) +3. **Verification**: Example app builds and runs with SPM + +### Phase 3: CI Integration (Third Commit) +1. Update GitHub Actions to test both dependency managers +2. Use Flutter config flags for SPM/CocoaPods selection + +## Success Criteria +- ✅ SPM support working in Flutter projects +- ✅ Full CocoaPods backward compatibility maintained +- ✅ All existing functionality preserved +- ✅ CI/CD tests both dependency managers +- ✅ No breaking changes for existing users +- ✅ Proper resource and privacy manifest handling \ No newline at end of file diff --git a/example/ios/Podfile b/example/ios/Podfile index afde56aa..04c36cf4 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -32,7 +32,6 @@ target 'Runner' do use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do inherit! :search_paths end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a5a6792b..63dad78d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,48 +1,35 @@ PODS: - Flutter (1.0.0) - - integration_test (0.0.1): - - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - workmanager_apple (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter - integration_test: - :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" workmanager_apple: :path: ".symlinks/plugins/workmanager_apple/ios" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - workmanager_apple: 46692e3180809ea34232c2c29ad16d35ab793ded + workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778 -PODFILE CHECKSUM: bf5d48b0f58a968d755f5b593e79332a40015529 +PODFILE CHECKSUM: 1959d098c91d8a792531a723c4a9d7e9f6a01e38 COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index b32dca93..01efe3a0 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 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 */; }; @@ -73,6 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, A4F342DE9D752B13EF553010 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -196,6 +198,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -251,6 +256,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -361,16 +369,12 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", "${BUILT_PRODUCTS_DIR}/workmanager_apple/workmanager_apple.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/workmanager_apple.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -840,6 +844,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 58c125ed..577f5b39 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'Sources/workmanager_apple/**/*' s.dependency 'Flutter' s.ios.deployment_target = '14.0' diff --git a/workmanager_platform_interface/pigeons/workmanager_api.dart b/workmanager_platform_interface/pigeons/workmanager_api.dart index f56c1b59..a5fb3adb 100644 --- a/workmanager_platform_interface/pigeons/workmanager_api.dart +++ b/workmanager_platform_interface/pigeons/workmanager_api.dart @@ -9,7 +9,8 @@ import 'package:pigeon/pigeon.dart'; kotlinOptions: KotlinOptions( package: 'dev.fluttercommunity.workmanager.pigeon', ), - swiftOut: '../workmanager_apple/ios/Classes/pigeon/WorkmanagerApi.g.swift', + swiftOut: + '../workmanager_apple/ios/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift', copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'workmanager_platform_interface', ))