feat: Rework getting plugin implementation candidates and plugin resolution (#145258)
Part of #137040 and #80374 - Extracted getting plugin implementation candidates and the default implementation to its own method - Extracted resolving the plugin implementation to its own method - Simplify candidate selection algorithm - Support overriding inline dart implementation for an app-facing plugin - Throw error, if a federated plugin implements an app-facing plugin, but also references a default implementation - Throw error, if a plugin provides an inline implementation, but also references a default implementation
This commit is contained in:
parent
b8dc9da9b6
commit
be3e916443
@ -97,7 +97,7 @@ Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = t
|
||||
package.name,
|
||||
packageRoot,
|
||||
project.manifest.dependencies,
|
||||
fileSystem: fs
|
||||
fileSystem: fs,
|
||||
);
|
||||
if (plugin != null) {
|
||||
plugins.add(plugin);
|
||||
@ -186,9 +186,9 @@ bool _writeFlutterPluginsList(
|
||||
pluginsMap[platformKey] = _createPluginMapOfPlatform(plugins, platformKey);
|
||||
}
|
||||
|
||||
final Map<String, Object> result = <String, Object> {};
|
||||
final Map<String, Object> result = <String, Object>{};
|
||||
|
||||
result['info'] = 'This is a generated file; do not edit or check into version control.';
|
||||
result['info'] = 'This is a generated file; do not edit or check into version control.';
|
||||
result[_kFlutterPluginsPluginListKey] = pluginsMap;
|
||||
/// The dependencyGraph object is kept for backwards compatibility, but
|
||||
/// should be removed once migration is complete.
|
||||
@ -1145,7 +1145,7 @@ bool hasPlugins(FlutterProject project) {
|
||||
// TODO(stuartmorgan): Expand implementation to apply to all implementations,
|
||||
// not just Dart-only, per the federated plugin spec.
|
||||
List<PluginInterfaceResolution> resolvePlatformImplementation(
|
||||
List<Plugin> plugins
|
||||
List<Plugin> plugins,
|
||||
) {
|
||||
const Iterable<String> platformKeys = <String>[
|
||||
AndroidPlugin.kConfigKey,
|
||||
@ -1154,127 +1154,247 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
|
||||
MacOSPlugin.kConfigKey,
|
||||
WindowsPlugin.kConfigKey,
|
||||
];
|
||||
final List<PluginInterfaceResolution> finalResolution = <PluginInterfaceResolution>[];
|
||||
final List<PluginInterfaceResolution> pluginResolutions = <PluginInterfaceResolution>[];
|
||||
bool hasResolutionError = false;
|
||||
bool hasPluginPubspecError = false;
|
||||
|
||||
for (final String platformKey in platformKeys) {
|
||||
// Key: the plugin name
|
||||
final Map<String, List<Plugin>> possibleResolutions = <String, List<Plugin>>{};
|
||||
// Key: the plugin name, value: the list of plugin candidates for the implementation of [platformKey].
|
||||
final Map<String, List<Plugin>> pluginImplCandidates = <String, List<Plugin>>{};
|
||||
|
||||
// Key: the plugin name, value: the plugin name of the default implementation of [platformKey].
|
||||
final Map<String, String> defaultImplementations = <String, String>{};
|
||||
|
||||
for (final Plugin plugin in plugins) {
|
||||
final String? defaultImplementation = plugin.defaultPackagePlatforms[platformKey];
|
||||
if (plugin.platforms[platformKey] == null && defaultImplementation == null) {
|
||||
// The plugin doesn't implement this platform.
|
||||
continue;
|
||||
}
|
||||
String? implementsPackage = plugin.implementsPackage;
|
||||
if (implementsPackage == null || implementsPackage.isEmpty) {
|
||||
final bool hasInlineDartImplementation =
|
||||
plugin.pluginDartClassPlatforms[platformKey] != null;
|
||||
if (defaultImplementation == null && !hasInlineDartImplementation) {
|
||||
// Skip native inline PluginPlatform implementation
|
||||
continue;
|
||||
}
|
||||
if (defaultImplementation != null) {
|
||||
defaultImplementations[plugin.name] = defaultImplementation;
|
||||
continue;
|
||||
} else {
|
||||
// An app-facing package (i.e., one with no 'implements') with an
|
||||
// inline implementation should be its own default implementation.
|
||||
// Desktop platforms originally did not work that way, and enabling
|
||||
// it unconditionally would break existing published plugins, so
|
||||
// only treat it as such if either:
|
||||
// - the platform is not desktop, or
|
||||
// - the plugin requires at least Flutter 2.11 (when this opt-in logic
|
||||
// was added), so that existing plugins continue to work.
|
||||
// See https://github.com/flutter/flutter/issues/87862 for details.
|
||||
final bool isDesktop = platformKey == 'linux' || platformKey == 'macos' || platformKey == 'windows';
|
||||
final semver.VersionConstraint? flutterConstraint = plugin.flutterConstraint;
|
||||
final semver.Version? minFlutterVersion = flutterConstraint != null &&
|
||||
flutterConstraint is semver.VersionRange ? flutterConstraint.min : null;
|
||||
final bool hasMinVersionForImplementsRequirement = minFlutterVersion != null &&
|
||||
minFlutterVersion.compareTo(semver.Version(2, 11, 0)) >= 0;
|
||||
if (!isDesktop || hasMinVersionForImplementsRequirement) {
|
||||
implementsPackage = plugin.name;
|
||||
defaultImplementations[plugin.name] = 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[platformKey] == null ||
|
||||
plugin.pluginDartClassPlatforms[platformKey] == 'none') {
|
||||
final String? error = _validatePlugin(plugin, platformKey);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasPluginPubspecError = true;
|
||||
continue;
|
||||
}
|
||||
final String? implementsPluginName = _getImplementedPlugin(plugin, platformKey);
|
||||
final String? defaultImplPluginName = _getDefaultImplPlugin(plugin, platformKey);
|
||||
|
||||
// If it hasn't been skipped, it's a candidate for auto-registration, so
|
||||
// add it as a possible resolution.
|
||||
possibleResolutions.putIfAbsent(implementsPackage, () => <Plugin>[]);
|
||||
possibleResolutions[implementsPackage]!.add(plugin);
|
||||
if (defaultImplPluginName != null) {
|
||||
// Each plugin can only have one default implementation for this [platformKey].
|
||||
defaultImplementations[plugin.name] = defaultImplPluginName;
|
||||
}
|
||||
if (implementsPluginName != null) {
|
||||
pluginImplCandidates.putIfAbsent(implementsPluginName, () => <Plugin>[]);
|
||||
pluginImplCandidates[implementsPluginName]!.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
final List<Plugin> pluginResolution = <Plugin>[];
|
||||
final Map<String, Plugin> pluginResolution = <String, Plugin>{};
|
||||
|
||||
// Resolve all the possible resolutions to a single option for each plugin, or throw if that's not possible.
|
||||
for (final MapEntry<String, List<Plugin>> entry in possibleResolutions.entries) {
|
||||
final List<Plugin> candidates = entry.value;
|
||||
// If there's only one candidate, use it.
|
||||
if (candidates.length == 1) {
|
||||
pluginResolution.add(candidates.first);
|
||||
continue;
|
||||
}
|
||||
// Next, try direct dependencies of the resolving application.
|
||||
final Iterable<Plugin> directDependencies = candidates.where((Plugin plugin) {
|
||||
return plugin.isDirectDependency;
|
||||
});
|
||||
if (directDependencies.isNotEmpty) {
|
||||
if (directDependencies.length > 1) {
|
||||
globals.printError(
|
||||
'Plugin ${entry.key}:$platformKey has conflicting direct dependency implementations:\n'
|
||||
'${directDependencies.map((Plugin plugin) => ' ${plugin.name}\n').join()}'
|
||||
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n'
|
||||
);
|
||||
hasResolutionError = true;
|
||||
} else {
|
||||
pluginResolution.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((Plugin plugin) => plugin.name == defaultPackageName);
|
||||
if (defaultIndex != -1) {
|
||||
pluginResolution.add(candidates[defaultIndex]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Otherwise, require an explicit choice.
|
||||
if (candidates.length > 1) {
|
||||
globals.printError(
|
||||
'Plugin ${entry.key}:$platformKey has multiple possible implementations:\n'
|
||||
'${candidates.map((Plugin plugin) => ' ${plugin.name}\n').join()}'
|
||||
'To fix this issue, add one of these dependencies to pubspec.yaml.\n'
|
||||
);
|
||||
// Now resolve all the possible resolutions to a single option for each
|
||||
// plugin, or throw if that's not possible.
|
||||
for (final MapEntry<String, List<Plugin>> implCandidatesEntry in pluginImplCandidates.entries) {
|
||||
final (Plugin? resolution, String? error) = _resolveImplementationOfPlugin(
|
||||
platformKey: platformKey,
|
||||
pluginName: implCandidatesEntry.key,
|
||||
candidates: implCandidatesEntry.value,
|
||||
defaultPackageName: defaultImplementations[implCandidatesEntry.key],
|
||||
);
|
||||
if (error != null) {
|
||||
globals.printError(error);
|
||||
hasResolutionError = true;
|
||||
continue;
|
||||
} else if (resolution != null) {
|
||||
pluginResolution[implCandidatesEntry.key] = resolution;
|
||||
}
|
||||
}
|
||||
|
||||
finalResolution.addAll(
|
||||
pluginResolution.map((Plugin plugin) =>
|
||||
PluginInterfaceResolution(plugin: plugin, platform: platformKey)),
|
||||
pluginResolutions.addAll(
|
||||
pluginResolution.values.map((Plugin plugin) {
|
||||
return PluginInterfaceResolution(plugin: plugin, platform: platformKey);
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (hasPluginPubspecError) {
|
||||
throwToolExit('Please resolve the plugin pubspec errors');
|
||||
}
|
||||
if (hasResolutionError) {
|
||||
throwToolExit('Please resolve the plugin implementation selection errors');
|
||||
}
|
||||
return finalResolution;
|
||||
return pluginResolutions;
|
||||
}
|
||||
|
||||
/// Validates conflicting plugin parameters in pubspec, such as
|
||||
/// `dartPluginClass`, `default_package` and `implements`.
|
||||
///
|
||||
/// Returns an error, if failing.
|
||||
String? _validatePlugin(Plugin plugin, String platformKey) {
|
||||
final String? implementsPackage = plugin.implementsPackage;
|
||||
final String? defaultImplPluginName = plugin.defaultPackagePlatforms[platformKey];
|
||||
|
||||
if (plugin.name == implementsPackage &&
|
||||
plugin.name == defaultImplPluginName) {
|
||||
// Allow self implementing and self as platform default.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (defaultImplPluginName != null) {
|
||||
if (implementsPackage != null && implementsPackage.isNotEmpty) {
|
||||
return 'Plugin ${plugin.name}:$platformKey provides an implementation for $implementsPackage '
|
||||
'and also references a default implementation for $defaultImplPluginName, which is currently not supported. '
|
||||
'Ask the maintainers of ${plugin.name} to either remove the implementation via `implements: $implementsPackage` '
|
||||
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
|
||||
}
|
||||
|
||||
if (_hasPluginInlineDartImpl(plugin, platformKey)) {
|
||||
return 'Plugin ${plugin.name}:$platformKey which provides an inline implementation '
|
||||
'cannot also reference a default implementation for $defaultImplPluginName. '
|
||||
'Ask the maintainers of ${plugin.name} to either remove the implementation via `platforms: $platformKey: dartPluginClass` '
|
||||
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Determine if this [plugin] serves as implementation for an app-facing
|
||||
/// package for the given platform [platformKey].
|
||||
///
|
||||
/// If so, return the package name, which the [plugin] implements.
|
||||
///
|
||||
/// Options:
|
||||
/// * The [plugin] (e.g. 'url_launcher_linux') serves as implementation for
|
||||
/// an app-facing package (e.g. 'url_launcher').
|
||||
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also
|
||||
/// serves as its own default implementation.
|
||||
/// * The [plugin] does not provide an implementation.
|
||||
String? _getImplementedPlugin(Plugin plugin, String platformKey) {
|
||||
final bool hasInlineDartImpl = _hasPluginInlineDartImpl(plugin, platformKey);
|
||||
|
||||
if (hasInlineDartImpl) {
|
||||
final String? implementsPackage = plugin.implementsPackage;
|
||||
|
||||
// Only can serve, if the plugin has a dart inline implementation.
|
||||
if (implementsPackage != null && implementsPackage.isNotEmpty) {
|
||||
return implementsPackage;
|
||||
}
|
||||
|
||||
if (_isEligibleDartSelfImpl(plugin, platformKey)) {
|
||||
// The inline Dart plugin implements itself.
|
||||
return plugin.name;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Determine if this [plugin] (or package) references a default plugin with an
|
||||
/// implementation for the given platform [platformKey].
|
||||
///
|
||||
/// If so, return the plugin name, which provides the default implementation.
|
||||
///
|
||||
/// Options:
|
||||
/// * The [plugin] (e.g. 'url_launcher') references a default implementation
|
||||
/// (e.g. 'url_launcher_linux').
|
||||
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also
|
||||
/// serves as its own default implementation.
|
||||
/// * The [plugin] does not reference a default implementation.
|
||||
String? _getDefaultImplPlugin(Plugin plugin, String platformKey) {
|
||||
final String? defaultImplPluginName =
|
||||
plugin.defaultPackagePlatforms[platformKey];
|
||||
if (defaultImplPluginName != null) {
|
||||
return defaultImplPluginName;
|
||||
}
|
||||
|
||||
if (_hasPluginInlineDartImpl(plugin, platformKey) &&
|
||||
_isEligibleDartSelfImpl(plugin, platformKey)) {
|
||||
// The inline Dart plugin serves as its own default implementation.
|
||||
return plugin.name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Determine if the [plugin]'s inline dart implementation for the
|
||||
/// [platformKey] is eligible to serve as its own default.
|
||||
///
|
||||
/// An app-facing package (i.e., one with no 'implements') with an
|
||||
/// inline implementation should be its own default implementation.
|
||||
/// Desktop platforms originally did not work that way, and enabling
|
||||
/// it unconditionally would break existing published plugins, so
|
||||
/// only treat it as such if either:
|
||||
/// - the platform is not desktop, or
|
||||
/// - the plugin requires at least Flutter 2.11 (when this opt-in logic
|
||||
/// was added), so that existing plugins continue to work.
|
||||
/// See https://github.com/flutter/flutter/issues/87862 for details.
|
||||
bool _isEligibleDartSelfImpl(Plugin plugin, String platformKey) {
|
||||
final bool isDesktop = platformKey == 'linux' || platformKey == 'macos' || platformKey == 'windows';
|
||||
final semver.VersionConstraint? flutterConstraint = plugin.flutterConstraint;
|
||||
final semver.Version? minFlutterVersion = flutterConstraint != null &&
|
||||
flutterConstraint is semver.VersionRange ? flutterConstraint.min : null;
|
||||
final bool hasMinVersionForImplementsRequirement = minFlutterVersion != null &&
|
||||
minFlutterVersion.compareTo(semver.Version(2, 11, 0)) >= 0;
|
||||
return !isDesktop || hasMinVersionForImplementsRequirement;
|
||||
}
|
||||
|
||||
/// Determine if the plugin provides an inline dart implementation.
|
||||
bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
|
||||
return plugin.pluginDartClassPlatforms[platformKey] != null &&
|
||||
plugin.pluginDartClassPlatforms[platformKey] != 'none';
|
||||
}
|
||||
|
||||
/// Get the resolved plugin [resolution] from the [candidates] serving as implementation for
|
||||
/// [pluginName].
|
||||
///
|
||||
/// Returns an [error] string, if failing.
|
||||
(Plugin? resolution, String? error) _resolveImplementationOfPlugin({
|
||||
required String platformKey,
|
||||
required String pluginName,
|
||||
required List<Plugin> candidates,
|
||||
String? defaultPackageName,
|
||||
}) {
|
||||
// If there's only one candidate, use it.
|
||||
if (candidates.length == 1) {
|
||||
return (candidates.first, null);
|
||||
}
|
||||
// Next, try direct dependencies of the resolving application.
|
||||
final Iterable<Plugin> directDependencies = candidates.where((Plugin plugin) {
|
||||
return plugin.isDirectDependency;
|
||||
});
|
||||
if (directDependencies.isNotEmpty) {
|
||||
if (directDependencies.length > 1) {
|
||||
|
||||
// Allow overriding an app-facing package with an inline implementation (which is a direct dependency)
|
||||
// with another direct dependency which implements the app-facing package.
|
||||
final Iterable<Plugin> implementingPackage = directDependencies.where((Plugin plugin) => plugin.implementsPackage != null && plugin.implementsPackage!.isNotEmpty);
|
||||
final Set<Plugin> appFacingPackage = directDependencies.toSet()..removeAll(implementingPackage);
|
||||
if (implementingPackage.length == 1 && appFacingPackage.length == 1) {
|
||||
return (implementingPackage.first, null);
|
||||
}
|
||||
|
||||
return (
|
||||
null,
|
||||
'Plugin $pluginName:$platformKey has conflicting direct dependency implementations:\n'
|
||||
'${directDependencies.map((Plugin plugin) => ' ${plugin.name}\n').join()}'
|
||||
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n',
|
||||
);
|
||||
} else {
|
||||
return (directDependencies.first, null);
|
||||
}
|
||||
}
|
||||
// Next, defer to the default implementation if there is one.
|
||||
if (defaultPackageName != null) {
|
||||
final int defaultIndex = candidates
|
||||
.indexWhere((Plugin plugin) => plugin.name == defaultPackageName);
|
||||
if (defaultIndex != -1) {
|
||||
return (candidates[defaultIndex], null);
|
||||
}
|
||||
}
|
||||
// Otherwise, require an explicit choice.
|
||||
if (candidates.length > 1) {
|
||||
return (
|
||||
null,
|
||||
'Plugin $pluginName:$platformKey has multiple possible implementations:\n'
|
||||
'${candidates.map((Plugin plugin) => ' ${plugin.name}\n').join()}'
|
||||
'To fix this issue, add one of these dependencies to pubspec.yaml.\n',
|
||||
);
|
||||
}
|
||||
// No implementation provided
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
/// Generates the Dart plugin registrant, which allows to bind a platform
|
||||
@ -1321,7 +1441,7 @@ Future<void> generateMainDartWithPluginRegistrant(
|
||||
} on FileSystemException catch (error) {
|
||||
globals.printWarning(
|
||||
'Unable to remove ${newMainDart.path}, received error: $error.\n'
|
||||
'You might need to run flutter clean.'
|
||||
'You might need to run flutter clean.',
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -551,6 +551,190 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWithoutContext('selects user selected implementation despite inline implementation', () async {
|
||||
final Set<String> directDependencies = <String>{
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
};
|
||||
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'android': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherAndroid',
|
||||
},
|
||||
'ios': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherIos',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'user_selected_url_launcher_implementation',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'android': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherAndroid',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
expect(resolutions.length, equals(2));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'user_selected_url_launcher_implementation',
|
||||
'dartClass': 'UrlLauncherAndroid',
|
||||
'platform': 'android',
|
||||
})
|
||||
);
|
||||
expect(resolutions[1].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'url_launcher',
|
||||
'dartClass': 'UrlLauncherIos',
|
||||
'platform': 'ios',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'provides error when a plugin has a default implementation and implements another plugin',
|
||||
() async {
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux_1',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_1',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux_2',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_2',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the plugin pubspec errors',
|
||||
));
|
||||
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin url_launcher_linux_1:linux provides an implementation for url_launcher '
|
||||
'and also references a default implementation for url_launcher_linux_2, which is currently not supported. '
|
||||
'Ask the maintainers of url_launcher_linux_1 to either remove the implementation via `implements: url_launcher` '
|
||||
'or avoid referencing a default implementation via `platforms: linux: default_package: url_launcher_linux_2`.'
|
||||
'\n\n');
|
||||
});
|
||||
|
||||
testUsingContext(
|
||||
'provides error when a plugin has a default implementation and an inline implementation',
|
||||
() async {
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux',
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
null,
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the plugin pubspec errors',
|
||||
));
|
||||
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin url_launcher:linux which provides an inline implementation '
|
||||
'cannot also reference a default implementation for url_launcher_linux. '
|
||||
'Ask the maintainers of url_launcher to either remove the implementation via `platforms: linux: dartPluginClass` '
|
||||
'or avoid referencing a default implementation via `platforms: linux: default_package: url_launcher_linux`.'
|
||||
'\n\n');
|
||||
});
|
||||
|
||||
testUsingContext('provides error when user selected multiple implementations', () async {
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux_1',
|
||||
|
Loading…
x
Reference in New Issue
Block a user