From a488f54082b8c9fe3e555e67d29a120ddc540ba7 Mon Sep 17 00:00:00 2001 From: Ilya Snakin Date: Wed, 29 Jul 2026 17:32:09 +0200 Subject: [PATCH] fix(flutter): name the iOS podspec after the pub package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flutter derives the expected podspec path from the pub package name, so it looked for `ios/pulsar_haptics.podspec`, did not find it, and — because `ios/pulsar_haptics/Package.swift` is present — concluded the plugin is Swift Package Manager only. In an app that uses CocoaPods, `flutter pub get` then failed with "Plugin pulsar_haptics is only compatible with Swift Package Manager" and the plugin could not be adopted at all. The same name drives the module `GeneratedPluginRegistrant` imports (`@import pulsar_haptics;`), so even with SPM disabled by hand the pod built under the wrong module name. Rename `ios/pulsar.podspec` to `ios/pulsar_haptics.podspec`, set `s.name` to match, and tag `s.version` with a `pulsar-sync:flutter-version` marker so `scripts/sync-sdk-versions.mjs` keeps it in step with the pub version instead of leaving it stuck at 0.0.1. CocoaPods compares product module names case-insensitively, so the plugin pod `pulsar_haptics` now collides with the native pod's derived module `Pulsar_haptics` (from `Pulsar-haptics`) whenever the app opts into frameworks. The example app's Podfile drops `use_frameworks!` for that reason; declaring `s.module_name = 'Pulsar'` on the native podspec — matching the module name its Swift Package already exposes — would lift the restriction for apps that need frameworks, but that needs a native release and is left out here. Verified on the example app with Flutter 3.44.0: with SPM disabled, `flutter pub get` and `pod install` succeed and `flutter build ios --debug --simulator` builds; with SPM enabled, the build still succeeds. Co-Authored-By: Claude Opus 5 --- .github/workflows/_build-ios-flutter.yml | 21 ++++++++----------- CONTRIBUTING.md | 2 +- flutter/PulsarApp/ios/Podfile | 4 +++- flutter/PulsarApp/ios/Podfile.lock | 19 +++++++++++------ ...{pulsar.podspec => pulsar_haptics.podspec} | 9 +++++--- scripts/sync-sdk-versions.mjs | 3 ++- 6 files changed, 34 insertions(+), 24 deletions(-) rename flutter/pulsar/ios/{pulsar.podspec => pulsar_haptics.podspec} (84%) diff --git a/.github/workflows/_build-ios-flutter.yml b/.github/workflows/_build-ios-flutter.yml index 277e5753..c19b5f3b 100644 --- a/.github/workflows/_build-ios-flutter.yml +++ b/.github/workflows/_build-ios-flutter.yml @@ -43,20 +43,17 @@ jobs: fi # Why we point the path dep at a uniquely-named symlink instead of - # `iOS/Pulsar` directly. Flutter 3.44.1's plugin SPM integration - # mirrors the plugin under `/ios/Flutter/ephemeral/Packages/ - # .packages/pulsar` — the *podspec* name (`flutter/pulsar/ios/ - # pulsar.podspec` is named `pulsar`), not the Dart name - # (`pulsar_haptics`). Pointing the path dep at `iOS/Pulsar` - # collides with that symlink's last path component on macOS's - # case-insensitive filesystem under SPM tools-5.9: SPM derives - # the same identity `pulsar` from both and reports a self-cycle + # `iOS/Pulsar` directly. Flutter's plugin SPM integration mirrors + # the plugin under `/ios/Flutter/ephemeral/Packages/ + # .packages/`, which was `pulsar` while the podspec was: + # SPM then derived the identity `pulsar` from `iOS/Pulsar` too on + # macOS's case-insensitive filesystem and reported a self-cycle # with the misleading "cyclic dependency between packages # FlutterGeneratedPluginSwiftPackage -> pulsar_haptics -> - # pulsar_haptics requires tools-version 6.0 or later" wording. - # The `name:` alias on the dep isn't honored as a tie-breaker - # under tools-5.9. Route through `$RUNNER_TEMP/pulsar-sdk-source` - # so the resolved path ends in a unique component. + # pulsar_haptics requires tools-version 6.0 or later" wording + # (the `name:` alias is no tie-breaker under tools-5.9). The + # mirror is `pulsar_haptics` now, so keep the symlink only as a + # guard against the next name that collides. - name: Patch plugin SPM dep to local iOS/Pulsar sources env: PULSAR_ABS_PATH: ${{ github.workspace }}/iOS/Pulsar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99362d08..45628d6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ pulsar/ The iOS and Android SDKs are standalone native libraries. The React Native (Turbo Module) and Flutter (method-channel plugin) SDKs each ship a thin bridge and consume the published native artifacts rather than vendoring a copy of the native sources: -- **iOS:** the bridge podspec (`react-native/react-native-pulsar/Pulsar.podspec`, `flutter/pulsar/ios/pulsar.podspec`) depends on the published `Pulsar-haptics` CocoaPod by default. For local development in the example app, set `USE_LOCAL_PULSAR_IOS=1` before `pod install` to use `iOS/Pulsar/` instead. +- **iOS:** the bridge podspec (`react-native/react-native-pulsar/Pulsar.podspec`, `flutter/pulsar/ios/pulsar_haptics.podspec`) depends on the published `Pulsar-haptics` CocoaPod by default. For local development in the example app, set `USE_LOCAL_PULSAR_IOS=1` before `pod install` to use `iOS/Pulsar/` instead. - **Android:** the bridge Gradle module (`react-native/react-native-pulsar/android/build.gradle`, `flutter/pulsar/android/build.gradle.kts`) depends on the published `com.swmansion:pulsar` Maven artifact by default. For local development, set `USE_LOCAL_PULSAR_ANDROID=1` to compile against `Android/Pulsar/src/main/java/` instead. The Kotlin Multiplatform SDK (`kmp/Pulsar/library`) follows the same convention on Android: diff --git a/flutter/PulsarApp/ios/Podfile b/flutter/PulsarApp/ios/Podfile index 4e57f25c..06305950 100644 --- a/flutter/PulsarApp/ios/Podfile +++ b/flutter/PulsarApp/ios/Podfile @@ -28,7 +28,9 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do - use_frameworks! + # No `use_frameworks!`: CocoaPods compares module names case-insensitively, so + # the `pulsar_haptics` pod collides with `Pulsar-haptics`'s `Pulsar_haptics`. + # Re-enable once the native pod declares `s.module_name = 'Pulsar'`. if ENV['USE_LOCAL_PULSAR_IOS'] == '1' Pod::UI.puts 'Using local Pulsar-haptics pod from iOS/Pulsar'.green diff --git a/flutter/PulsarApp/ios/Podfile.lock b/flutter/PulsarApp/ios/Podfile.lock index 4f6db4e0..3cd8b66b 100644 --- a/flutter/PulsarApp/ios/Podfile.lock +++ b/flutter/PulsarApp/ios/Podfile.lock @@ -1,22 +1,29 @@ PODS: - Flutter (1.0.0) - - pulsar (0.0.1): + - Pulsar-haptics (1.1.2) + - pulsar_haptics (0.0.3): - Flutter + - Pulsar-haptics (= 1.1.2) DEPENDENCIES: - Flutter (from `Flutter`) - - pulsar (from `.symlinks/plugins/pulsar/ios`) + - pulsar_haptics (from `.symlinks/plugins/pulsar_haptics/ios`) + +SPEC REPOS: + trunk: + - Pulsar-haptics EXTERNAL SOURCES: Flutter: :path: Flutter - pulsar: - :path: ".symlinks/plugins/pulsar/ios" + pulsar_haptics: + :path: ".symlinks/plugins/pulsar_haptics/ios" SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - pulsar: 9bed0645c2937650a3252df9ed531e52730f572f + Pulsar-haptics: 1adbf9d10edc35c68aa03c0871cc6b9f2217d121 + pulsar_haptics: 254e582a25ef6b02586aa6df058892c000db3987 -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e +PODFILE CHECKSUM: 64ff3ed00f29d41c4253cb06bf23da3065a98024 COCOAPODS: 1.16.2 diff --git a/flutter/pulsar/ios/pulsar.podspec b/flutter/pulsar/ios/pulsar_haptics.podspec similarity index 84% rename from flutter/pulsar/ios/pulsar.podspec rename to flutter/pulsar/ios/pulsar_haptics.podspec index 55620f76..a6029a94 100644 --- a/flutter/pulsar/ios/pulsar.podspec +++ b/flutter/pulsar/ios/pulsar_haptics.podspec @@ -1,12 +1,15 @@ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint pulsar.podspec` to validate before publishing. +# Run `pod lib lint pulsar_haptics.podspec` to validate before publishing. +# +# File name and `s.name` must stay equal to the pub package name — Flutter +# derives both the podspec path it looks for and the module it imports from it. # pulsar_ios_pod_version = ENV['PULSAR_IOS_POD_VERSION'] || '1.1.2' # pulsar-sync:flutter-pulsar-ios Pod::Spec.new do |s| - s.name = 'pulsar' - s.version = '0.0.1' + s.name = 'pulsar_haptics' + s.version = '0.0.3' # pulsar-sync:flutter-version s.summary = 'Rich haptic feedback for Flutter with presets, pattern playback, and realtime control.' s.description = <<-DESC Pulsar gives you 150+ ready-to-play haptic presets, a pattern composer for fully custom diff --git a/scripts/sync-sdk-versions.mjs b/scripts/sync-sdk-versions.mjs index 1018b807..c500e5fd 100644 --- a/scripts/sync-sdk-versions.mjs +++ b/scripts/sync-sdk-versions.mjs @@ -38,7 +38,8 @@ const markedVersions = [ { file: 'kmp/Pulsar/library/build.gradle.kts', key: 'kmp-version', version: versions.kmp.version }, { file: 'kmp/Pulsar/library/build.gradle.kts', key: 'kmp-pulsar-android', version: versions.kmp.pulsarCore.androidMavenVersion }, { file: 'flutter/pulsar/pubspec.yaml', key: 'flutter-version', version: versions.flutter.version }, - { file: 'flutter/pulsar/ios/pulsar.podspec', key: 'flutter-pulsar-ios', version: versions.flutter.pulsarCore.iosPodVersion }, + { file: 'flutter/pulsar/ios/pulsar_haptics.podspec', key: 'flutter-version', version: versions.flutter.version }, + { file: 'flutter/pulsar/ios/pulsar_haptics.podspec', key: 'flutter-pulsar-ios', version: versions.flutter.pulsarCore.iosPodVersion }, { file: 'flutter/pulsar/android/build.gradle.kts', key: 'flutter-pulsar-android', version: versions.flutter.pulsarCore.androidMavenVersion }, ];