Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Prompt plugin authors to add Swift Package Manager compatibility to their plugin#182246

Open
okorohelijah wants to merge 10 commits intoflutter:masterfrom
okorohelijah:fix_148222
Open

Prompt plugin authors to add Swift Package Manager compatibility to their plugin#182246
okorohelijah wants to merge 10 commits intoflutter:masterfrom
okorohelijah:fix_148222

Conversation

@okorohelijah
Copy link
Contributor

@okorohelijah okorohelijah commented Feb 11, 2026

Prompt plugin authors to add Swift Package Manager compatibility to their plugin

Fixes #148222

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Feb 11, 2026
@okorohelijah okorohelijah changed the title Prompt plugin authors to add Swift Package Manager compatibility to t… Prompt plugin authors to add Swift Package Manager compatibility to their plugin Feb 11, 2026
@okorohelijah
Copy link
Contributor Author

The doc for migrating to spm doesnt seem to include adding the FlutterFramework dependency. I plan on updating it

@okorohelijah okorohelijah marked this pull request as ready for review February 12, 2026 09:57
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a validation check to prompt plugin authors to add Swift Package Manager (SPM) compatibility for their iOS and macOS plugins. The check runs when refreshPluginsList is called for a plugin's example app. It verifies if a plugin with a podspec also has a Package.swift file, and if so, whether it correctly depends on FlutterFramework. Warnings are displayed to guide plugin authors on how to add or fix their SPM support. The changes include new logic in flutter_plugins.dart and plugins.dart, along with corresponding unit tests.

@jmagman jmagman added the platform-ios iOS applications specifically label Feb 12, 2026
Copy link
Contributor

@vashworth vashworth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move all logic into DarwinDependencyManagement?

@okorohelijah okorohelijah requested a review from a team as a code owner February 18, 2026 12:27
@github-actions github-actions bot added a: desktop Running on desktop team-ios Owned by iOS platform team and removed platform-ios iOS applications specifically labels Feb 18, 2026
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simplified by adding the warning to _evaluatePluginsAndPrintWarnings, which already checks if the plugin is SwiftPM/CocoaPods compatible.

Future<({int totalCount, int swiftPackageCount, int podCount})> _evaluatePluginsAndPrintWarnings({
required FlutterDarwinPlatform platform,
required XcodeBasedProject xcodeProject,
required bool hostPlatformIsMacOS,
}) async {
var pluginCount = 0;
var swiftPackageCount = 0;
var cocoapodCount = 0;
for (final Plugin plugin in _plugins) {
if (plugin.platforms[platform.name] == null) {
continue;
}
final String? swiftPackagePath = plugin.pluginSwiftPackageManifestPath(
_fileSystem,
platform.name,
);
final bool swiftPackageManagerCompatible =
swiftPackagePath != null && _fileSystem.file(swiftPackagePath).existsSync();
final String? podspecPath = plugin.pluginPodspecPath(_fileSystem, platform.name);
final bool cocoaPodsCompatible =
podspecPath != null && _fileSystem.file(podspecPath).existsSync();
// If a plugin is missing both a Package.swift and Podspec, it won't be
// included by either Swift Package Manager or Cocoapods. This can happen
// when a plugin doesn't have native platform code.
// For example, image_picker_macos only uses dart code.
if (!swiftPackageManagerCompatible && !cocoaPodsCompatible) {
continue;
}

I'd do something like

Future<({int totalCount, int swiftPackageCount, int podCount})> _evaluatePluginsAndPrintWarnings({
   ...
  }) async {
    String? pluginFromExampleApp = _loadPluginFromExampleProject(...);
    for (final Plugin plugin in _plugins) {
      ...
       if (!swiftPackageManagerCompatible && !cocoaPodsCompatible) { 
         continue; 
       } 
      if (plugin.name == pluginFromExampleApp) {
          if (swiftPackageManagerCompatible && !_hasFlutterFrameworkDependency()) {
            _logger.printWarning(...)
          } else if (!swiftPackageManagerCompatible) {
            _logger.printWarning(...)
          }
      }
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_evaluatePluginsAndPrintWarnings operates on the app's dependency plugins during every build and targets app developers while the validation only fires for plugin example apps, targets the parent plugin being developed, and does a deeper content analysis of the Package.swift (checking for FlutterFramework dependency declarations while filtering out comments). I thought merging them could be be a mix of 2 different things so I kept them separate for clarity wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer to separate the call path, maybe worth creating a common helper function to be called by both path?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @hellohuanlin. If we're going to keep them separate, let's move the common logic into a helper function

' .package(name: "FlutterFramework", path: "../FlutterFramework")\n'
'And add FlutterFramework as a target dependency:\n'
' .product(name: "FlutterFramework", package: "FlutterFramework")\n'
'See $kSwiftPackageManagerDocsUrl for more information.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait for flutter/website#12979 to land, so we can point to the docs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer to separate the call path, maybe worth creating a common helper function to be called by both path?

@@ -129,6 +129,9 @@ class DarwinDependencyManagement {
);

_analytics.send(event);

// Validate Swift Package Manager support for plugin example apps and print warning if it doesn't support Swift Package Manager.
_validatePluginSwiftPackageManagerSupport(platform: platform);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's unclear from the function name that this is for plugin's example app, rather than a regular app. How about something like _validatePluginSwiftPMSupportViaPluginExampleApp

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or _validateExampleAppPluginSupportsSwiftPackageManager


/// Tracks which plugin/platform combinations have already been warned about
/// during this session to avoid duplicate warnings.
static final Set<String> _spmValidationWarningsShown = <String>{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Worth commenting how duplicate can happen. (i'm also curious)
  2. why static?
  3. let's be consistent and pick one among SPM vs SwiftPM vs SwiftPackageManager naming

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed

return;
}

final cacheKey = '${parentPlugin.name}:${platform.name}';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uber nit: cacheKey sounds like you are fetching something from the cache. How about something like dedupKey

///
/// This looks for common patterns used to declare a FlutterFramework dependency:
/// - `.package(name: "FlutterFramework", path: "../FlutterFramework")` - package dependency
/// - `.product(name: "FlutterFramework", package: "FlutterFramework")` - target dependency
Copy link
Contributor

@hellohuanlin hellohuanlin Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with SwiftPM, but can target dependency be just a string (if no naming conflict)? Something like:

  dependencies: [
    "FlutterFramework",
  ]

class SwiftPackageManagerPluginValidationResult {
SwiftPackageManagerPluginValidationResult({
required this.hasPodspec,
required this.hasPackageSwift,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I assume this refers to "Package.swift" file? How about hasSwiftPMManifest?

final bool hasFlutterFrameworkDependency;
final List<String> validationMessages;

bool get isFullyCompatible => hasPackageSwift && hasFlutterFrameworkDependency;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does "fully compatible" mean? (and what is "partially" compatible? the ones without
"FlutterFramework" dependency?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @hellohuanlin. If we're going to keep them separate, let's move the common logic into a helper function

@@ -129,6 +129,9 @@ class DarwinDependencyManagement {
);

_analytics.send(event);

// Validate Swift Package Manager support for plugin example apps and print warning if it doesn't support Swift Package Manager.
_validatePluginSwiftPackageManagerSupport(platform: platform);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or _validateExampleAppPluginSupportsSwiftPackageManager


/// Tracks which plugin/platform combinations have already been warned about
/// during this session to avoid duplicate warnings.
static final Set<String> _spmValidationWarningsShown = <String>{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed

/// Checks if the current project is a plugin example app and validates
/// the parent plugin's Swift Package Manager compatibility:
/// 1. If the plugin has a podspec but no Package.swift, prompts the user to
/// add SPM support.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// add SPM support.
/// add SwiftPM support.

/// during this session to avoid duplicate warnings.
static final Set<String> _spmValidationWarningsShown = <String>{};

/// Validates Swift Package Manager support for plugins in the current project.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Validates Swift Package Manager support for plugins in the current project.
/// Validates Swift Package Manager support for the plugin when building it's example app.

/// 2. If the plugin has a Package.swift, validates that it has a dependency
/// on FlutterFramework.
///
/// Warnings are printed to inform plugin authors about SPM compatibility issues.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Warnings are printed to inform plugin authors about SPM compatibility issues.
/// Warnings are printed to inform plugin authors about SwiftPM compatibility issues.

Comment on lines +281 to +310
final Directory projectDir = _project.directory;
if (!projectDir.path.endsWith('example')) {
return;
}

final Directory parentDir = projectDir.parent;
final File parentPubspec = parentDir.childFile('pubspec.yaml');
if (!parentPubspec.existsSync()) {
return;
}

final FlutterProject parentProject;
try {
parentProject = FlutterProject.fromDirectory(parentDir);
} on Exception catch (e) {
_logger.printTrace('Failed to parse parent project for SPM validation: $e');
return;
}

if (!parentProject.isPlugin) {
return;
}

final Plugin? parentPlugin = _plugins
.where((Plugin p) => p.name == parentProject.manifest.appName)
.firstOrNull;

if (parentPlugin == null) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move this logic into it's own method so it's clear what it's doing. For example, something like

Plugin? pluginFromExampleApp = _loadPluginFromExampleProject(...);

Comment on lines +324 to +327
if (result.validationMessages.isNotEmpty) {
_spmValidationWarningsShown.add(cacheKey);
result.validationMessages.forEach(_logger.printWarning);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validationMessages does not need to be a List, it can just be a string since there is only ever one

return;
}

final SwiftPackageManagerPluginValidationResult result =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing out of this result is used except the validationMessages. Instead of returning a class, just return a String

Comment on lines +400 to +408
// Remove both single-line (//) and block (/* ... */) comments.
final String uncommentedContents = contents.replaceAll(RegExp(r'//.*|/\*[\s\S]*?\*/'), '');

final bool hasPackageDependency = uncommentedContents.contains(
RegExp(r'\.package\s*\(\s*name\s*:\s*"FlutterFramework"'),
);
final bool hasTargetDependency = uncommentedContents.contains(
RegExp(r'\.product\s*\(\s*name\s*:\s*"FlutterFramework"'),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather make this check overly generous rather than have potential false positives. Let's just check if the Package.swift contains the string FlutterFramework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: desktop Running on desktop team-ios Owned by iOS platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prompt plugin authors to add Swift Package Manager compatibility to their plugin

4 participants