Skip to content

Commit e2e1d54

Browse files
enedclaude
andauthored
feat: add iOS Swift Package Manager support (#631)
* feat: restructure iOS plugin for SPM compatibility - Phase 1 - Create SPM-compliant directory structure: Sources/workmanager_apple/ - Move all Swift files from Classes/ to Sources/workmanager_apple/ - Preserve pigeon subdirectory structure - Update podspec to reference new file locations: Sources/workmanager_apple/**/* - Update Pigeon configuration to generate Swift files in new location - Add comprehensive SPM migration plan documentation ✅ Verified: CocoaPods builds successfully with new structure ✅ Backward compatibility: No breaking changes for existing users This prepares the foundation for Swift Package Manager support while maintaining full CocoaPods compatibility. * feat: add Swift Package Manager support - Phase 2 - Add Package.swift with iOS 14+ target and proper resource handling - Configure SPM target for workmanager_apple with Sources/workmanager_apple path - Include Resources/PrivacyInfo.xcprivacy processing for SPM - Support Swift tools version 5.9 ✅ Verified: SPM builds successfully with flutter config --enable-swift-package-manager ✅ Verified: CocoaPods backward compatibility maintained ✅ Verified: Both dependency managers work independently Users can now choose their preferred dependency manager: - CocoaPods: flutter config --no-enable-swift-package-manager - SPM: flutter config --enable-swift-package-manager This completes the core SPM migration while maintaining full backward compatibility. * feat: add dual CI testing for iOS SPM and CocoaPods Integrates Swift Package Manager testing into the examples.yml workflow: - Matrix build strategy tests both dependency managers - SPM build removes Podfile for pure testing - Maintains backward compatibility with CocoaPods 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: restore original Podfile configuration Reverts unintended changes to Podfile that were accidentally included in the previous commit * chore: apply linting fixes and update dependencies - Apply swiftlint fixes to Package.swift - Apply dart formatting to workmanager_api.dart - Update Podfile.lock checksum after iOS build --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 28232b1 commit e2e1d54

21 files changed

+280
-24
lines changed

.github/workflows/examples.yml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,40 @@ jobs:
2828
2929
example_ios:
3030
runs-on: macos-latest
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
dependency_manager: [cocoapods, spm]
35+
include:
36+
- dependency_manager: cocoapods
37+
build_name: "CocoaPods"
38+
flutter_config: "flutter config --no-enable-swift-package-manager"
39+
cleanup_step: "echo 'Using CocoaPods - no cleanup needed'"
40+
- dependency_manager: spm
41+
build_name: "Swift Package Manager"
42+
flutter_config: "flutter config --enable-swift-package-manager"
43+
cleanup_step: "cd example/ios && rm -f Podfile Podfile.lock && rm -rf Pods"
44+
name: iOS Example (${{ matrix.build_name }})
3145
steps:
3246
- uses: actions/checkout@v4
3347
- uses: subosito/flutter-action@v2
3448
with:
3549
channel: "stable"
3650
cache: true
3751

38-
- name: build
52+
- name: Configure Flutter for ${{ matrix.build_name }}
53+
run: ${{ matrix.flutter_config }}
54+
55+
- name: Bootstrap project
3956
run: |
4057
dart pub global activate melos
4158
melos bootstrap
42-
cd example && flutter build ios --debug --no-codesign
59+
60+
- name: Cleanup for ${{ matrix.build_name }}
61+
run: ${{ matrix.cleanup_step }}
62+
63+
- name: Build iOS example with ${{ matrix.build_name }}
64+
run: |
65+
cd example
66+
flutter clean
67+
flutter build ios --debug --no-codesign

SPM_MIGRATION_PLAN.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Swift Package Manager Migration Plan
2+
3+
## Overview
4+
Migrate `workmanager_apple` plugin to support Swift Package Manager (SPM) while maintaining full CocoaPods backward compatibility.
5+
6+
## Current Structure Analysis
7+
```
8+
workmanager_apple/ios/
9+
├── Assets/
10+
├── Classes/
11+
│ ├── BackgroundTaskOperation.swift
12+
│ ├── BackgroundWorker.swift
13+
│ ├── Extensions.swift
14+
│ ├── LoggingDebugHandler.swift
15+
│ ├── NotificationDebugHandler.swift
16+
│ ├── SimpleLogger.swift
17+
│ ├── ThumbnailGenerator.swift
18+
│ ├── UserDefaultsHelper.swift
19+
│ ├── WMPError.swift
20+
│ ├── WorkmanagerDebugHandler.swift
21+
│ ├── WorkmanagerPlugin.swift
22+
│ └── pigeon/
23+
│ └── WorkmanagerApi.g.swift
24+
├── Resources/
25+
│ └── PrivacyInfo.xcprivacy
26+
└── workmanager_apple.podspec
27+
```
28+
29+
## Migration Strategy
30+
31+
### Phase 1: SPM Structure Setup
32+
1. Create `workmanager_apple/ios/Package.swift`
33+
2. Create new directory structure:
34+
```
35+
workmanager_apple/ios/
36+
├── Sources/
37+
│ └── workmanager_apple/
38+
│ ├── include/
39+
│ │ └── workmanager_apple-umbrella.h (if needed)
40+
│ └── [all .swift files moved here]
41+
└── Resources/
42+
└── PrivacyInfo.xcprivacy
43+
```
44+
45+
### Phase 2: File Migration
46+
- **Move Swift files** from `Classes/` to `Sources/workmanager_apple/`
47+
- **Preserve pigeon structure** as `Sources/workmanager_apple/pigeon/`
48+
- **Update import statements** if needed
49+
- **Handle resources** - PrivacyInfo.xcprivacy
50+
51+
### Phase 3: Configuration Files
52+
- **Create Package.swift** with proper target definitions
53+
- **Update podspec** to reference new file locations
54+
- **Maintain backward compatibility** for CocoaPods users
55+
56+
### Phase 4: Testing Strategy
57+
- **Dual build testing** in GitHub Actions
58+
- **CocoaPods build**: Test existing workflow
59+
- **SPM build**: New workflow for SPM validation
60+
- **Example app testing**: Both dependency managers
61+
62+
## Implementation Details
63+
64+
### Package.swift Configuration
65+
```swift
66+
// swift-tools-version: 5.9
67+
import PackageDescription
68+
69+
let package = Package(
70+
name: "workmanager_apple",
71+
platforms: [
72+
.iOS(.v14)
73+
],
74+
products: [
75+
.library(name: "workmanager_apple", targets: ["workmanager_apple"])
76+
],
77+
targets: [
78+
.target(
79+
name: "workmanager_apple",
80+
resources: [.process("Resources")]
81+
)
82+
]
83+
)
84+
```
85+
86+
### GitHub Actions Strategy
87+
88+
**Matrix Strategy using Flutter SPM configuration:**
89+
- **CocoaPods Build**: `flutter config --no-enable-swift-package-manager` + build
90+
- **SPM Build**: `flutter config --enable-swift-package-manager` + build
91+
92+
**Key Features:**
93+
1. **Flutter-native approach**: Use `flutter config` flags to switch dependency managers
94+
2. **Simple validation**: Does example app build and run with both configurations?
95+
3. **Matrix builds**: Test both `--enable-swift-package-manager` and `--no-enable-swift-package-manager`
96+
97+
**GitHub Actions Matrix:**
98+
```yaml
99+
strategy:
100+
matrix:
101+
spm_enabled: [true, false]
102+
include:
103+
- spm_enabled: true
104+
config_cmd: "flutter config --enable-swift-package-manager"
105+
name: "SPM"
106+
- spm_enabled: false
107+
config_cmd: "flutter config --no-enable-swift-package-manager"
108+
name: "CocoaPods"
109+
```
110+
111+
## Risk Mitigation
112+
113+
### Backward Compatibility
114+
- **Keep CocoaPods support** indefinitely
115+
- **Update podspec paths** to point to new locations
116+
- **Test both build systems** in CI
117+
118+
### File Organization
119+
- **Maintain logical grouping** of Swift files
120+
- **Preserve pigeon integration** with generated files
121+
- **Handle resources properly** in both systems
122+
123+
### Dependencies
124+
- **No external Swift dependencies** currently - simplifies migration
125+
- **Flutter framework dependency** handled by both systems
126+
127+
## Testing Requirements
128+
129+
### Pre-Migration Tests
130+
- [ ] Current CocoaPods build works
131+
- [ ] Example app builds and runs
132+
- [ ] All functionality works on physical device
133+
134+
### Verification Strategy
135+
**Simple test**: Does the example app build and run with both dependency managers?
136+
137+
**CocoaPods Build:**
138+
```bash
139+
flutter config --no-enable-swift-package-manager
140+
cd example && flutter build ios --debug --no-codesign
141+
```
142+
143+
**SPM Build:**
144+
```bash
145+
flutter config --enable-swift-package-manager
146+
cd example && flutter build ios --debug --no-codesign
147+
```
148+
149+
**Flutter Requirements:**
150+
- Flutter 3.24+ required for SPM support
151+
- SPM is off by default, must be explicitly enabled
152+
153+
### CI/CD Integration
154+
- Use Flutter's built-in SPM configuration flags
155+
- Test both dependency managers via matrix builds
156+
- No separate long-lived branches needed
157+
158+
## Implementation Phases
159+
160+
### Phase 1: Directory Restructure (First Commit)
161+
1. Create SPM-compliant directory structure
162+
2. Move all Swift files to `Sources/workmanager_apple/`
163+
3. Update podspec to reference new locations
164+
4. Ensure CocoaPods + Pigeon still work
165+
5. **Verification**: Example app builds and runs with CocoaPods
166+
167+
### Phase 2: SPM Configuration (Second Commit)
168+
1. Add `Package.swift` with proper configuration
169+
2. Handle resources (PrivacyInfo.xcprivacy)
170+
3. **Verification**: Example app builds and runs with SPM
171+
172+
### Phase 3: CI Integration (Third Commit)
173+
1. Update GitHub Actions to test both dependency managers
174+
2. Use Flutter config flags for SPM/CocoaPods selection
175+
176+
## Success Criteria
177+
- ✅ SPM support working in Flutter projects
178+
- ✅ Full CocoaPods backward compatibility maintained
179+
- ✅ All existing functionality preserved
180+
- ✅ CI/CD tests both dependency managers
181+
- ✅ No breaking changes for existing users
182+
- ✅ Proper resource and privacy manifest handling

example/ios/Podfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ target 'Runner' do
3232
use_modular_headers!
3333

3434
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35-
3635
target 'RunnerTests' do
3736
inherit! :search_paths
3837
end

example/ios/Podfile.lock

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,35 @@
11
PODS:
22
- Flutter (1.0.0)
3-
- integration_test (0.0.1):
4-
- Flutter
53
- path_provider_foundation (0.0.1):
64
- Flutter
75
- FlutterMacOS
86
- permission_handler_apple (9.3.0):
97
- Flutter
10-
- shared_preferences_foundation (0.0.1):
11-
- Flutter
12-
- FlutterMacOS
138
- workmanager_apple (0.0.1):
149
- Flutter
1510

1611
DEPENDENCIES:
1712
- Flutter (from `Flutter`)
18-
- integration_test (from `.symlinks/plugins/integration_test/ios`)
1913
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
2014
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
21-
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
2215
- workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
2316

2417
EXTERNAL SOURCES:
2518
Flutter:
2619
:path: Flutter
27-
integration_test:
28-
:path: ".symlinks/plugins/integration_test/ios"
2920
path_provider_foundation:
3021
:path: ".symlinks/plugins/path_provider_foundation/darwin"
3122
permission_handler_apple:
3223
:path: ".symlinks/plugins/permission_handler_apple/ios"
33-
shared_preferences_foundation:
34-
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
3524
workmanager_apple:
3625
:path: ".symlinks/plugins/workmanager_apple/ios"
3726

3827
SPEC CHECKSUMS:
3928
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
40-
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
4129
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
4230
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
43-
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
44-
workmanager_apple: 46692e3180809ea34232c2c29ad16d35ab793ded
31+
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
4532

46-
PODFILE CHECKSUM: bf5d48b0f58a968d755f5b593e79332a40015529
33+
PODFILE CHECKSUM: 1959d098c91d8a792531a723c4a9d7e9f6a01e38
4734

4835
COCOAPODS: 1.16.2

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1111
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
1212
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
1314
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
1415
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
1516
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -73,6 +74,7 @@
7374
isa = PBXFrameworksBuildPhase;
7475
buildActionMask = 2147483647;
7576
files = (
77+
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
7678
A4F342DE9D752B13EF553010 /* Pods_Runner.framework in Frameworks */,
7779
);
7880
runOnlyForDeploymentPostprocessing = 0;
@@ -196,6 +198,9 @@
196198
dependencies = (
197199
);
198200
name = Runner;
201+
packageProductDependencies = (
202+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
203+
);
199204
productName = Runner;
200205
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
201206
productType = "com.apple.product-type.application";
@@ -251,6 +256,9 @@
251256
Base,
252257
);
253258
mainGroup = 97C146E51CF9000F007C117D;
259+
packageReferences = (
260+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
261+
);
254262
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
255263
projectDirPath = "";
256264
projectRoot = "";
@@ -361,16 +369,12 @@
361369
);
362370
inputPaths = (
363371
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
364-
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
365372
"${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
366-
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
367373
"${BUILT_PRODUCTS_DIR}/workmanager_apple/workmanager_apple.framework",
368374
);
369375
name = "[CP] Embed Pods Frameworks";
370376
outputPaths = (
371-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
372377
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
373-
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
374378
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/workmanager_apple.framework",
375379
);
376380
runOnlyForDeploymentPostprocessing = 0;
@@ -840,6 +844,20 @@
840844
defaultConfigurationName = Release;
841845
};
842846
/* End XCConfigurationList section */
847+
848+
/* Begin XCLocalSwiftPackageReference section */
849+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
850+
isa = XCLocalSwiftPackageReference;
851+
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
852+
};
853+
/* End XCLocalSwiftPackageReference section */
854+
855+
/* Begin XCSwiftPackageProductDependency section */
856+
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
857+
isa = XCSwiftPackageProductDependency;
858+
productName = FlutterGeneratedPluginSwiftPackage;
859+
};
860+
/* End XCSwiftPackageProductDependency section */
843861
};
844862
rootObject = 97C146E61CF9000F007C117D /* Project object */;
845863
}

example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">
8+
<PreActions>
9+
<ExecutionAction
10+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
11+
<ActionContent
12+
title = "Run Prepare Flutter Framework Script"
13+
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
14+
<EnvironmentBuildable>
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
18+
BuildableName = "Runner.app"
19+
BlueprintName = "Runner"
20+
ReferencedContainer = "container:Runner.xcodeproj">
21+
</BuildableReference>
22+
</EnvironmentBuildable>
23+
</ActionContent>
24+
</ExecutionAction>
25+
</PreActions>
826
<BuildActionEntries>
927
<BuildActionEntry
1028
buildForTesting = "YES"

0 commit comments

Comments
 (0)