From d6ba493f58c07388b0e20ef21288b90b0e024bd5 Mon Sep 17 00:00:00 2001 From: Bhavik Shah Date: Thu, 11 Jun 2026 19:11:53 -0700 Subject: [PATCH] feat: add Swift Package Manager support for iOS Relocate the workmanager_apple iOS plugin into the directory layout that Flutter's Swift Package Manager (SPM) integration detects, while keeping full CocoaPods backward compatibility. Plugins are expected to support both dependency managers during the transition. - Move Package.swift to ios/workmanager_apple/Package.swift so Flutter detects the Swift package (previously at ios/Package.swift, never found). - Rename the library product to "workmanager-apple" (Flutter requires SwiftPM product names without underscores). - Move the privacy manifest into the SPM target and reference it with .process("PrivacyInfo.xcprivacy") so the resource path stays inside the target root. - Update workmanager_apple.podspec source_files/resource_bundles paths to the new location so CocoaPods builds keep working, and fix placeholder metadata. - Add import UIKit to Extensions.swift (required under SPM's per-file modular compilation for UIBackgroundFetchResult). - Point the Pigeon swiftOut path at the relocated sources. - Ignore .build/ and .swiftpm/. Verified the example app builds under both SPM (flutter config --enable-swift-package-manager) and CocoaPods. Co-Authored-By: Iwan Gerber Co-Authored-By: Claude Opus 4.8 --- SPM_MIGRATION_PLAN.md | 182 ------------------ workmanager_apple/ios/.gitignore | 4 + .../ios/workmanager_apple.podspec | 13 +- .../{ => workmanager_apple}/Assets/.gitkeep | 0 .../ios/{ => workmanager_apple}/Package.swift | 7 +- .../BackgroundTaskOperation.swift | 0 .../workmanager_apple/BackgroundWorker.swift | 0 .../workmanager_apple/Extensions.swift | 1 + .../LoggingDebugHandler.swift | 0 .../NotificationDebugHandler.swift | 0 .../workmanager_apple}/PrivacyInfo.xcprivacy | 0 .../workmanager_apple/SimpleLogger.swift | 0 .../ThumbnailGenerator.swift | 0 .../UserDefaultsHelper.swift | 0 .../Sources/workmanager_apple/WMPError.swift | 0 .../WorkmanagerDebugHandler.swift | 0 .../workmanager_apple/WorkmanagerPlugin.swift | 0 .../pigeon/WorkmanagerApi.g.swift | 0 .../pigeons/workmanager_api.dart | 2 +- 19 files changed, 16 insertions(+), 193 deletions(-) delete mode 100644 SPM_MIGRATION_PLAN.md rename workmanager_apple/ios/{ => workmanager_apple}/Assets/.gitkeep (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Package.swift (65%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/BackgroundTaskOperation.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/BackgroundWorker.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/Extensions.swift (98%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/LoggingDebugHandler.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/NotificationDebugHandler.swift (100%) rename workmanager_apple/ios/{Resources => workmanager_apple/Sources/workmanager_apple}/PrivacyInfo.xcprivacy (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/SimpleLogger.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/ThumbnailGenerator.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/UserDefaultsHelper.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/WMPError.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/WorkmanagerDebugHandler.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/WorkmanagerPlugin.swift (100%) rename workmanager_apple/ios/{ => workmanager_apple}/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift (100%) diff --git a/SPM_MIGRATION_PLAN.md b/SPM_MIGRATION_PLAN.md deleted file mode 100644 index f523c325..00000000 --- a/SPM_MIGRATION_PLAN.md +++ /dev/null @@ -1,182 +0,0 @@ -# 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/workmanager_apple/ios/.gitignore b/workmanager_apple/ios/.gitignore index 710ec6cf..514404e6 100644 --- a/workmanager_apple/ios/.gitignore +++ b/workmanager_apple/ios/.gitignore @@ -3,6 +3,10 @@ .sconsign.dblite .svn/ +# Swift Package Manager +.build/ +.swiftpm/ + .DS_Store *.swp profile diff --git a/workmanager_apple/ios/workmanager_apple.podspec b/workmanager_apple/ios/workmanager_apple.podspec index f71ee7cc..710d3885 100644 --- a/workmanager_apple/ios/workmanager_apple.podspec +++ b/workmanager_apple/ios/workmanager_apple.podspec @@ -4,20 +4,19 @@ Pod::Spec.new do |s| s.name = 'workmanager_apple' s.version = '0.0.1' - s.summary = 'Flutter Workmanager' + s.summary = 'Flutter Workmanager for iOS' s.description = <<-DESC -Flutter Android Workmanager +iOS implementation of the Flutter Workmanager plugin, allowing you to schedule background work. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/fluttercommunity/flutter_workmanager' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Flutter Community' => 'authors@fluttercommunity.dev' } s.source = { :path => '.' } - s.source_files = 'Sources/workmanager_apple/**/*' + s.source_files = 'workmanager_apple/Sources/workmanager_apple/**/*.swift' s.dependency 'Flutter' s.ios.deployment_target = '14.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.resource_bundles = { 'flutter_workmanager_privacy' => ['Resources/PrivacyInfo.xcprivacy'] } + s.resource_bundles = { 'flutter_workmanager_privacy' => ['workmanager_apple/Sources/workmanager_apple/PrivacyInfo.xcprivacy'] } end - diff --git a/workmanager_apple/ios/Assets/.gitkeep b/workmanager_apple/ios/workmanager_apple/Assets/.gitkeep similarity index 100% rename from workmanager_apple/ios/Assets/.gitkeep rename to workmanager_apple/ios/workmanager_apple/Assets/.gitkeep diff --git a/workmanager_apple/ios/Package.swift b/workmanager_apple/ios/workmanager_apple/Package.swift similarity index 65% rename from workmanager_apple/ios/Package.swift rename to workmanager_apple/ios/workmanager_apple/Package.swift index 66351ca9..e0dac61e 100644 --- a/workmanager_apple/ios/Package.swift +++ b/workmanager_apple/ios/workmanager_apple/Package.swift @@ -9,17 +9,18 @@ let package = Package( .iOS(.v14) ], products: [ + // The library name uses a hyphen because Flutter requires SwiftPM product + // names to not contain underscores. The target keeps the underscore. .library( - name: "workmanager_apple", + name: "workmanager-apple", targets: ["workmanager_apple"] ) ], targets: [ .target( name: "workmanager_apple", - path: "Sources/workmanager_apple", resources: [ - .process("../Resources") + .process("PrivacyInfo.xcprivacy") ] ) ] diff --git a/workmanager_apple/ios/Sources/workmanager_apple/BackgroundTaskOperation.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/BackgroundTaskOperation.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/BackgroundTaskOperation.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/BackgroundTaskOperation.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/BackgroundWorker.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/BackgroundWorker.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/BackgroundWorker.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/BackgroundWorker.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/Extensions.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/Extensions.swift similarity index 98% rename from workmanager_apple/ios/Sources/workmanager_apple/Extensions.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/Extensions.swift index ba53f9ef..ca3f47ed 100644 --- a/workmanager_apple/ios/Sources/workmanager_apple/Extensions.swift +++ b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/Extensions.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit extension UIBackgroundFetchResult: CustomDebugStringConvertible { public var debugDescription: String { diff --git a/workmanager_apple/ios/Sources/workmanager_apple/LoggingDebugHandler.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/LoggingDebugHandler.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/LoggingDebugHandler.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/LoggingDebugHandler.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/NotificationDebugHandler.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/NotificationDebugHandler.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/NotificationDebugHandler.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/NotificationDebugHandler.swift diff --git a/workmanager_apple/ios/Resources/PrivacyInfo.xcprivacy b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/PrivacyInfo.xcprivacy similarity index 100% rename from workmanager_apple/ios/Resources/PrivacyInfo.xcprivacy rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/PrivacyInfo.xcprivacy diff --git a/workmanager_apple/ios/Sources/workmanager_apple/SimpleLogger.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/SimpleLogger.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/SimpleLogger.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/SimpleLogger.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/ThumbnailGenerator.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/ThumbnailGenerator.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/ThumbnailGenerator.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/ThumbnailGenerator.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/UserDefaultsHelper.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/UserDefaultsHelper.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/UserDefaultsHelper.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/UserDefaultsHelper.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/WMPError.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WMPError.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/WMPError.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WMPError.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerDebugHandler.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WorkmanagerDebugHandler.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerDebugHandler.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WorkmanagerDebugHandler.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WorkmanagerPlugin.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/WorkmanagerPlugin.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/WorkmanagerPlugin.swift diff --git a/workmanager_apple/ios/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift b/workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift similarity index 100% rename from workmanager_apple/ios/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift rename to workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift diff --git a/workmanager_platform_interface/pigeons/workmanager_api.dart b/workmanager_platform_interface/pigeons/workmanager_api.dart index a5fb3adb..b5094ca4 100644 --- a/workmanager_platform_interface/pigeons/workmanager_api.dart +++ b/workmanager_platform_interface/pigeons/workmanager_api.dart @@ -10,7 +10,7 @@ import 'package:pigeon/pigeon.dart'; package: 'dev.fluttercommunity.workmanager.pigeon', ), swiftOut: - '../workmanager_apple/ios/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift', + '../workmanager_apple/ios/workmanager_apple/Sources/workmanager_apple/pigeon/WorkmanagerApi.g.swift', copyrightHeader: 'pigeons/copyright.txt', dartPackageName: 'workmanager_platform_interface', ))