From 57fcee28c72a7bce60ddfcebd4ca02a8f6d6592f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 26 May 2021 16:20:21 -0700 Subject: [PATCH] Allow platform variants for Windows plugins (#82816) Windows plugins are designed to share implementations between Win32 and UWP, but not all plugins will support both. This adds a new 'supportedVariants' key to Windows plugins that allows specifying 'win32' and/or 'uwp' (and potentially others in the future in case that becomes necessary). Plugins without any supported variants will be assumed to be Win32 for backward compatibility. This will allow compiling Windows projects that use Win32-only Windows plugins (which is currently all of them) in UWP mode. The plugins will of course throw missing implementation exceptions at runtime, but tehy won't prevent being able to build as they currently do. Fixes https://github.com/flutter/flutter/issues/82815 --- .../lib/src/flutter_plugins.dart | 57 +++++++++------ .../lib/src/platform_plugins.dart | 46 +++++++++++- .../general.shard/plugin_parsing_test.dart | 73 +++++++++++++++++++ .../general.shard/windows/plugins_test.dart | 49 ++++++++++++- 4 files changed, 199 insertions(+), 26 deletions(-) diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 9296293ff8..9e4f42f17f 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 dff6242a62..cd4f11cdbb 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 490dc9aae8..b23b3fd029 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 1eb14b84b4..0b3ca10b90 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);