diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 9296293ff89..9e4f42f17f7 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -101,23 +101,23 @@ const String _kFlutterPluginsNameKey = 'name'; const String _kFlutterPluginsPathKey = 'path'; const String _kFlutterPluginsDependenciesKey = 'dependencies'; - /// Filters [plugins] to those supported by [platformKey]. - List> _filterPluginsByPlatform(Listplugins, String platformKey) { - final Iterable platformPlugins = plugins.where((Plugin p) { - return p.platforms.containsKey(platformKey); - }); +/// Filters [plugins] to those supported by [platformKey]. +List> _filterPluginsByPlatform(List plugins, String platformKey) { + final Iterable platformPlugins = plugins.where((Plugin p) { + return p.platforms.containsKey(platformKey); + }); - final Set pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet(); - final List> list = >[]; - for (final Plugin plugin in platformPlugins) { - list.add({ - _kFlutterPluginsNameKey: plugin.name, - _kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path), - _kFlutterPluginsDependenciesKey: [...plugin.dependencies.where(pluginNames.contains)], - }); - } - return list; + final Set pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet(); + final List> pluginInfo = >[]; + for (final Plugin plugin in platformPlugins) { + pluginInfo.add({ + _kFlutterPluginsNameKey: plugin.name, + _kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path), + _kFlutterPluginsDependenciesKey: [...plugin.dependencies.where(pluginNames.contains)], + }); } + return pluginInfo; +} /// Writes the .flutter-plugins-dependencies file based on the list of plugins. /// If there aren't any plugins, then the files aren't written to disk. The resulting @@ -815,28 +815,43 @@ List _filterNativePlugins(List plugins, String platformKey) { }).toList(); } +/// Returns only the plugins with the given platform variant. +List _filterPluginsByVariant(List plugins, String platformKey, PluginPlatformVariant variant) { + return plugins.where((Plugin element) { + final PluginPlatform platformPlugin = element.platforms[platformKey]; + if (platformPlugin == null) { + return false; + } + assert(variant == null || platformPlugin is VariantPlatformPlugin); + return variant == null || + (platformPlugin as VariantPlatformPlugin).supportedVariants.contains(variant); + }).toList(); +} + @visibleForTesting Future writeWindowsPluginFiles(FlutterProject project, List plugins, TemplateRenderer templateRenderer) async { final List nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey); - final List> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey); + final List win32Plugins = _filterPluginsByVariant(nativePlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.win32); + final List> pluginInfo = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey); final Map context = { 'os': 'windows', - 'plugins': windowsPlugins, + 'plugins': pluginInfo, 'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windows), }; await _writeCppPluginRegistrant(project.windows.managedDirectory, context, templateRenderer); await _writePluginCmakefile(project.windows.generatedPluginCmakeFile, context, templateRenderer); } -/// The tooling currently treats UWP and win32 as identical for the -/// purposes of tooling support and initial UWP bootstrap. +/// The tooling currently treats UWP and win32 as identical, other than variant +/// filtering, for the purposes of tooling support and initial UWP bootstrap. @visibleForTesting Future writeWindowsUwpPluginFiles(FlutterProject project, List plugins, TemplateRenderer templateRenderer) async { final List nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey); - final List> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey); + final List uwpPlugins = _filterPluginsByVariant(nativePlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.winuwp); + final List> pluginInfo = _extractPlatformMaps(uwpPlugins, WindowsPlugin.kConfigKey); final Map context = { 'os': 'windows', - 'plugins': windowsPlugins, + 'plugins': pluginInfo, 'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windowsUwp), }; await _writeCppPluginRegistrant(project.windowsUwp.managedDirectory, context, templateRenderer); diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index dff6242a620..cd4f11cdbb3 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart @@ -16,6 +16,18 @@ const String kDartPluginClass = 'dartPluginClass'; // Constant for 'defaultPackage' key in plugin maps. const String kDefaultPackage = 'default_package'; +/// Constant for 'supportedVariants' key in plugin maps. +const String kSupportedVariants = 'supportedVariants'; + +/// Platform variants that a Windows plugin can support. +enum PluginPlatformVariant { + /// Win32 variant of Windows. + win32, + + // UWP variant of Windows. + winuwp, +} + /// Marker interface for all platform specific plugin config implementations. abstract class PluginPlatform { const PluginPlatform(); @@ -23,6 +35,12 @@ abstract class PluginPlatform { Map toMap(); } +/// A plugin that has platform variants. +abstract class VariantPlatformPlugin { + /// The platform variants supported by the plugin. + Set get supportedVariants; +} + abstract class NativeOrDartPlugin { /// Determines whether the plugin has a native implementation or if it's a /// Dart-only plugin. @@ -259,12 +277,13 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { /// /// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required. /// [pluginClass] will be the entry point to the plugin's native code. -class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{ +class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, VariantPlatformPlugin { const WindowsPlugin({ required this.name, this.pluginClass, this.dartPluginClass, this.defaultPackage, + this.variants = const {}, }) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null); factory WindowsPlugin.fromYaml(String name, YamlMap yaml) { @@ -274,11 +293,31 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{ if (pluginClass == 'none') { pluginClass = null; } + final Set variants = {}; + final YamlList? variantList = yaml[kSupportedVariants] as YamlList?; + if (variantList == null) { + // If no variant list is provided assume Win32 for backward compatibility. + variants.add(PluginPlatformVariant.win32); + } else { + const Map variantByName = { + 'win32': PluginPlatformVariant.win32, + 'uwp': PluginPlatformVariant.winuwp, + }; + for (final String variantName in variantList.cast()) { + final PluginPlatformVariant? variant = variantByName[variantName]; + if (variant != null) { + variants.add(variant); + } + // Ignore unrecognized variants to make adding new variants in the + // future non-breaking. + } + } return WindowsPlugin( name: name, pluginClass: pluginClass, dartPluginClass: yaml[kDartPluginClass] as String?, defaultPackage: yaml[kDefaultPackage] as String?, + variants: variants, ); } @@ -286,6 +325,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{ if (yaml == null) { return false; } + return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String || yaml[kDefaultPackage] is String; @@ -297,6 +337,10 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{ final String? pluginClass; final String? dartPluginClass; final String? defaultPackage; + final Set variants; + + @override + Set get supportedVariants => variants; @override bool isNative() => pluginClass != null; diff --git a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart index 490dc9aae8a..b23b3fd029f 100644 --- a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart +++ b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart @@ -253,6 +253,79 @@ void main() { }); }); + testWithoutContext('Windows allows supported mode lists', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + const String pluginYamlRaw = + 'platforms:\n' + ' windows:\n' + ' pluginClass: WinSamplePlugin\n' + ' supportedVariants:\n' + ' - win32\n' + ' - uwp\n'; + + final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap; + final Plugin plugin = Plugin.fromYaml( + _kTestPluginName, + _kTestPluginPath, + pluginYaml, + const [], + fileSystem: fileSystem, + ); + + final WindowsPlugin windowsPlugin = plugin.platforms[WindowsPlugin.kConfigKey]! as WindowsPlugin; + expect(windowsPlugin.supportedVariants, [ + PluginPlatformVariant.win32, + PluginPlatformVariant.winuwp, + ]); + }); + + testWithoutContext('Windows assumes win32 when no variants are given', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + const String pluginYamlRaw = + 'platforms:\n' + ' windows:\n' + ' pluginClass: WinSamplePlugin\n'; + + final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap; + final Plugin plugin = Plugin.fromYaml( + _kTestPluginName, + _kTestPluginPath, + pluginYaml, + const [], + fileSystem: fileSystem, + ); + + final WindowsPlugin windowsPlugin = plugin.platforms[WindowsPlugin.kConfigKey]! as WindowsPlugin; + expect(windowsPlugin.supportedVariants, [ + PluginPlatformVariant.win32, + ]); + }); + + testWithoutContext('Windows ignores unknown variants', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + const String pluginYamlRaw = + 'platforms:\n' + ' windows:\n' + ' pluginClass: WinSamplePlugin\n' + ' supportedVariants:\n' + ' - not_yet_invented_variant\n' + ' - uwp\n'; + + final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap; + final Plugin plugin = Plugin.fromYaml( + _kTestPluginName, + _kTestPluginPath, + pluginYaml, + const [], + fileSystem: fileSystem, + ); + + final WindowsPlugin windowsPlugin = plugin.platforms[WindowsPlugin.kConfigKey]! as WindowsPlugin; + expect(windowsPlugin.supportedVariants, { + PluginPlatformVariant.winuwp, + }); + }); + testWithoutContext('Plugin parsing throws a fatal error on an empty plugin', () { final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final YamlMap? pluginYaml = loadYaml('') as YamlMap?; diff --git a/packages/flutter_tools/test/general.shard/windows/plugins_test.dart b/packages/flutter_tools/test/general.shard/windows/plugins_test.dart index 1eb14b84b46..0b3ca10b90a 100644 --- a/packages/flutter_tools/test/general.shard/windows/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/plugins_test.dart @@ -35,7 +35,7 @@ const String kPluginDependencies = r''' void main() { - testWithoutContext('injects Win32 plugins', () async { + testWithoutContext('Win32 injects Win32 plugins', () async { final FileSystem fileSystem = MemoryFileSystem.test(); setUpProject(fileSystem); final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory); @@ -46,7 +46,12 @@ void main() { path: 'foo', defaultPackagePlatforms: const {}, pluginDartClassPlatforms: const {}, - platforms: const {WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: 'Foo')}, + platforms: const { + WindowsPlugin.kConfigKey: WindowsPlugin( + name: 'test', + pluginClass: 'Foo', + variants: {PluginPlatformVariant.win32}, + )}, dependencies: [], isDirectDependency: true, ), @@ -61,7 +66,7 @@ void main() { ); }); - testWithoutContext('UWP injects Win32 plugins', () async { + testWithoutContext('UWP injects plugins marked as UWP-compatible', () async { final FileSystem fileSystem = MemoryFileSystem.test(); setUpProject(fileSystem); final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory); @@ -72,7 +77,12 @@ void main() { path: 'foo', defaultPackagePlatforms: const {}, pluginDartClassPlatforms: const {}, - platforms: const {WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: 'Foo')}, + platforms: const { + WindowsPlugin.kConfigKey: WindowsPlugin( + name: 'test', + pluginClass: 'Foo', + variants: {PluginPlatformVariant.winuwp}, + )}, dependencies: [], isDirectDependency: true, ), @@ -87,6 +97,37 @@ void main() { ); }); + testWithoutContext('UWP does not inject Win32-only plugins', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + setUpProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory); + + await writeWindowsUwpPluginFiles(flutterProject, [ + Plugin( + name: 'test', + path: 'foo', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + WindowsPlugin.kConfigKey: WindowsPlugin( + name: 'test', + pluginClass: 'Foo', + variants: {PluginPlatformVariant.win32}, + )}, + dependencies: [], + isDirectDependency: true, + ), + ], renderer); + + final Directory managed = flutterProject.windowsUwp.managedDirectory; + expect(flutterProject.windowsUwp.generatedPluginCmakeFile, exists); + expect(managed.childFile('generated_plugin_registrant.h'), exists); + expect( + managed.childFile('generated_plugin_registrant.cc').readAsStringSync(), + isNot(contains('#include ')), + ); + }); + testWithoutContext('Symlink injection treats UWP as Win32', () { final FileSystem fileSystem = MemoryFileSystem.test(); setUpProject(fileSystem);