From d550ba54eb10ab3cc356be334c88d2fe490f746a Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Tue, 7 Nov 2023 12:37:19 -0800 Subject: [PATCH] [flutter_tools] toolexit when using plugins with preview device (#136936) Part of https://github.com/flutter/flutter/issues/130277 Without this, if a user runs an app that has plugins that call method channels with the `preview` device, the app will build successfully, however, they will get a runtime error when their dart code tries to call the method channel that does not exist in the native build (which was pre-built and thus does not include the plugin code). This change adds a validation when injecting plugins that will tool exit if the device-id is `preview` and their project contains plugins with method channels. --- .../flutter_tools/lib/src/commands/run.dart | 2 -- packages/flutter_tools/lib/src/dart/pub.dart | 4 +-- .../lib/src/flutter_plugins.dart | 26 +++++++++++++++++-- .../flutter_tools/lib/src/preview_device.dart | 5 ++++ packages/flutter_tools/lib/src/project.dart | 12 ++++++++- .../lib/src/runner/flutter_command.dart | 10 ++++++- .../test/general.shard/plugins_test.dart | 26 ++++++++++++++++++- 7 files changed, 76 insertions(+), 9 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index a11116851d..c5796efd22 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - - import 'dart:async'; import 'package:meta/meta.dart'; diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index 40e60f9005..15b4a6fed9 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -150,7 +150,7 @@ abstract class Pub { String? flutterRootOverride, bool checkUpToDate = false, bool shouldSkipThirdPartyGenerator = true, - PubOutputMode outputMode = PubOutputMode.all + PubOutputMode outputMode = PubOutputMode.all, }); /// Runs pub in 'batch' mode. @@ -255,7 +255,7 @@ class _DefaultPub implements Pub { String? flutterRootOverride, bool checkUpToDate = false, bool shouldSkipThirdPartyGenerator = true, - PubOutputMode outputMode = PubOutputMode.all + PubOutputMode outputMode = PubOutputMode.all, }) async { final String directory = project.directory.path; final File packageConfigFile = project.packageConfigFile; diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index fd38dd79be..3a78ab3d83 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -920,8 +920,29 @@ List _filterPluginsByVariant(List plugins, String platformKey, P } @visibleForTesting -Future writeWindowsPluginFiles(FlutterProject project, List plugins, TemplateRenderer templateRenderer) async { +Future writeWindowsPluginFiles( + FlutterProject project, + List plugins, + TemplateRenderer templateRenderer, { + Iterable? allowedPlugins, +}) async { final List methodChannelPlugins = _filterMethodChannelPlugins(plugins, WindowsPlugin.kConfigKey); + if (allowedPlugins != null) { + final List disallowedPlugins = methodChannelPlugins + .toList() + ..removeWhere((Plugin plugin) => allowedPlugins.contains(plugin.name)); + if (disallowedPlugins.isNotEmpty) { + final StringBuffer buffer = StringBuffer(); + buffer.writeln('The Flutter Preview device does not support the following plugins from your pubspec.yaml:'); + buffer.writeln(); + buffer.writeln(disallowedPlugins.map((Plugin p) => p.name).toList().toString()); + buffer.writeln(); + buffer.writeln('In order to build a Flutter app with plugins, you must use another target platform,'); + buffer.writeln('such as Windows. Type `flutter doctor` into your terminal to see which target platforms'); + buffer.writeln('are ready to be used, and how to get required dependencies for other platforms.'); + throwToolExit(buffer.toString()); + } + } final List win32Plugins = _filterPluginsByVariant(methodChannelPlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.win32); final List> windowsMethodChannelPlugins = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey); final List ffiPlugins = _filterFfiPlugins(plugins, WindowsPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains); @@ -1156,6 +1177,7 @@ Future injectPlugins( bool linuxPlatform = false, bool macOSPlatform = false, bool windowsPlatform = false, + Iterable? allowedPlugins, }) async { final List plugins = await findPlugins(project); // Sort the plugins by name to keep ordering stable in generated files. @@ -1173,7 +1195,7 @@ Future injectPlugins( await _writeMacOSPluginRegistrant(project, plugins); } if (windowsPlatform) { - await writeWindowsPluginFiles(project, plugins, globals.templateRenderer); + await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins); } if (!project.isModule) { final List darwinProjects = [ diff --git a/packages/flutter_tools/lib/src/preview_device.dart b/packages/flutter_tools/lib/src/preview_device.dart index aedd505b43..20b54b0468 100644 --- a/packages/flutter_tools/lib/src/preview_device.dart +++ b/packages/flutter_tools/lib/src/preview_device.dart @@ -123,6 +123,11 @@ class PreviewDevice extends Device { final Artifacts _artifacts; final File _previewBinary; + /// The set of plugins that are allowed to be used by Preview users. + /// + /// Currently no plugins are supported. + static const List supportedPubPlugins = []; + @override void clearLogs() { } diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index c8d9bbf0a0..07c57a15b6 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -329,7 +329,14 @@ class FlutterProject { /// registrants for app and module projects only. /// /// Will not create project platform directories if they do not already exist. - Future regeneratePlatformSpecificTooling({DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) async { + /// + /// If [allowedPlugins] is non-null, all plugins with method channels in the + /// project's pubspec.yaml will be validated to be in that set, or else a + /// [ToolExit] will be thrown. + Future regeneratePlatformSpecificTooling({ + DeprecationBehavior deprecationBehavior = DeprecationBehavior.none, + Iterable? allowedPlugins, + }) async { return ensureReadyForPlatformSpecificTooling( androidPlatform: android.existsSync(), iosPlatform: ios.existsSync(), @@ -340,6 +347,7 @@ class FlutterProject { windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(), webPlatform: featureFlags.isWebEnabled && web.existsSync(), deprecationBehavior: deprecationBehavior, + allowedPlugins: allowedPlugins, ); } @@ -353,6 +361,7 @@ class FlutterProject { bool windowsPlatform = false, bool webPlatform = false, DeprecationBehavior deprecationBehavior = DeprecationBehavior.none, + Iterable? allowedPlugins, }) async { if (!directory.existsSync() || isPlugin) { return; @@ -383,6 +392,7 @@ class FlutterProject { linuxPlatform: linuxPlatform, macOSPlatform: macOSPlatform, windowsPlatform: windowsPlatform, + allowedPlugins: allowedPlugins, ); } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 05f9250ddb..56dd642afb 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -27,6 +27,7 @@ import '../dart/pub.dart'; import '../device.dart'; import '../features.dart'; import '../globals.dart' as globals; +import '../preview_device.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../web/compile.dart'; @@ -1694,7 +1695,14 @@ Run 'flutter -h' (or 'flutter -h') for available flutter commands and project: project, checkUpToDate: cachePubGet, ); - await project.regeneratePlatformSpecificTooling(); + + // null implicitly means all plugins are allowed + List? allowedPlugins; + if (stringArg(FlutterGlobalOptions.kDeviceIdOption, global: true) == 'preview') { + // The preview device does not currently support any plugins. + allowedPlugins = PreviewDevice.supportedPubPlugins; + } + await project.regeneratePlatformSpecificTooling(allowedPlugins: allowedPlugins); if (reportNullSafety) { await _sendNullSafetyAnalyticsEvents(project); } diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 8efcbefca3..a1787c0116 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -17,6 +17,7 @@ import 'package:flutter_tools/src/flutter_plugins.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/plugins.dart'; +import 'package:flutter_tools/src/preview_device.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:test/fake.dart'; @@ -210,7 +211,7 @@ void main() { ? fakePubCache.childDirectory(name) : fileSystem.directory(nameOrPath); packagesFile.writeAsStringSync( - '$name:file://${pluginDirectory.childFile('lib').uri}\n', + '$name:${pluginDirectory.childFile('lib').uri}\n', mode: FileMode.writeOnlyAppend); pluginDirectory.childFile('pubspec.yaml') ..createSync(recursive: true) @@ -1438,6 +1439,29 @@ flutter: FileSystem: () => fsWindows, ProcessManager: () => FakeProcessManager.any(), }); + + testUsingContext('injectPlugins will validate if all plugins in the project are part of the passed allowedPlugins', () async { + // Re-run the setup using the Windows filesystem. + setUpProject(fsWindows); + createFakePlugins(fsWindows, const ['plugin_one', 'plugin_two']); + + expect( + () => injectPlugins( + flutterProject, + linuxPlatform: true, + windowsPlatform: true, + allowedPlugins: PreviewDevice.supportedPubPlugins, + ), + throwsToolExit(message: ''' +The Flutter Preview device does not support the following plugins from your pubspec.yaml: + +[plugin_one, plugin_two] +'''), + ); + }, overrides: { + FileSystem: () => fsWindows, + ProcessManager: () => FakeProcessManager.empty(), + }); }); group('createPluginSymlinks', () {