[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.
This commit is contained in:
parent
5ebca79f76
commit
d550ba54eb
@ -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', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user