[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
|
// 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 {
|
|||||||
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 {
|
|||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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(
|
|||||||
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(
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
linuxPlatform: linuxPlatform,
|
linuxPlatform: linuxPlatform,
|
||||||
macOSPlatform: macOSPlatform,
|
macOSPlatform: macOSPlatform,
|
||||||
windowsPlatform: windowsPlatform,
|
windowsPlatform: windowsPlatform,
|
||||||
|
allowedPlugins: allowedPlugins,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|||||||
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';
|
|||||||
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() {
|
|||||||
? 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:
|
|||||||
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', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user