Unverified Commit d550ba54 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[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.
parent 5ebca79f
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
......
...@@ -150,7 +150,7 @@ abstract class Pub { ...@@ -150,7 +150,7 @@ abstract class Pub {
String? flutterRootOverride, String? flutterRootOverride,
bool checkUpToDate = false, bool checkUpToDate = false,
bool shouldSkipThirdPartyGenerator = true, bool shouldSkipThirdPartyGenerator = true,
PubOutputMode outputMode = PubOutputMode.all PubOutputMode outputMode = PubOutputMode.all,
}); });
/// Runs pub in 'batch' mode. /// Runs pub in 'batch' mode.
...@@ -255,7 +255,7 @@ class _DefaultPub implements Pub { ...@@ -255,7 +255,7 @@ class _DefaultPub implements Pub {
String? flutterRootOverride, String? flutterRootOverride,
bool checkUpToDate = false, bool checkUpToDate = false,
bool shouldSkipThirdPartyGenerator = true, bool shouldSkipThirdPartyGenerator = true,
PubOutputMode outputMode = PubOutputMode.all PubOutputMode outputMode = PubOutputMode.all,
}) async { }) async {
final String directory = project.directory.path; final String directory = project.directory.path;
final File packageConfigFile = project.packageConfigFile; final File packageConfigFile = project.packageConfigFile;
......
...@@ -920,8 +920,29 @@ List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, P ...@@ -920,8 +920,29 @@ List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, P
} }
@visibleForTesting @visibleForTesting
Future<void> writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins, TemplateRenderer templateRenderer) async { Future<void> writeWindowsPluginFiles(
FlutterProject project,
List<Plugin> plugins,
TemplateRenderer templateRenderer, {
Iterable<String>? allowedPlugins,
}) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, WindowsPlugin.kConfigKey); final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, WindowsPlugin.kConfigKey);
if (allowedPlugins != null) {
final List<Plugin> 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<Plugin> win32Plugins = _filterPluginsByVariant(methodChannelPlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.win32); final List<Plugin> win32Plugins = _filterPluginsByVariant(methodChannelPlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.win32);
final List<Map<String, Object?>> windowsMethodChannelPlugins = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey); final List<Map<String, Object?>> windowsMethodChannelPlugins = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey);
final List<Plugin> ffiPlugins = _filterFfiPlugins(plugins, WindowsPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains); final List<Plugin> ffiPlugins = _filterFfiPlugins(plugins, WindowsPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains);
...@@ -1156,6 +1177,7 @@ Future<void> injectPlugins( ...@@ -1156,6 +1177,7 @@ Future<void> injectPlugins(
bool linuxPlatform = false, bool linuxPlatform = false,
bool macOSPlatform = false, bool macOSPlatform = false,
bool windowsPlatform = false, bool windowsPlatform = false,
Iterable<String>? allowedPlugins,
}) 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. // Sort the plugins by name to keep ordering stable in generated files.
...@@ -1173,7 +1195,7 @@ Future<void> injectPlugins( ...@@ -1173,7 +1195,7 @@ Future<void> injectPlugins(
await _writeMacOSPluginRegistrant(project, plugins); await _writeMacOSPluginRegistrant(project, plugins);
} }
if (windowsPlatform) { if (windowsPlatform) {
await writeWindowsPluginFiles(project, plugins, globals.templateRenderer); await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins);
} }
if (!project.isModule) { if (!project.isModule) {
final List<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[ final List<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[
......
...@@ -123,6 +123,11 @@ class PreviewDevice extends Device { ...@@ -123,6 +123,11 @@ class PreviewDevice extends Device {
final Artifacts _artifacts; final Artifacts _artifacts;
final File _previewBinary; final File _previewBinary;
/// The set of plugins that are allowed to be used by Preview users.
///
/// Currently no plugins are supported.
static const List<String> supportedPubPlugins = <String>[];
@override @override
void clearLogs() { } void clearLogs() { }
......
...@@ -329,7 +329,14 @@ class FlutterProject { ...@@ -329,7 +329,14 @@ class FlutterProject {
/// registrants for app and module projects only. /// registrants for app and module projects only.
/// ///
/// Will not create project platform directories if they do not already exist. /// Will not create project platform directories if they do not already exist.
Future<void> 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<void> regeneratePlatformSpecificTooling({
DeprecationBehavior deprecationBehavior = DeprecationBehavior.none,
Iterable<String>? allowedPlugins,
}) async {
return ensureReadyForPlatformSpecificTooling( return ensureReadyForPlatformSpecificTooling(
androidPlatform: android.existsSync(), androidPlatform: android.existsSync(),
iosPlatform: ios.existsSync(), iosPlatform: ios.existsSync(),
...@@ -340,6 +347,7 @@ class FlutterProject { ...@@ -340,6 +347,7 @@ class FlutterProject {
windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(), windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(),
webPlatform: featureFlags.isWebEnabled && web.existsSync(), webPlatform: featureFlags.isWebEnabled && web.existsSync(),
deprecationBehavior: deprecationBehavior, deprecationBehavior: deprecationBehavior,
allowedPlugins: allowedPlugins,
); );
} }
...@@ -353,6 +361,7 @@ class FlutterProject { ...@@ -353,6 +361,7 @@ class FlutterProject {
bool windowsPlatform = false, bool windowsPlatform = false,
bool webPlatform = false, bool webPlatform = false,
DeprecationBehavior deprecationBehavior = DeprecationBehavior.none, DeprecationBehavior deprecationBehavior = DeprecationBehavior.none,
Iterable<String>? allowedPlugins,
}) async { }) async {
if (!directory.existsSync() || isPlugin) { if (!directory.existsSync() || isPlugin) {
return; return;
...@@ -383,6 +392,7 @@ class FlutterProject { ...@@ -383,6 +392,7 @@ class FlutterProject {
linuxPlatform: linuxPlatform, linuxPlatform: linuxPlatform,
macOSPlatform: macOSPlatform, macOSPlatform: macOSPlatform,
windowsPlatform: windowsPlatform, windowsPlatform: windowsPlatform,
allowedPlugins: allowedPlugins,
); );
} }
......
...@@ -27,6 +27,7 @@ import '../dart/pub.dart'; ...@@ -27,6 +27,7 @@ import '../dart/pub.dart';
import '../device.dart'; import '../device.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../preview_device.dart';
import '../project.dart'; import '../project.dart';
import '../reporting/reporting.dart'; import '../reporting/reporting.dart';
import '../web/compile.dart'; import '../web/compile.dart';
...@@ -1694,7 +1695,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and ...@@ -1694,7 +1695,14 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
project: project, project: project,
checkUpToDate: cachePubGet, checkUpToDate: cachePubGet,
); );
await project.regeneratePlatformSpecificTooling();
// null implicitly means all plugins are allowed
List<String>? 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) { if (reportNullSafety) {
await _sendNullSafetyAnalyticsEvents(project); await _sendNullSafetyAnalyticsEvents(project);
} }
......
...@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/flutter_plugins.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/globals.dart' as globals;
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/plugins.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/project.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
...@@ -210,7 +211,7 @@ void main() { ...@@ -210,7 +211,7 @@ void main() {
? fakePubCache.childDirectory(name) ? fakePubCache.childDirectory(name)
: fileSystem.directory(nameOrPath); : fileSystem.directory(nameOrPath);
packagesFile.writeAsStringSync( packagesFile.writeAsStringSync(
'$name:file://${pluginDirectory.childFile('lib').uri}\n', '$name:${pluginDirectory.childFile('lib').uri}\n',
mode: FileMode.writeOnlyAppend); mode: FileMode.writeOnlyAppend);
pluginDirectory.childFile('pubspec.yaml') pluginDirectory.childFile('pubspec.yaml')
..createSync(recursive: true) ..createSync(recursive: true)
...@@ -1438,6 +1439,29 @@ flutter: ...@@ -1438,6 +1439,29 @@ flutter:
FileSystem: () => fsWindows, FileSystem: () => fsWindows,
ProcessManager: () => FakeProcessManager.any(), 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 <String>['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: <Type, Generator>{
FileSystem: () => fsWindows,
ProcessManager: () => FakeProcessManager.empty(),
});
}); });
group('createPluginSymlinks', () { group('createPluginSymlinks', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment