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 @@
// 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';
......
......@@ -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;
......
......@@ -920,8 +920,29 @@ List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, P
}
@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);
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<Map<String, Object?>> windowsMethodChannelPlugins = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey);
final List<Plugin> ffiPlugins = _filterFfiPlugins(plugins, WindowsPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains);
......@@ -1156,6 +1177,7 @@ Future<void> injectPlugins(
bool linuxPlatform = false,
bool macOSPlatform = false,
bool windowsPlatform = false,
Iterable<String>? allowedPlugins,
}) async {
final List<Plugin> plugins = await findPlugins(project);
// Sort the plugins by name to keep ordering stable in generated files.
......@@ -1173,7 +1195,7 @@ Future<void> 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<XcodeBasedProject> darwinProjects = <XcodeBasedProject>[
......
......@@ -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<String> supportedPubPlugins = <String>[];
@override
void clearLogs() { }
......
......@@ -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<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(
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<String>? allowedPlugins,
}) async {
if (!directory.existsSync() || isPlugin) {
return;
......@@ -383,6 +392,7 @@ class FlutterProject {
linuxPlatform: linuxPlatform,
macOSPlatform: macOSPlatform,
windowsPlatform: windowsPlatform,
allowedPlugins: allowedPlugins,
);
}
......
......@@ -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 <command> -h') for available flutter commands and
project: project,
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) {
await _sendNullSafetyAnalyticsEvents(project);
}
......
......@@ -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 <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', () {
......
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