feat: Support overriding native endorsed plugins (#137040)

These changes allow to override existing native endorsed (federated & inline) plugins with e.g. non-endorsed plugin implementations via direct dependencies as described in the section *Platform implementation selection* in the [design doc](https://docs.google.com/document/d/1LD7QjmzJZLCopUrFAAE98wOUQpjmguyGTN2wd_89Srs).

```yaml
# pubspec.yaml
name: example
dependencies:
  my_plugin: ^0.0.1
  my_plugin_android_alternative: ^0.0.1
```

Closes #80374, closes #59657

Related: Override Dart plugins (#87991, #79669)
This commit is contained in:
August 2024-07-11 20:54:01 +02:00 committed by GitHub
parent 90025e2964
commit d55a5f96c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1229 additions and 743 deletions

View File

@ -106,6 +106,12 @@ Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = t
return plugins; return plugins;
} }
/// Plugin resolution type to determine the injection mechanism.
enum _PluginResolutionType {
dart,
nativeOrDart,
}
// Key strings for the .flutter-plugins-dependencies file. // Key strings for the .flutter-plugins-dependencies file.
const String _kFlutterPluginsPluginListKey = 'plugins'; const String _kFlutterPluginsPluginListKey = 'plugins';
const String _kFlutterPluginsNameKey = 'name'; const String _kFlutterPluginsNameKey = 'name';
@ -181,9 +187,14 @@ bool _writeFlutterPluginsList(
project.web.pluginConfigKey, project.web.pluginConfigKey,
]; ];
final Map<String, List<Plugin>> resolvedPlatformPlugins = _resolvePluginImplementations(
plugins,
pluginResolutionType: _PluginResolutionType.nativeOrDart,
);
final Map<String, Object> pluginsMap = <String, Object>{}; final Map<String, Object> pluginsMap = <String, Object>{};
for (final String platformKey in platformKeys) { for (final String platformKey in platformKeys) {
pluginsMap[platformKey] = _createPluginMapOfPlatform(plugins, platformKey); pluginsMap[platformKey] = _createPluginMapOfPlatform(resolvedPlatformPlugins[platformKey] ?? <Plugin>[], platformKey);
} }
final Map<String, Object> result = <String, Object>{}; final Map<String, Object> result = <String, Object>{};
@ -212,18 +223,15 @@ bool _writeFlutterPluginsList(
} }
/// Creates a map representation of the [plugins] for those supported by [platformKey]. /// Creates a map representation of the [plugins] for those supported by [platformKey].
/// All given [plugins] must provide an implementation for the [platformKey].
List<Map<String, Object>> _createPluginMapOfPlatform( List<Map<String, Object>> _createPluginMapOfPlatform(
List<Plugin> plugins, List<Plugin> plugins,
String platformKey, String platformKey,
) { ) {
final Iterable<Plugin> resolvedPlatformPlugins = plugins.where((Plugin p) { final Set<String> pluginNames = plugins.map((Plugin plugin) => plugin.name).toSet();
return p.platforms.containsKey(platformKey);
});
final Set<String> pluginNames = resolvedPlatformPlugins.map((Plugin plugin) => plugin.name).toSet();
final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[]; final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[];
for (final Plugin plugin in resolvedPlatformPlugins) { for (final Plugin plugin in plugins) {
// This is guaranteed to be non-null due to the `where` filter above. assert(plugin.platforms[platformKey] != null, 'Plugin ${plugin.name} does not provide an implementation for $platformKey.');
final PluginPlatform platformPlugin = plugin.platforms[platformKey]!; final PluginPlatform platformPlugin = plugin.platforms[platformKey]!;
pluginInfo.add(<String, Object>{ pluginInfo.add(<String, Object>{
_kFlutterPluginsNameKey: plugin.name, _kFlutterPluginsNameKey: plugin.name,
@ -1051,10 +1059,9 @@ Future<void> injectBuildTimePluginFiles(
bool webPlatform = false, bool webPlatform = false,
}) async { }) async {
final List<Plugin> plugins = await findPlugins(project); final List<Plugin> plugins = await findPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files. final Map<String, List<Plugin>> pluginsByPlatform = _resolvePluginImplementations(plugins, pluginResolutionType: _PluginResolutionType.nativeOrDart);
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
if (webPlatform) { if (webPlatform) {
await _writeWebPluginRegistrant(project, plugins, destination); await _writeWebPluginRegistrant(project, pluginsByPlatform[WebPlugin.kConfigKey]!, destination);
} }
} }
@ -1081,22 +1088,22 @@ Future<void> injectPlugins(
DarwinDependencyManagement? darwinDependencyManagement, DarwinDependencyManagement? darwinDependencyManagement,
}) async { }) async {
final List<Plugin> plugins = await findPlugins(project); final List<Plugin> plugins = await findPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files. final Map<String, List<Plugin>> pluginsByPlatform = _resolvePluginImplementations(plugins, pluginResolutionType: _PluginResolutionType.nativeOrDart);
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
if (androidPlatform) { if (androidPlatform) {
await _writeAndroidPluginRegistrant(project, plugins); await _writeAndroidPluginRegistrant(project, pluginsByPlatform[AndroidPlugin.kConfigKey]!);
} }
if (iosPlatform) { if (iosPlatform) {
await _writeIOSPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, pluginsByPlatform[IOSPlugin.kConfigKey]!);
} }
if (linuxPlatform) { if (linuxPlatform) {
await _writeLinuxPluginFiles(project, plugins); await _writeLinuxPluginFiles(project, pluginsByPlatform[LinuxPlugin.kConfigKey]!);
} }
if (macOSPlatform) { if (macOSPlatform) {
await _writeMacOSPluginRegistrant(project, plugins); await _writeMacOSPluginRegistrant(project, pluginsByPlatform[MacOSPlugin.kConfigKey]!);
} }
if (windowsPlatform) { if (windowsPlatform) {
await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins); await writeWindowsPluginFiles(project, pluginsByPlatform[WindowsPlugin.kConfigKey]!, globals.templateRenderer, allowedPlugins: allowedPlugins);
} }
if (iosPlatform || macOSPlatform) { if (iosPlatform || macOSPlatform) {
final DarwinDependencyManagement darwinDependencyManagerSetup = darwinDependencyManagement ?? DarwinDependencyManagement( final DarwinDependencyManagement darwinDependencyManagerSetup = darwinDependencyManagement ?? DarwinDependencyManagement(
@ -1130,7 +1137,7 @@ bool hasPlugins(FlutterProject project) {
return _readFileContent(project.flutterPluginsFile) != null; return _readFileContent(project.flutterPluginsFile) != null;
} }
/// Resolves the platform implementation for Dart-only plugins. /// Resolves the plugin implementations for all platforms.
/// ///
/// * If there is only one dependency on a package that implements the /// * If there is only one dependency on a package that implements the
/// frontend plugin for the current platform, use that. /// frontend plugin for the current platform, use that.
@ -1142,42 +1149,125 @@ bool hasPlugins(FlutterProject project) {
/// * Else fail. /// * Else fail.
/// ///
/// For more details, https://flutter.dev/go/federated-plugins. /// For more details, https://flutter.dev/go/federated-plugins.
// TODO(stuartmorgan): Expand implementation to apply to all implementations, ///
// not just Dart-only, per the federated plugin spec. /// If [selectDartPluginsOnly] is enabled, only Dart plugin implementations are
/// considered. Else, native and Dart plugin implementations are considered.
List<PluginInterfaceResolution> resolvePlatformImplementation( List<PluginInterfaceResolution> resolvePlatformImplementation(
List<Plugin> plugins, List<Plugin> plugins, {
) { required bool selectDartPluginsOnly,
const Iterable<String> platformKeys = <String>[ }) {
AndroidPlugin.kConfigKey, final Map<String, List<Plugin>> resolution = _resolvePluginImplementations(
IOSPlugin.kConfigKey, plugins,
LinuxPlugin.kConfigKey, pluginResolutionType: selectDartPluginsOnly ? _PluginResolutionType.dart : _PluginResolutionType.nativeOrDart,
MacOSPlugin.kConfigKey, );
WindowsPlugin.kConfigKey, return resolution.entries.expand((MapEntry<String, List<Plugin>> entry) {
]; return entry.value.map((Plugin plugin) {
final List<PluginInterfaceResolution> pluginResolutions = <PluginInterfaceResolution>[]; return PluginInterfaceResolution(plugin: plugin, platform: entry.key);
bool hasResolutionError = false; });
bool hasPluginPubspecError = false; }).toList();
}
/// Resolves the plugin implementations for all platforms,
/// see [resolvePlatformImplementation].
///
/// Only plugins which provide the according platform implementation are returned.
Map<String, List<Plugin>> _resolvePluginImplementations(
List<Plugin> plugins, {
required _PluginResolutionType pluginResolutionType,
}) {
final Map<String, List<Plugin>> pluginsByPlatform = <String, List<Plugin>>{
AndroidPlugin.kConfigKey: <Plugin>[],
IOSPlugin.kConfigKey: <Plugin>[],
LinuxPlugin.kConfigKey: <Plugin>[],
MacOSPlugin.kConfigKey: <Plugin>[],
WindowsPlugin.kConfigKey: <Plugin>[],
WebPlugin.kConfigKey: <Plugin>[],
};
bool hasPluginPubspecError = false;
bool hasResolutionError = false;
for (final String platformKey in pluginsByPlatform.keys) {
final (
List<Plugin> platformPluginResolutions,
bool hasPlatformPluginPubspecError,
bool hasPlatformResolutionError
) = _resolvePluginImplementationsByPlatform(
plugins,
platformKey,
pluginResolutionType: pluginResolutionType,
);
if (hasPlatformPluginPubspecError) {
hasPluginPubspecError = true;
} else if (hasPlatformResolutionError) {
hasResolutionError = true;
} else {
pluginsByPlatform[platformKey] = platformPluginResolutions;
}
}
if (hasPluginPubspecError) {
throwToolExit('Please resolve the plugin pubspec errors');
}
if (hasResolutionError) {
throwToolExit('Please resolve the plugin implementation selection errors');
}
return pluginsByPlatform;
}
/// Resolves the plugins for the given [platformKey] (Dart-only or native
/// implementations).
(List<Plugin> pluginImplementations, bool hasPluginPubspecError, bool hasResolutionError) _resolvePluginImplementationsByPlatform(
Iterable<Plugin> plugins,
String platformKey, {
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
}) {
bool hasPluginPubspecError = false;
bool hasResolutionError = false;
for (final String platformKey in platformKeys) {
// Key: the plugin name, value: the list of plugin candidates for the implementation of [platformKey]. // Key: the plugin name, value: the list of plugin candidates for the implementation of [platformKey].
final Map<String, List<Plugin>> pluginImplCandidates = <String, List<Plugin>>{}; final Map<String, List<Plugin>> pluginImplCandidates = <String, List<Plugin>>{};
// Key: the plugin name, value: the plugin name of the default implementation of [platformKey]. // Key: the plugin name, value: the plugin of the default implementation of [platformKey].
final Map<String, String> defaultImplementations = <String, String>{}; final Map<String, Plugin> defaultImplementations = <String, Plugin>{};
for (final Plugin plugin in plugins) { for (final Plugin plugin in plugins) {
final String? error = _validatePlugin(plugin, platformKey); final String? error = _validatePlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
if (error != null) { if (error != null) {
globals.printError(error); globals.printError(error);
hasPluginPubspecError = true; hasPluginPubspecError = true;
continue; continue;
} }
final String? implementsPluginName = _getImplementedPlugin(plugin, platformKey); final String? implementsPluginName = _getImplementedPlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
final String? defaultImplPluginName = _getDefaultImplPlugin(plugin, platformKey); final String? defaultImplPluginName = _getDefaultImplPlugin(plugin, platformKey, pluginResolutionType: pluginResolutionType);
if (defaultImplPluginName != null) { if (defaultImplPluginName != null) {
final Plugin? defaultPackage = plugins.where((Plugin plugin) => plugin.name == defaultImplPluginName).firstOrNull;
if (defaultPackage != null) {
if (_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: _PluginResolutionType.nativeOrDart)) {
if (pluginResolutionType == _PluginResolutionType.nativeOrDart ||
_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: pluginResolutionType)) {
// Each plugin can only have one default implementation for this [platformKey]. // Each plugin can only have one default implementation for this [platformKey].
defaultImplementations[plugin.name] = defaultImplPluginName; defaultImplementations[plugin.name] = defaultPackage;
// No need to add the default plugin to `pluginImplCandidates`,
// as if the plugin is present and provides an implementation
// it is added via `_getImplementedPlugin`.
}
} else {
// Only warn, if neither an implementation for native nor for Dart is given.
globals.printWarning(
'Package ${plugin.name}:$platformKey references $defaultImplPluginName:$platformKey as the default plugin, but it does not provide an inline implementation.\n'
'Ask the maintainers of ${plugin.name} to either avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName` '
'or add an inline implementation to $defaultImplPluginName via `platforms: $platformKey:` `pluginClass` or `dartPluginClass`.\n',
);
}
} else {
globals.printWarning(
'Package ${plugin.name}:$platformKey references $defaultImplPluginName:$platformKey as the default plugin, but the package does not exist.\n'
'Ask the maintainers of ${plugin.name} to either avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName` '
'or create a plugin named $defaultImplPluginName.\n',
);
}
} }
if (implementsPluginName != null) { if (implementsPluginName != null) {
pluginImplCandidates.putIfAbsent(implementsPluginName, () => <Plugin>[]); pluginImplCandidates.putIfAbsent(implementsPluginName, () => <Plugin>[]);
@ -1185,6 +1275,7 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
} }
} }
// Key: the plugin name, value: the plugin which provides an implementation for [platformKey].
final Map<String, Plugin> pluginResolution = <String, Plugin>{}; final Map<String, Plugin> pluginResolution = <String, Plugin>{};
// Now resolve all the possible resolutions to a single option for each // Now resolve all the possible resolutions to a single option for each
@ -1192,9 +1283,10 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
for (final MapEntry<String, List<Plugin>> implCandidatesEntry in pluginImplCandidates.entries) { for (final MapEntry<String, List<Plugin>> implCandidatesEntry in pluginImplCandidates.entries) {
final (Plugin? resolution, String? error) = _resolveImplementationOfPlugin( final (Plugin? resolution, String? error) = _resolveImplementationOfPlugin(
platformKey: platformKey, platformKey: platformKey,
pluginResolutionType: pluginResolutionType,
pluginName: implCandidatesEntry.key, pluginName: implCandidatesEntry.key,
candidates: implCandidatesEntry.value, candidates: implCandidatesEntry.value,
defaultPackageName: defaultImplementations[implCandidatesEntry.key], defaultPackage: defaultImplementations[implCandidatesEntry.key],
); );
if (error != null) { if (error != null) {
globals.printError(error); globals.printError(error);
@ -1204,26 +1296,19 @@ List<PluginInterfaceResolution> resolvePlatformImplementation(
} }
} }
pluginResolutions.addAll( // Sort the plugins by name to keep ordering stable in generated files.
pluginResolution.values.map((Plugin plugin) { final List<Plugin> pluginImplementations = pluginResolution.values.toList()
return PluginInterfaceResolution(plugin: plugin, platform: platformKey); ..sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
}), return (pluginImplementations, hasPluginPubspecError, hasResolutionError);
);
}
if (hasPluginPubspecError) {
throwToolExit('Please resolve the plugin pubspec errors');
}
if (hasResolutionError) {
throwToolExit('Please resolve the plugin implementation selection errors');
}
return pluginResolutions;
} }
/// Validates conflicting plugin parameters in pubspec, such as /// Validates conflicting plugin parameters in pubspec, such as
/// `dartPluginClass`, `default_package` and `implements`. /// `dartPluginClass`, `default_package` and `implements`.
/// ///
/// Returns an error, if failing. /// Returns an error, if failing.
String? _validatePlugin(Plugin plugin, String platformKey) { String? _validatePlugin(Plugin plugin, String platformKey, {
required _PluginResolutionType pluginResolutionType,
}) {
final String? implementsPackage = plugin.implementsPackage; final String? implementsPackage = plugin.implementsPackage;
final String? defaultImplPluginName = plugin.defaultPackagePlatforms[platformKey]; final String? defaultImplPluginName = plugin.defaultPackagePlatforms[platformKey];
@ -1241,10 +1326,10 @@ String? _validatePlugin(Plugin plugin, String platformKey) {
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n'; 'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
} }
if (_hasPluginInlineDartImpl(plugin, platformKey)) { if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType)) {
return 'Plugin ${plugin.name}:$platformKey which provides an inline implementation ' return 'Plugin ${plugin.name}:$platformKey which provides an inline implementation '
'cannot also reference a default implementation for $defaultImplPluginName. ' 'cannot also reference a default implementation for $defaultImplPluginName. '
'Ask the maintainers of ${plugin.name} to either remove the implementation via `platforms: $platformKey: dartPluginClass` ' 'Ask the maintainers of ${plugin.name} to either remove the implementation via `platforms: $platformKey:${pluginResolutionType == _PluginResolutionType.dart ? ' dartPluginClass' : '` `pluginClass` or `dartPLuginClass'}` '
'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n'; 'or avoid referencing a default implementation via `platforms: $platformKey: default_package: $defaultImplPluginName`.\n';
} }
} }
@ -1262,19 +1347,22 @@ String? _validatePlugin(Plugin plugin, String platformKey) {
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also /// * The [plugin] (e.g. 'url_launcher') implements itself and then also
/// serves as its own default implementation. /// serves as its own default implementation.
/// * The [plugin] does not provide an implementation. /// * The [plugin] does not provide an implementation.
String? _getImplementedPlugin(Plugin plugin, String platformKey) { String? _getImplementedPlugin(
final bool hasInlineDartImpl = _hasPluginInlineDartImpl(plugin, platformKey); Plugin plugin,
String platformKey, {
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
}) {
if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType)) {
// Only can serve, if the plugin has an inline implementation.
if (hasInlineDartImpl) {
final String? implementsPackage = plugin.implementsPackage; final String? implementsPackage = plugin.implementsPackage;
// Only can serve, if the plugin has a dart inline implementation.
if (implementsPackage != null && implementsPackage.isNotEmpty) { if (implementsPackage != null && implementsPackage.isNotEmpty) {
// The inline plugin implements another package.
return implementsPackage; return implementsPackage;
} }
if (_isEligibleDartSelfImpl(plugin, platformKey)) { if (pluginResolutionType == _PluginResolutionType.nativeOrDart || _isEligibleDartSelfImpl(plugin, platformKey)) {
// The inline Dart plugin implements itself. // The inline plugin implements itself.
return plugin.name; return plugin.name;
} }
} }
@ -1293,16 +1381,20 @@ String? _getImplementedPlugin(Plugin plugin, String platformKey) {
/// * The [plugin] (e.g. 'url_launcher') implements itself and then also /// * The [plugin] (e.g. 'url_launcher') implements itself and then also
/// serves as its own default implementation. /// serves as its own default implementation.
/// * The [plugin] does not reference a default implementation. /// * The [plugin] does not reference a default implementation.
String? _getDefaultImplPlugin(Plugin plugin, String platformKey) { String? _getDefaultImplPlugin(
Plugin plugin,
String platformKey, {
_PluginResolutionType pluginResolutionType = _PluginResolutionType.nativeOrDart,
}) {
final String? defaultImplPluginName = final String? defaultImplPluginName =
plugin.defaultPackagePlatforms[platformKey]; plugin.defaultPackagePlatforms[platformKey];
if (defaultImplPluginName != null) { if (defaultImplPluginName != null) {
return defaultImplPluginName; return defaultImplPluginName;
} }
if (_hasPluginInlineDartImpl(plugin, platformKey) && if (_hasPluginInlineImpl(plugin, platformKey, pluginResolutionType: pluginResolutionType) &&
_isEligibleDartSelfImpl(plugin, platformKey)) { (pluginResolutionType == _PluginResolutionType.nativeOrDart || _isEligibleDartSelfImpl(plugin, platformKey))) {
// The inline Dart plugin serves as its own default implementation. // The inline plugin serves as its own default implementation.
return plugin.name; return plugin.name;
} }
@ -1331,7 +1423,17 @@ bool _isEligibleDartSelfImpl(Plugin plugin, String platformKey) {
return !isDesktop || hasMinVersionForImplementsRequirement; return !isDesktop || hasMinVersionForImplementsRequirement;
} }
/// Determine if the plugin provides an inline dart implementation. /// Determine if the plugin provides an inline implementation.
bool _hasPluginInlineImpl(
Plugin plugin,
String platformKey, {
required _PluginResolutionType pluginResolutionType,
}) {
return pluginResolutionType == _PluginResolutionType.nativeOrDart && plugin.platforms[platformKey] != null ||
pluginResolutionType == _PluginResolutionType.dart && _hasPluginInlineDartImpl(plugin, platformKey);
}
/// Determine if the plugin provides an inline Dart implementation.
bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) { bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
return plugin.pluginDartClassPlatforms[platformKey] != null && return plugin.pluginDartClassPlatforms[platformKey] != null &&
plugin.pluginDartClassPlatforms[platformKey] != 'none'; plugin.pluginDartClassPlatforms[platformKey] != 'none';
@ -1343,9 +1445,10 @@ bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
/// Returns an [error] string, if failing. /// Returns an [error] string, if failing.
(Plugin? resolution, String? error) _resolveImplementationOfPlugin({ (Plugin? resolution, String? error) _resolveImplementationOfPlugin({
required String platformKey, required String platformKey,
required _PluginResolutionType pluginResolutionType,
required String pluginName, required String pluginName,
required List<Plugin> candidates, required List<Plugin> candidates,
String? defaultPackageName, Plugin? defaultPackage,
}) { }) {
// If there's only one candidate, use it. // If there's only one candidate, use it.
if (candidates.length == 1) { if (candidates.length == 1) {
@ -1377,12 +1480,10 @@ bool _hasPluginInlineDartImpl(Plugin plugin, String platformKey) {
} }
} }
// Next, defer to the default implementation if there is one. // Next, defer to the default implementation if there is one.
if (defaultPackageName != null) { if (defaultPackage != null && candidates.contains(defaultPackage)) {
final int defaultIndex = candidates // By definition every candidate has an inline implementation
.indexWhere((Plugin plugin) => plugin.name == defaultPackageName); assert(_hasPluginInlineImpl(defaultPackage, platformKey, pluginResolutionType: pluginResolutionType));
if (defaultIndex != -1) { return (defaultPackage, null);
return (candidates[defaultIndex], null);
}
} }
// Otherwise, require an explicit choice. // Otherwise, require an explicit choice.
if (candidates.length > 1) { if (candidates.length > 1) {
@ -1417,6 +1518,7 @@ Future<void> generateMainDartWithPluginRegistrant(
final List<Plugin> plugins = await findPlugins(rootProject); final List<Plugin> plugins = await findPlugins(rootProject);
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation( final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
plugins, plugins,
selectDartPluginsOnly: true,
); );
final LanguageVersion entrypointVersion = determineLanguageVersion( final LanguageVersion entrypointVersion = determineLanguageVersion(
mainFile, mainFile,

View File

@ -382,7 +382,7 @@ class Plugin {
/// This is a mapping from platform config key to the default package implementation. /// This is a mapping from platform config key to the default package implementation.
final Map<String, String> defaultPackagePlatforms; final Map<String, String> defaultPackagePlatforms;
/// This is a mapping from platform config key to the plugin class for the given platform. /// This is a mapping from platform config key to the Dart plugin class for the given platform.
final Map<String, String> pluginDartClassPlatforms; final Map<String, String> pluginDartClassPlatforms;
/// Whether this plugin is a direct dependency of the app. /// Whether this plugin is a direct dependency of the app.

View File

@ -43,7 +43,8 @@ void main() {
'url_launcher_linux', 'url_launcher_linux',
'url_launcher_macos', 'url_launcher_macos',
}; };
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher_linux', 'url_launcher_linux',
'', '',
@ -76,7 +77,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(2)); expect(resolutions.length, equals(2));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
@ -139,6 +142,7 @@ void main() {
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
], ],
selectDartPluginsOnly: true,
); );
expect(resolutions.length, equals(1)); expect(resolutions.length, equals(1));
@ -155,7 +159,8 @@ void main() {
final Set<String> directDependencies = <String>{ final Set<String> directDependencies = <String>{
'url_launcher_macos', 'url_launcher_macos',
}; };
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher_macos', 'url_launcher_macos',
'', '',
@ -188,7 +193,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(2)); expect(resolutions.length, equals(2));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
@ -210,7 +217,8 @@ void main() {
testWithoutContext('selects inline implementation on mobile', () async { testWithoutContext('selects inline implementation on mobile', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -229,7 +237,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(2)); expect(resolutions.length, equals(2));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
<String, String>{ <String, String>{
@ -252,7 +262,8 @@ void main() {
'missing min Flutter SDK constraint', () async { 'missing min Flutter SDK constraint', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -274,7 +285,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(0)); expect(resolutions.length, equals(0));
}); });
@ -283,7 +296,8 @@ void main() {
'min Flutter SDK constraint < 2.11', () async { 'min Flutter SDK constraint < 2.11', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -305,7 +319,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(0)); expect(resolutions.length, equals(0));
}); });
@ -313,7 +329,8 @@ void main() {
'min Flutter SDK requirement of at least 2.11', () async { 'min Flutter SDK requirement of at least 2.11', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -335,7 +352,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(3)); expect(resolutions.length, equals(3));
expect( expect(
resolutions.map((PluginInterfaceResolution resolution) => resolution.toMap()), resolutions.map((PluginInterfaceResolution resolution) => resolution.toMap()),
@ -362,7 +381,8 @@ void main() {
testWithoutContext('selects default implementation', () async { testWithoutContext('selects default implementation', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -429,7 +449,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(1)); expect(resolutions.length, equals(1));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
<String, String>{ <String, String>{
@ -443,7 +465,8 @@ void main() {
testWithoutContext('selects default implementation if interface is direct dependency', () async { testWithoutContext('selects default implementation if interface is direct dependency', () async {
final Set<String> directDependencies = <String>{'url_launcher'}; final Set<String> directDependencies = <String>{'url_launcher'};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -475,7 +498,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(1)); expect(resolutions.length, equals(1));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
<String, String>{ <String, String>{
@ -486,13 +511,14 @@ void main() {
); );
}); });
testWithoutContext('selects user selected implementation despite default implementation', () async { testWithoutContext('user-selected implementation overrides default implementation', () async {
final Set<String> directDependencies = <String>{ final Set<String> directDependencies = <String>{
'user_selected_url_launcher_implementation', 'user_selected_url_launcher_implementation',
'url_launcher', 'url_launcher',
}; };
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -540,7 +566,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(1)); expect(resolutions.length, equals(1));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
<String, String>{ <String, String>{
@ -551,13 +579,14 @@ void main() {
); );
}); });
testWithoutContext('selects user selected implementation despite inline implementation', () async { testWithoutContext('user-selected implementation overrides inline implementation', () async {
final Set<String> directDependencies = <String>{ final Set<String> directDependencies = <String>{
'user_selected_url_launcher_implementation', 'user_selected_url_launcher_implementation',
'url_launcher', 'url_launcher',
}; };
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -592,7 +621,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(2)); expect(resolutions.length, equals(2));
expect(resolutions[0].toMap(), equals( expect(resolutions[0].toMap(), equals(
<String, String>{ <String, String>{
@ -617,7 +648,8 @@ void main() {
'url_launcher', 'url_launcher',
}; };
expect(() { expect(() {
resolvePlatformImplementation(<Plugin>[ resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -665,7 +697,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
}, },
throwsToolExit( throwsToolExit(
message: 'Please resolve the plugin pubspec errors', message: 'Please resolve the plugin pubspec errors',
@ -687,7 +721,8 @@ void main() {
'url_launcher', 'url_launcher',
}; };
expect(() { expect(() {
resolvePlatformImplementation(<Plugin>[ resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher', 'url_launcher',
'', '',
@ -720,7 +755,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
}, },
throwsToolExit( throwsToolExit(
message: 'Please resolve the plugin pubspec errors', message: 'Please resolve the plugin pubspec errors',
@ -735,13 +772,189 @@ void main() {
'\n\n'); '\n\n');
}); });
testUsingContext('provides warning when a plugin references a default plugin without implementation', () async {
final Set<String> directDependencies = <String>{'url_launcher'};
final List<PluginInterfaceResolution> resolutions =
resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml(
'url_launcher',
'',
YamlMap.wrap(<String, dynamic>{
'platforms': <String, dynamic>{
'linux': <String, dynamic>{
'default_package': 'url_launcher_linux',
},
},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
Plugin.fromYaml(
'url_launcher_linux',
'',
YamlMap.wrap(<String, dynamic>{
'implements': 'url_launcher',
'platforms': <String, dynamic>{},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(0));
expect(
testLogger.warningText,
'Package url_launcher:linux references url_launcher_linux:linux as the default plugin, '
'but it does not provide an inline implementation.\n'
'Ask the maintainers of url_launcher to either avoid referencing a default implementation via `platforms: linux: default_package: url_launcher_linux` '
'or add an inline implementation to url_launcher_linux via `platforms: linux:` `pluginClass` or `dartPluginClass`.\n'
'\n');
});
testUsingContext('avoid warning when a plugin references a default plugin with a native implementation only', () async {
final Set<String> directDependencies = <String>{'url_launcher'};
final List<PluginInterfaceResolution> resolutions =
resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml(
'url_launcher',
'',
YamlMap.wrap(<String, dynamic>{
'platforms': <String, dynamic>{
'linux': <String, dynamic>{
'default_package': 'url_launcher_linux',
},
},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
Plugin.fromYaml(
'url_launcher_linux',
'',
YamlMap.wrap(<String, dynamic>{
'implements': 'url_launcher',
'platforms': <String, dynamic>{
'linux': <String, dynamic>{
'pluginClass': 'UrlLauncherLinux',
},
},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(0));
expect(testLogger.warningText, '');
});
testUsingContext('selects default Dart implementation without warning, while choosing plugin selection for nativeOrDart', () async {
final Set<String> directDependencies = <String>{'url_launcher'};
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml(
'url_launcher',
'',
YamlMap.wrap(<String, dynamic>{
'platforms': <String, dynamic>{
'linux': <String, dynamic>{
'default_package': 'url_launcher_linux',
},
},
}),
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': 'UrlLauncherLinux',
},
},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
],
// Using nativeOrDart plugin selection.
selectDartPluginsOnly: false,
);
expect(resolutions.length, equals(1));
// Test avoiding trigger a warning for default plugins, while Dart and native plugins selection is enabled.
expect(testLogger.warningText, '');
expect(resolutions[0].toMap(), equals(
<String, String>{
'pluginName': 'url_launcher_linux',
'dartClass': 'UrlLauncherLinux',
'platform': 'linux',
})
);
});
testUsingContext('provides warning when a plugin references a default plugin which does not exist', () async {
final Set<String> directDependencies = <String>{'url_launcher'};
final List<PluginInterfaceResolution> resolutions =
resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml(
'url_launcher',
'',
YamlMap.wrap(<String, dynamic>{
'platforms': <String, dynamic>{
'linux': <String, dynamic>{
'default_package': 'url_launcher_linux',
},
},
}),
null,
<String>[],
fileSystem: fs,
appDependencies: directDependencies,
),
],
selectDartPluginsOnly: true,
);
expect(resolutions.length, equals(0));
expect(
testLogger.warningText,
'Package url_launcher:linux references url_launcher_linux:linux as the default plugin, '
'but the package does not exist.\n'
'Ask the maintainers of url_launcher to either avoid referencing a default implementation via `platforms: linux: default_package: url_launcher_linux` '
'or create a plugin named url_launcher_linux.\n'
'\n');
});
testUsingContext('provides error when user selected multiple implementations', () async { testUsingContext('provides error when user selected multiple implementations', () async {
final Set<String> directDependencies = <String>{ final Set<String> directDependencies = <String>{
'url_launcher_linux_1', 'url_launcher_linux_1',
'url_launcher_linux_2', 'url_launcher_linux_2',
}; };
expect(() { expect(() {
resolvePlatformImplementation(<Plugin>[ resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher_linux_1', 'url_launcher_linux_1',
'', '',
@ -774,8 +987,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
}, },
throwsToolExit( throwsToolExit(
message: 'Please resolve the plugin implementation selection errors', message: 'Please resolve the plugin implementation selection errors',
@ -786,8 +1000,8 @@ void main() {
'Plugin url_launcher:linux has conflicting direct dependency implementations:\n' 'Plugin url_launcher:linux has conflicting direct dependency implementations:\n'
' url_launcher_linux_1\n' ' url_launcher_linux_1\n'
' url_launcher_linux_2\n' ' url_launcher_linux_2\n'
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n'
'\n\n' '\n'
); );
}); });
@ -799,7 +1013,8 @@ void main() {
'url_launcher_windows_2', 'url_launcher_windows_2',
}; };
expect(() { expect(() {
resolvePlatformImplementation(<Plugin>[ resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher_linux_1', 'url_launcher_linux_1',
'', '',
@ -864,7 +1079,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
}, },
throwsToolExit( throwsToolExit(
message: 'Please resolve the plugin implementation selection errors', message: 'Please resolve the plugin implementation selection errors',
@ -875,20 +1092,21 @@ void main() {
'Plugin url_launcher:linux has conflicting direct dependency implementations:\n' 'Plugin url_launcher:linux has conflicting direct dependency implementations:\n'
' url_launcher_linux_1\n' ' url_launcher_linux_1\n'
' url_launcher_linux_2\n' ' url_launcher_linux_2\n'
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n'
'\n\n' '\n'
'Plugin url_launcher:windows has conflicting direct dependency implementations:\n' 'Plugin url_launcher:windows has conflicting direct dependency implementations:\n'
' url_launcher_windows_1\n' ' url_launcher_windows_1\n'
' url_launcher_windows_2\n' ' url_launcher_windows_2\n'
'To fix this issue, remove all but one of these dependencies from pubspec.yaml.' 'To fix this issue, remove all but one of these dependencies from pubspec.yaml.\n'
'\n\n' '\n'
); );
}); });
testUsingContext('provides error when user needs to select among multiple implementations', () async { testUsingContext('provides error when user needs to select among multiple implementations', () async {
final Set<String> directDependencies = <String>{}; final Set<String> directDependencies = <String>{};
expect(() { expect(() {
resolvePlatformImplementation(<Plugin>[ resolvePlatformImplementation(
<Plugin>[
Plugin.fromYaml( Plugin.fromYaml(
'url_launcher_linux_1', 'url_launcher_linux_1',
'', '',
@ -921,7 +1139,9 @@ void main() {
fileSystem: fs, fileSystem: fs,
appDependencies: directDependencies, appDependencies: directDependencies,
), ),
]); ],
selectDartPluginsOnly: true,
);
}, },
throwsToolExit( throwsToolExit(
message: 'Please resolve the plugin implementation selection errors', message: 'Please resolve the plugin implementation selection errors',
@ -932,8 +1152,8 @@ void main() {
'Plugin url_launcher:linux has multiple possible implementations:\n' 'Plugin url_launcher:linux has multiple possible implementations:\n'
' url_launcher_linux_1\n' ' url_launcher_linux_1\n'
' url_launcher_linux_2\n' ' url_launcher_linux_2\n'
'To fix this issue, add one of these dependencies to pubspec.yaml.' 'To fix this issue, add one of these dependencies to pubspec.yaml.\n'
'\n\n' '\n',
); );
}); });
}); });

View File

@ -9,6 +9,7 @@ import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart'; import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/error_handling_io.dart'; import 'package:flutter_tools/src/base/error_handling_io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/base/time.dart';
@ -892,6 +893,7 @@ dependencies:
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
group('Build time plugin injection', () {
testUsingContext("Registrant for web doesn't escape slashes in imports", () async { testUsingContext("Registrant for web doesn't escape slashes in imports", () async {
flutterProject.isModule = true; flutterProject.isModule = true;
final Directory webPluginWithNestedFile = final Directory webPluginWithNestedFile =
@ -930,6 +932,61 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri}
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('user-selected implementation overrides inline implementation on web', () async {
final List<Directory> directories = createFakePlugins(fs, <String>[
'user_selected_url_launcher_implementation',
'url_launcher',
]);
// Add inline web implementation to `user_selected_url_launcher_implementation`
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
implements: url_launcher
platforms:
web:
pluginClass: UserSelectedUrlLauncherWeb
fileName: src/web_plugin.dart
''');
// Add inline native implementation to `url_launcher`
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
web:
pluginClass: InlineUrlLauncherWeb
fileName: src/web_plugin.dart
''');
final FlutterManifest manifest = FlutterManifest.createFromString('''
name: test
version: 1.0.0
dependencies:
url_launcher: ^1.0.0
user_selected_url_launcher_implementation: ^1.0.0
''', logger: BufferLogger.test())!;
flutterProject.manifest = manifest;
flutterProject.isModule = true;
final Directory destination = flutterProject.directory.childDirectory('lib');
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: destination);
final File registrant = flutterProject.directory
.childDirectory('lib')
.childFile('web_plugin_registrant.dart');
expect(registrant.existsSync(), isTrue);
expect(registrant.readAsStringSync(), contains("import 'package:user_selected_url_launcher_implementation/src/web_plugin.dart';"));
expect(registrant.readAsStringSync(), isNot(contains("import 'package:url_launcher/src/web_plugin.dart';")));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
testUsingContext('Injecting creates generated Android registrant, but does not include Dart-only plugins', () async { testUsingContext('Injecting creates generated Android registrant, but does not include Dart-only plugins', () async {
// Create a plugin without a pluginClass. // Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs); final Directory pluginDirectory = createFakePlugin(fs);
@ -1075,6 +1132,112 @@ flutter:
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('user-selected implementation overrides inline implementation', () async {
final List<Directory> directories = createFakePlugins(fs, <String>[
'user_selected_url_launcher_implementation',
'url_launcher',
]);
// Add inline native implementation to `user_selected_url_launcher_implementation`
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
implements: url_launcher
platforms:
linux:
pluginClass: UserSelectedUrlLauncherLinux
''');
// Add inline native implementation to `url_launcher`
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
linux:
pluginClass: InlineUrlLauncherLinux
''');
final FlutterManifest manifest = FlutterManifest.createFromString('''
name: test
version: 1.0.0
dependencies:
url_launcher: ^1.0.0
user_selected_url_launcher_implementation: ^1.0.0
''', logger: BufferLogger.test())!;
flutterProject.manifest = manifest;
await injectPlugins(flutterProject, linuxPlatform: true);
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
expect(registrantImpl.existsSync(), isTrue);
expect(registrantImpl.readAsStringSync(), contains('user_selected_url_launcher_linux_register_with_registrar'));
expect(registrantImpl.readAsStringSync(), isNot(contains('inline_url_launcher_linux_register_with_registrar')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('user-selected implementation overrides default implementation', () async {
final List<Directory> directories = createFakePlugins(fs, <String>[
'user_selected_url_launcher_implementation',
'url_launcher',
'url_launcher_linux',
]);
// Add inline native implementation to `user_selected_url_launcher_implementation`
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
implements: url_launcher
platforms:
linux:
pluginClass: UserSelectedUrlLauncherLinux
''');
// Add default native implementation to `url_launcher`
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
linux:
default_package: url_launcher_linux
''');
// Add inline native implementation to `url_launcher_linux`
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
flutter:
plugin:
platforms:
linux:
pluginClass: InlineUrlLauncherLinux
''');
final FlutterManifest manifest = FlutterManifest.createFromString('''
name: test
version: 1.0.0
dependencies:
url_launcher: ^1.0.0
user_selected_url_launcher_implementation: ^1.0.0
''', logger: BufferLogger.test())!;
flutterProject.manifest = manifest;
await injectPlugins(flutterProject, linuxPlatform: true);
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
expect(registrantImpl.existsSync(), isTrue);
expect(registrantImpl.readAsStringSync(), contains('user_selected_url_launcher_linux_register_with_registrar'));
expect(registrantImpl.readAsStringSync(), isNot(contains('inline_url_launcher_linux_register_with_registrar')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async { testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async {
// Create a plugin without a pluginClass. // Create a plugin without a pluginClass.
final Directory pluginDirectory = createFakePlugin(fs); final Directory pluginDirectory = createFakePlugin(fs);
@ -1662,6 +1825,7 @@ The Flutter Preview device does not support the following plugins from your pubs
expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull); expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull);
}); });
}); });
testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async { testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async {
final Platform platform = FakePlatform(operatingSystem: 'windows'); final Platform platform = FakePlatform(operatingSystem: 'windows');
final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]'); final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]');