From eaaacdcba9867977eadc801058c619115752e240 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 9 Jan 2023 10:33:03 -0800 Subject: [PATCH] Allow iOS and macOS plugins to share darwin directory (#115337) --- .ci.yaml | 19 +++++ TESTOWNERS | 1 + dev/devicelab/bin/tasks/plugin_test_ios.dart | 5 +- .../bin/tasks/plugin_test_macos.dart | 18 ++++ dev/devicelab/lib/tasks/plugin_tests.dart | 82 +++++++++++++++++++ packages/flutter_tools/bin/podhelper.rb | 9 +- .../lib/src/flutter_plugins.dart | 3 + .../lib/src/platform_plugins.dart | 37 ++++++++- .../general.shard/plugin_parsing_test.dart | 7 ++ .../test/general.shard/plugins_test.dart | 62 ++++++++++---- 10 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 dev/devicelab/bin/tasks/plugin_test_macos.dart diff --git a/.ci.yaml b/.ci.yaml index 8b442679af95..f1764a4e624c 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -2944,6 +2944,25 @@ targets: - bin/** - .ci.yaml + - name: Mac plugin_test_macos + bringup: true + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "xcode", "version": "14a5294e"}, + {"dependency": "gems", "version": "v3.3.14"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - name: Mac_x64 tool_host_cross_arch_tests recipe: flutter/flutter_drone timeout: 60 diff --git a/TESTOWNERS b/TESTOWNERS index 46261a846507..b6ca4612df99 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -248,6 +248,7 @@ /dev/devicelab/bin/tasks/platform_view_win_desktop__start_up.dart @yaakovschectman @flutter/desktop /dev/devicelab/bin/tasks/plugin_lint_mac.dart @stuartmorgan @flutter/plugin /dev/devicelab/bin/tasks/plugin_test_ios.dart @jmagman @flutter/ios +/dev/devicelab/bin/tasks/plugin_test_macos.dart @jmagman @flutter/desktop /dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin /dev/devicelab/bin/tasks/run_debug_test_android.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/run_debug_test_macos.dart @cbracken @flutter/tool diff --git a/dev/devicelab/bin/tasks/plugin_test_ios.dart b/dev/devicelab/bin/tasks/plugin_test_ios.dart index c8fd56728770..4014fa18bf0f 100644 --- a/dev/devicelab/bin/tasks/plugin_test_ios.dart +++ b/dev/devicelab/bin/tasks/plugin_test_ios.dart @@ -9,12 +9,11 @@ Future main() async { await task(combine([ PluginTest('ios', ['-i', 'objc', '--platforms=ios']).call, PluginTest('ios', ['-i', 'swift', '--platforms=ios']).call, - PluginTest('macos', ['--platforms=macos']).call, // Test that Dart-only plugins are supported. PluginTest('ios', ['--platforms=ios'], dartOnlyPlugin: true).call, - PluginTest('macos', ['--platforms=macos'], dartOnlyPlugin: true).call, + // Test that shared darwin directories are supported. + PluginTest('ios', ['--platforms=ios,macos'], sharedDarwinSource: true).call, // Test that FFI plugins are supported. PluginTest('ios', ['--platforms=ios'], template: 'plugin_ffi').call, - PluginTest('macos', ['--platforms=macos'], template: 'plugin_ffi').call, ])); } diff --git a/dev/devicelab/bin/tasks/plugin_test_macos.dart b/dev/devicelab/bin/tasks/plugin_test_macos.dart new file mode 100644 index 000000000000..d8e8f1990c8a --- /dev/null +++ b/dev/devicelab/bin/tasks/plugin_test_macos.dart @@ -0,0 +1,18 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/plugin_tests.dart'; + +Future main() async { + await task(combine([ + PluginTest('macos', ['--platforms=macos']).call, + // Test that Dart-only plugins are supported. + PluginTest('macos', ['--platforms=macos'], dartOnlyPlugin: true).call, + // Test that shared darwin directories are supported. + PluginTest('macos', ['--platforms=ios,macos'], sharedDarwinSource: true).call, + // Test that FFI plugins are supported. + PluginTest('macos', ['--platforms=macos'], template: 'plugin_ffi').call, + ])); +} diff --git a/dev/devicelab/lib/tasks/plugin_tests.dart b/dev/devicelab/lib/tasks/plugin_tests.dart index b805b577572d..d95484bebbca 100644 --- a/dev/devicelab/lib/tasks/plugin_tests.dart +++ b/dev/devicelab/lib/tasks/plugin_tests.dart @@ -33,6 +33,7 @@ class PluginTest { this.pluginCreateEnvironment, this.appCreateEnvironment, this.dartOnlyPlugin = false, + this.sharedDarwinSource = false, this.template = 'plugin', }); @@ -41,6 +42,7 @@ class PluginTest { final Map? pluginCreateEnvironment; final Map? appCreateEnvironment; final bool dartOnlyPlugin; + final bool sharedDarwinSource; final String template; Future call() async { @@ -58,6 +60,9 @@ class PluginTest { if (dartOnlyPlugin) { await plugin.convertDefaultPluginToDartPlugin(); } + if (sharedDarwinSource) { + await plugin.convertDefaultPluginToSharedDarwinPlugin(); + } section('Test plugin'); if (runFlutterTest) { await plugin.runFlutterTest(); @@ -159,6 +164,83 @@ class $dartPluginClass { } } + /// Converts an iOS/macOS plugin created from the standard template to a shared + /// darwin directory plugin. + Future convertDefaultPluginToSharedDarwinPlugin() async { + // Convert the metadata. + final File pubspec = pubspecFile; + String pubspecContent = await pubspec.readAsString(); + const String originalIOSKey = '\n ios:\n'; + const String originalMacOSKey = '\n macos:\n'; + if (!pubspecContent.contains(originalIOSKey) || !pubspecContent.contains(originalMacOSKey)) { + print(pubspecContent); + throw TaskResult.failure('Missing expected darwin platform plugin keys'); + } + pubspecContent = pubspecContent.replaceAll( + originalIOSKey, + '$originalIOSKey sharedDarwinSource: true\n' + ); + pubspecContent = pubspecContent.replaceAll( + originalMacOSKey, + '$originalMacOSKey sharedDarwinSource: true\n' + ); + await pubspec.writeAsString(pubspecContent, flush: true); + + // Copy ios to darwin, and delete macos. + final Directory iosDir = Directory(path.join(rootPath, 'ios')); + final Directory darwinDir = Directory(path.join(rootPath, 'darwin')); + recursiveCopy(iosDir, darwinDir); + + await iosDir.delete(recursive: true); + await Directory(path.join(rootPath, 'macos')).delete(recursive: true); + + final File podspec = File(path.join(darwinDir.path, '$name.podspec')); + String podspecContent = await podspec.readAsString(); + if (!podspecContent.contains('s.platform =')) { + print(podspecContent); + throw TaskResult.failure('Missing expected podspec platform'); + } + + // Remove "s.platform = :ios" to work on all platforms, including macOS. + podspecContent = podspecContent.replaceFirst(RegExp(r'.*s\.platform.*'), ''); + podspecContent = podspecContent.replaceFirst("s.dependency 'Flutter'", "s.ios.dependency 'Flutter'\ns.osx.dependency 'FlutterMacOS'"); + + await podspec.writeAsString(podspecContent, flush: true); + + // Make PlugintestPlugin.swift compile on iOS and macOS with target conditionals. + final String pluginClass = '${name[0].toUpperCase()}${name.substring(1)}Plugin'; + print('pluginClass: $pluginClass'); + final File pluginRegister = File(path.join(darwinDir.path, 'Classes', '$pluginClass.swift')); + final String pluginRegisterContent = ''' +#if os(macOS) +import FlutterMacOS +#elseif os(iOS) +import Flutter +#endif + +public class $pluginClass: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { +#if os(macOS) + let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger) +#elseif os(iOS) + let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger()) +#endif + let instance = $pluginClass() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { +#if os(macOS) + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) +#elseif os(iOS) + result("iOS " + UIDevice.current.systemVersion) +#endif + } +} +'''; + await pluginRegister.writeAsString(pluginRegisterContent, flush: true); + } + Future runFlutterTest() async { await inDirectory(Directory(rootPath), () async { await flutter('test'); diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb index c81ac37a1be4..a24a1f21ec0a 100644 --- a/packages/flutter_tools/bin/podhelper.rb +++ b/packages/flutter_tools/bin/podhelper.rb @@ -266,6 +266,11 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl plugin_name = plugin_hash['name'] plugin_path = plugin_hash['path'] has_native_build = plugin_hash.fetch('native_build', true) + + # iOS and macOS code can be shared in "darwin" directory, otherwise + # respectively in "ios" or "macos" directories. + shared_darwin_source = plugin_hash.fetch('shared_darwin_source', false) + platform_directory = shared_darwin_source ? 'darwin' : platform next unless plugin_name && plugin_path && has_native_build symlink = File.join(symlink_plugins_dir, plugin_name) File.symlink(plugin_path, symlink) @@ -273,7 +278,7 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl # Keep pod path relative so it can be checked into Podfile.lock. relative = flutter_relative_path_from_podfile(symlink) - pod plugin_name, path: File.join(relative, platform) + pod plugin_name, path: File.join(relative, platform_directory) end end @@ -288,7 +293,7 @@ def flutter_parse_plugins_file(file, platform) # dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3 return [] unless dependencies_hash.has_key?('plugins') - return [] unless dependencies_hash['plugins'].has_key?('ios') + return [] unless dependencies_hash['plugins'].has_key?(platform) dependencies_hash['plugins'][platform] || [] end diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index e350ab3cb9be..5ff86d20f887 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -104,6 +104,7 @@ const String _kFlutterPluginsNameKey = 'name'; const String _kFlutterPluginsPathKey = 'path'; const String _kFlutterPluginsDependenciesKey = 'dependencies'; const String _kFlutterPluginsHasNativeBuildKey = 'native_build'; +const String _kFlutterPluginsSharedDarwinSource = 'shared_darwin_source'; /// Filters [plugins] to those supported by [platformKey]. List> _filterPluginsByPlatform(List plugins, String platformKey) { @@ -119,6 +120,8 @@ List> _filterPluginsByPlatform(List plugins, String pluginInfo.add({ _kFlutterPluginsNameKey: plugin.name, _kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path), + if (platformPlugin is DarwinPlugin && (platformPlugin as DarwinPlugin).sharedDarwinSource) + _kFlutterPluginsSharedDarwinSource: (platformPlugin as DarwinPlugin).sharedDarwinSource, if (platformPlugin is NativeOrDartPlugin) _kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).hasMethodChannel() || (platformPlugin as NativeOrDartPlugin).hasFfi(), _kFlutterPluginsDependenciesKey: [...plugin.dependencies.where(pluginNames.contains)], diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index 129273b10279..ce45bdce8913 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart @@ -19,6 +19,10 @@ const String kFfiPlugin = 'ffiPlugin'; // Constant for 'defaultPackage' key in plugin maps. const String kDefaultPackage = 'default_package'; +/// Constant for 'sharedDarwinSource' key in plugin maps. +/// Can be set for iOS and macOS plugins. +const String kSharedDarwinSource = 'sharedDarwinSource'; + /// Constant for 'supportedVariants' key in plugin maps. const String kSupportedVariants = 'supportedVariants'; @@ -52,6 +56,11 @@ abstract class NativeOrDartPlugin { bool hasMethodChannel(); } +abstract class DarwinPlugin { + /// Indicates the iOS and macOS native code is shareable the subdirectory "darwin", + bool get sharedDarwinSource; +} + /// Contains parameters to template an Android plugin. /// /// The [name] of the plugin is required. Additionally, either: @@ -227,7 +236,7 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin { /// - the [dartPluginClass] that will be the entry point for the plugin's /// Dart code /// is required. -class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { +class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin, DarwinPlugin { const IOSPlugin({ required this.name, required this.classPrefix, @@ -235,7 +244,9 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { this.dartPluginClass, bool? ffiPlugin, this.defaultPackage, - }) : ffiPlugin = ffiPlugin ?? false; + bool? sharedDarwinSource, + }) : ffiPlugin = ffiPlugin ?? false, + sharedDarwinSource = sharedDarwinSource ?? false; factory IOSPlugin.fromYaml(String name, YamlMap yaml) { assert(validate(yaml)); // TODO(zanderso): https://github.com/flutter/flutter/issues/67241 @@ -246,6 +257,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { dartPluginClass: yaml[kDartPluginClass] as String?, ffiPlugin: yaml[kFfiPlugin] as bool?, defaultPackage: yaml[kDefaultPackage] as String?, + sharedDarwinSource: yaml[kSharedDarwinSource] as bool?, ); } @@ -256,6 +268,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String || yaml[kFfiPlugin] == true || + yaml[kSharedDarwinSource] == true || yaml[kDefaultPackage] is String; } @@ -271,6 +284,11 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { final bool ffiPlugin; final String? defaultPackage; + /// Indicates the iOS native code is shareable with macOS in + /// the subdirectory "darwin", otherwise in the subdirectory "ios". + @override + final bool sharedDarwinSource; + @override bool hasMethodChannel() => pluginClass != null; @@ -288,6 +306,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { if (pluginClass != null) 'class': pluginClass, if (dartPluginClass != null) kDartPluginClass : dartPluginClass, if (ffiPlugin) kFfiPlugin: true, + if (sharedDarwinSource) kSharedDarwinSource: true, if (defaultPackage != null) kDefaultPackage : defaultPackage, }; } @@ -298,14 +317,16 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin { /// The [name] of the plugin is required. Either [dartPluginClass] or /// [pluginClass] or [ffiPlugin] are required. /// [pluginClass] will be the entry point to the plugin's native code. -class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { +class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin, DarwinPlugin { const MacOSPlugin({ required this.name, this.pluginClass, this.dartPluginClass, bool? ffiPlugin, this.defaultPackage, - }) : ffiPlugin = ffiPlugin ?? false; + bool? sharedDarwinSource, + }) : ffiPlugin = ffiPlugin ?? false, + sharedDarwinSource = sharedDarwinSource ?? false; factory MacOSPlugin.fromYaml(String name, YamlMap yaml) { assert(validate(yaml)); @@ -320,6 +341,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { dartPluginClass: yaml[kDartPluginClass] as String?, ffiPlugin: yaml[kFfiPlugin] as bool?, defaultPackage: yaml[kDefaultPackage] as String?, + sharedDarwinSource: yaml[kSharedDarwinSource] as bool?, ); } @@ -330,6 +352,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String || yaml[kFfiPlugin] == true || + yaml[kSharedDarwinSource] == true || yaml[kDefaultPackage] is String; } @@ -341,6 +364,11 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { final bool ffiPlugin; final String? defaultPackage; + /// Indicates the macOS native code is shareable with iOS in + /// the subdirectory "darwin", otherwise in the subdirectory "macos". + @override + final bool sharedDarwinSource; + @override bool hasMethodChannel() => pluginClass != null; @@ -357,6 +385,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin { if (pluginClass != null) 'class': pluginClass, if (dartPluginClass != null) kDartPluginClass: dartPluginClass, if (ffiPlugin) kFfiPlugin: true, + if (sharedDarwinSource) kSharedDarwinSource: true, if (defaultPackage != null) kDefaultPackage: defaultPackage, }; } 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 0f8283855e42..2c534642f7b1 100644 --- a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart +++ b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart @@ -35,6 +35,7 @@ void main() { expect(iosPlugin.pluginClass, 'SamplePlugin'); expect(iosPlugin.classPrefix, 'FLT'); + expect(iosPlugin.sharedDarwinSource, isFalse); expect(androidPlugin.pluginClass, 'SamplePlugin'); expect(androidPlugin.package, 'com.flutter.dev'); }); @@ -47,10 +48,12 @@ void main() { ' pluginClass: ASamplePlugin\n' ' ios:\n' ' pluginClass: ISamplePlugin\n' + ' sharedDarwinSource: true\n' ' linux:\n' ' pluginClass: LSamplePlugin\n' ' macos:\n' ' pluginClass: MSamplePlugin\n' + ' sharedDarwinSource: true\n' ' web:\n' ' pluginClass: WebSamplePlugin\n' ' fileName: web_plugin.dart\n' @@ -76,10 +79,12 @@ void main() { expect(iosPlugin.pluginClass, 'ISamplePlugin'); expect(iosPlugin.classPrefix, ''); + expect(iosPlugin.sharedDarwinSource, isTrue); expect(androidPlugin.pluginClass, 'ASamplePlugin'); expect(androidPlugin.package, 'com.flutter.dev'); expect(linuxPlugin.pluginClass, 'LSamplePlugin'); expect(macOSPlugin.pluginClass, 'MSamplePlugin'); + expect(macOSPlugin.sharedDarwinSource, isTrue); expect(webPlugin.pluginClass, 'WebSamplePlugin'); expect(webPlugin.fileName, 'web_plugin.dart'); expect(windowsPlugin.pluginClass, 'WinSamplePlugin'); @@ -124,10 +129,12 @@ void main() { expect(iosPlugin.pluginClass, 'ISamplePlugin'); expect(iosPlugin.classPrefix, ''); + expect(iosPlugin.sharedDarwinSource, isFalse); expect(androidPlugin.pluginClass, 'ASamplePlugin'); expect(androidPlugin.package, 'com.flutter.dev'); expect(linuxPlugin.pluginClass, 'LSamplePlugin'); expect(macOSPlugin.pluginClass, 'MSamplePlugin'); + expect(macOSPlugin.sharedDarwinSource, isFalse); expect(webPlugin.pluginClass, 'WebSamplePlugin'); expect(webPlugin.fileName, 'web_plugin.dart'); expect(windowsPlugin.pluginClass, 'WinSamplePlugin'); diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 588ace68890c..c761f4c0938e 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -34,6 +34,7 @@ class _PluginPlatformInfo { this.pluginClass, this.dartPluginClass, this.androidPackage, + this.sharedDarwinSource = false, this.fileName }) : assert(pluginClass != null || dartPluginClass != null), assert(androidPackage == null || pluginClass != null); @@ -47,6 +48,8 @@ class _PluginPlatformInfo { /// The package entry for an Android plugin implementation using pluginClass. final String? androidPackage; + final bool sharedDarwinSource; + /// The fileName entry for a web plugin implementation. final String? fileName; @@ -61,6 +64,8 @@ class _PluginPlatformInfo { '${indentation}dartPluginClass: $dartPluginClass', if (androidPackage != null) '${indentation}package: $androidPackage', + if (sharedDarwinSource) + '${indentation}sharedDarwinSource: true', if (fileName != null) '${indentation}fileName: $fileName', ].join('\n'); @@ -595,14 +600,14 @@ dependencies: }); testUsingContext( - '.flutter-plugins-dependencies indicates native build inclusion', () async { + '.flutter-plugins-dependencies contains plugin platform info', () async { createPlugin( name: 'plugin-a', platforms: const { // Native-only; should include native build. 'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'), // Hybrid native and Dart; should include native build. - 'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar'), + 'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar', sharedDarwinSource: true), // Web; should not have the native build key at all since it doesn't apply. 'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'), // Dart-only; should not include native build. @@ -618,20 +623,45 @@ dependencies: expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true); final String pluginsString = flutterProject.flutterPluginsDependenciesFile.readAsStringSync(); final Map jsonContent = json.decode(pluginsString) as Map; - final Map? plugins = jsonContent['plugins'] as Map?; - - // Extracts the native_build key (if any) from the first plugin for the - // given platform. - bool? getNativeBuildValue(String platform) { - final List> platformPlugins = (plugins![platform] - as List).cast>(); - expect(platformPlugins.length, 1); - return platformPlugins[0]['native_build'] as bool?; - } - expect(getNativeBuildValue('android'), true); - expect(getNativeBuildValue('ios'), true); - expect(getNativeBuildValue('web'), null); - expect(getNativeBuildValue('windows'), false); + final Map? actualPlugins = jsonContent['plugins'] as Map?; + + final Map expectedPlugins = { + 'ios': >[ + { + 'name': 'plugin-a', + 'path': '/.tmp_rand0/flutter_plugin.rand0/', + 'shared_darwin_source': true, + 'native_build': true, + 'dependencies': [] + } + ], + 'android': >[ + { + 'name': 'plugin-a', + 'path': '/.tmp_rand0/flutter_plugin.rand0/', + 'native_build': true, + 'dependencies': [] + } + ], + 'macos': >[], + 'linux': >[], + 'windows': >[ + { + 'name': 'plugin-a', + 'path': '/.tmp_rand0/flutter_plugin.rand0/', + 'native_build': false, + 'dependencies': [] + } + ], + 'web': >[ + { + 'name': 'plugin-a', + 'path': '/.tmp_rand0/flutter_plugin.rand0/', + 'dependencies': [] + } + ] + }; + expect(actualPlugins, expectedPlugins); }, overrides: { FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(),