Skip to content

Commit

Permalink
Improve Dart plugin registration handling (#122046)
Browse files Browse the repository at this point in the history
Improve Dart plugin registration handling
  • Loading branch information
stuartmorgan authored Mar 9, 2023
1 parent 7e000b2 commit 62116f9
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 113 deletions.
120 changes: 80 additions & 40 deletions packages/flutter_tools/lib/src/flutter_plugins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1191,13 +1191,13 @@ bool hasPlugins(FlutterProject project) {

/// Resolves the platform implementation for Dart-only plugins.
///
/// * If there are multiple direct pub dependencies on packages that implement the
/// frontend plugin for the current platform, fail.
/// * If there is only one dependency on a package that implements the
/// frontend plugin for the current platform, use that.
/// * If there is a single direct dependency on a package that implements the
/// frontend plugin for the target platform, this package is the selected implementation.
/// * If there is no direct dependency on a package that implements the frontend
/// plugin for the target platform, and the frontend plugin has a default implementation
/// for the target platform the default implementation is selected.
/// frontend plugin for the current platform, use that.
/// * If there is no direct dependency on a package that implements the
/// frontend plugin, but there is a default for the current platform,
/// use that.
/// * Else fail.
///
/// For more details, https://flutter.dev/go/federated-plugins.
Expand All @@ -1214,11 +1214,15 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
MacOSPlugin.kConfigKey,
WindowsPlugin.kConfigKey,
];
final Map<String, PluginInterfaceResolution> directDependencyResolutions
= <String, PluginInterfaceResolution>{};
final Map<String, List<PluginInterfaceResolution>> possibleResolutions
= <String, List<PluginInterfaceResolution>>{};
final Map<String, String> defaultImplementations = <String, String>{};
bool didFindError = false;
// Generates a key for the maps above.
String getResolutionKey({required String platform, required String packageName}) {
return '$packageName:$platform';
}

bool hasPubspecError = false;
for (final Plugin plugin in plugins) {
for (final String platform in platforms) {
if (plugin.platforms[platform] == null &&
Expand Down Expand Up @@ -1257,11 +1261,12 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
'\n'
);
}
didFindError = true;
hasPubspecError = true;
continue;
}
final String defaultImplementationKey = getResolutionKey(platform: platform, packageName: plugin.name);
if (defaultImplementation != null) {
defaultImplementations['$platform/${plugin.name}'] = defaultImplementation;
defaultImplementations[defaultImplementationKey] = defaultImplementation;
continue;
} else {
// An app-facing package (i.e., one with no 'implements') with an
Expand All @@ -1281,52 +1286,87 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
minFlutterVersion.compareTo(semver.Version(2, 11, 0)) >= 0;
if (!isDesktop || hasMinVersionForImplementsRequirement) {
implementsPackage = plugin.name;
defaultImplementations['$platform/${plugin.name}'] = plugin.name;
defaultImplementations[defaultImplementationKey] = plugin.name;
} else {
// If it doesn't meet any of the conditions, it isn't eligible for
// auto-registration.
continue;
}
}
}
// If there's no Dart implementation, there's nothing to register.
if (plugin.pluginDartClassPlatforms[platform] == null ||
plugin.pluginDartClassPlatforms[platform] == 'none') {
continue;
}
final String resolutionKey = '$platform/$implementsPackage';
if (directDependencyResolutions.containsKey(resolutionKey)) {
final PluginInterfaceResolution? currResolution = directDependencyResolutions[resolutionKey];
if (currResolution != null && currResolution.plugin.isDirectDependency) {
if (plugin.isDirectDependency) {
if (throwOnPluginPubspecError) {
globals.printError(
'Plugin `${plugin.name}` implements an interface for `$platform`, which was already '
'implemented by plugin `${currResolution.plugin.name}`.\n'
'To fix this issue, remove either dependency from pubspec.yaml.'
'\n\n'
);
}
didFindError = true;
}
// Use the plugin implementation added by the user as a direct dependency.
continue;
}

// If it hasn't been skipped, it's a candidate for auto-registration, so
// add it as a possible resolution.
final String resolutionKey = getResolutionKey(platform: platform, packageName: implementsPackage);
if (!possibleResolutions.containsKey(resolutionKey)) {
possibleResolutions[resolutionKey] = <PluginInterfaceResolution>[];
}
directDependencyResolutions[resolutionKey] = PluginInterfaceResolution(
possibleResolutions[resolutionKey]!.add(PluginInterfaceResolution(
plugin: plugin,
platform: platform,
);
));
}
}
if (didFindError && throwOnPluginPubspecError) {
if (hasPubspecError && throwOnPluginPubspecError) {
throwToolExit('Please resolve the errors');
}

// Now resolve all the possible resolutions to a single option for each
// plugin, or throw if that's not possible.
bool hasResolutionError = false;
final List<PluginInterfaceResolution> finalResolution = <PluginInterfaceResolution>[];
for (final MapEntry<String, PluginInterfaceResolution> resolution in directDependencyResolutions.entries) {
if (resolution.value.plugin.isDirectDependency) {
finalResolution.add(resolution.value);
} else if (defaultImplementations.containsKey(resolution.key)) {
// Pick the default implementation.
if (defaultImplementations[resolution.key] == resolution.value.plugin.name) {
finalResolution.add(resolution.value);
for (final MapEntry<String, List<PluginInterfaceResolution>> entry in possibleResolutions.entries) {
final List<PluginInterfaceResolution> candidates = entry.value;
// If there's only one candidate, use it.
if (candidates.length == 1) {
finalResolution.add(candidates.first);
continue;
}
// Next, try direct dependencies of the resolving application.
final Iterable<PluginInterfaceResolution> directDependencies = candidates.where((PluginInterfaceResolution r) {
return r.plugin.isDirectDependency;
});
if (directDependencies.isNotEmpty) {
if (directDependencies.length > 1) {
globals.printError(
'Plugin ${entry.key} has conflicting direct dependency implementations:\n'
'${directDependencies.map((PluginInterfaceResolution r) => ' ${r.plugin.name}\n').join()}'
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n'
);
hasResolutionError = true;
} else {
finalResolution.add(directDependencies.first);
}
continue;
}
// Next, defer to the default implementation if there is one.
final String? defaultPackageName = defaultImplementations[entry.key];
if (defaultPackageName != null) {
final int defaultIndex = candidates
.indexWhere((PluginInterfaceResolution r) => r.plugin.name == defaultPackageName);
if (defaultIndex != -1) {
finalResolution.add(candidates[defaultIndex]);
continue;
}
}
// Otherwise, require an explicit choice.
if (candidates.length > 1) {
globals.printError(
'Plugin ${entry.key} has multiple possible implementations:\n'
'${candidates.map((PluginInterfaceResolution r) => ' ${r.plugin.name}\n').join()}'
'To fix this issue, add one of these dependencies to pubspec.yaml.\n'
);
hasResolutionError = true;
continue;
}
}
if (hasResolutionError) {
throwToolExit('Please resolve the errors');
}
return finalResolution;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/flutter_tools/lib/src/plugins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -418,4 +418,9 @@ class PluginInterfaceResolution {
'dartClass': plugin.pluginDartClassPlatforms[platform] ?? '',
};
}

@override
String toString() {
return '<PluginInterfaceResolution ${plugin.name} for $platform>';
}
}
Loading

0 comments on commit 62116f9

Please sign in to comment.