Unverified Commit e0127526 authored by Francisco Magdaleno's avatar Francisco Magdaleno Committed by GitHub

[flutter_tools] Don't generate native registrant classes if no pluginClass is defined (#53785)

parent 63f8b9a4
......@@ -9,6 +9,12 @@ import 'base/common.dart';
import 'base/file_system.dart';
import 'globals.dart' as globals;
/// Constant for 'pluginClass' key in plugin maps.
const String kPluginClass = 'pluginClass';
/// Constant for 'pluginClass' key in plugin maps.
const String kDartPluginClass = 'dartPluginClass';
/// Marker interface for all platform specific plugin config impls.
abstract class PluginPlatform {
const PluginPlatform();
......@@ -16,6 +22,12 @@ abstract class PluginPlatform {
Map<String, dynamic> toMap();
}
abstract class NativeOrDartPlugin {
/// Determines whether the plugin has a native implementation or if it's a
/// Dart-only plugin.
bool isNative();
}
/// Contains parameters to template an Android plugin.
///
/// The required fields include: [name] of the plugin, [package] of the plugin and
......@@ -191,19 +203,21 @@ class IOSPlugin extends PluginPlatform {
/// Contains the parameters to template a macOS plugin.
///
/// The required fields include: [name] of the plugin, and [pluginClass] that will
/// be the entry point to the plugin's native code.
class MacOSPlugin extends PluginPlatform {
/// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// [pluginClass] will be the entry point to the plugin's native code.
class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
const MacOSPlugin({
@required this.name,
@required this.pluginClass,
this.pluginClass,
this.dartPluginClass,
});
factory MacOSPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
return MacOSPlugin(
name: name,
pluginClass: yaml['pluginClass'] as String,
pluginClass: yaml[kPluginClass] as String,
dartPluginClass: yaml[kDartPluginClass] as String,
);
}
......@@ -211,38 +225,45 @@ class MacOSPlugin extends PluginPlatform {
if (yaml == null) {
return false;
}
return yaml['pluginClass'] is String;
return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
}
static const String kConfigKey = 'macos';
final String name;
final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'class': pluginClass,
if (pluginClass != null) 'class': pluginClass,
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
};
}
}
/// Contains the parameters to template a Windows plugin.
///
/// The required fields include: [name] of the plugin, and [pluginClass] that will
/// be the entry point to the plugin's native code.
class WindowsPlugin extends PluginPlatform {
/// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// [pluginClass] will be the entry point to the plugin's native code.
class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
const WindowsPlugin({
@required this.name,
@required this.pluginClass,
});
this.pluginClass,
this.dartPluginClass,
}) : assert(pluginClass != null || dartPluginClass != null);
factory WindowsPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
return WindowsPlugin(
name: name,
pluginClass: yaml['pluginClass'] as String,
pluginClass: yaml[kPluginClass] as String,
dartPluginClass: yaml[kDartPluginClass] as String,
);
}
......@@ -250,39 +271,46 @@ class WindowsPlugin extends PluginPlatform {
if (yaml == null) {
return false;
}
return yaml['pluginClass'] is String;
return yaml[kDartPluginClass] is String || yaml[kPluginClass] is String;
}
static const String kConfigKey = 'windows';
final String name;
final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'class': pluginClass,
'filename': _filenameForCppClass(pluginClass),
if (pluginClass != null) 'class': pluginClass,
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
};
}
}
/// Contains the parameters to template a Linux plugin.
///
/// The required fields include: [name] of the plugin, and [pluginClass] that will
/// be the entry point to the plugin's native code.
class LinuxPlugin extends PluginPlatform {
/// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// [pluginClass] will be the entry point to the plugin's native code.
class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
const LinuxPlugin({
@required this.name,
@required this.pluginClass,
});
this.pluginClass,
this.dartPluginClass,
}) : assert(pluginClass != null || dartPluginClass != null);
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
return LinuxPlugin(
name: name,
pluginClass: yaml['pluginClass'] as String,
pluginClass: yaml[kPluginClass] as String,
dartPluginClass: yaml[kDartPluginClass] as String,
);
}
......@@ -290,20 +318,25 @@ class LinuxPlugin extends PluginPlatform {
if (yaml == null) {
return false;
}
return yaml['pluginClass'] is String;
return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
}
static const String kConfigKey = 'linux';
final String name;
final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'class': pluginClass,
'filename': _filenameForCppClass(pluginClass),
if (pluginClass != null) 'class': pluginClass,
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
};
}
}
......
......@@ -871,7 +871,8 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
}
Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> linuxPlugins = _extractPlatformMaps(plugins, LinuxPlugin.kConfigKey);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, LinuxPlugin.kConfigKey);
final List<Map<String, dynamic>> linuxPlugins = _extractPlatformMaps(nativePlugins, LinuxPlugin.kConfigKey);
// The generated makefile is checked in, so can't use absolute paths. It is
// included by the main makefile, so relative paths must be relative to that
// file's directory.
......@@ -901,7 +902,8 @@ Future<void> _writeLinuxPluginMakefile(Directory destination, Map<String, dynami
}
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> macosPlugins = _extractPlatformMaps(plugins, MacOSPlugin.kConfigKey);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, MacOSPlugin.kConfigKey);
final List<Map<String, dynamic>> macosPlugins = _extractPlatformMaps(nativePlugins, MacOSPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
'os': 'macos',
'framework': 'FlutterMacOS',
......@@ -915,8 +917,25 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl
);
}
/// Filters out Dart-only plugins, which shouldn't be added to the native generated registrants.
List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
return plugins.where((Plugin element) {
final PluginPlatform plugin = element.platforms[platformKey];
if (plugin == null) {
return false;
}
if (plugin is NativeOrDartPlugin) {
return (plugin as NativeOrDartPlugin).isNative();
}
// Not all platforms have the ability to create Dart-only plugins. Therefore, any plugin that doesn't
// implement NativeOrDartPlugin is always native.
return true;
}).toList();
}
Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(plugins, WindowsPlugin.kConfigKey);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
'plugins': windowsPlugins,
};
......@@ -1092,7 +1111,9 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
}
if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
await _writeWindowsPluginFiles(project, plugins);
await VisualStudioSolutionUtils(project: project.windows, fileSystem: globals.fs).updatePlugins(plugins);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
await VisualStudioSolutionUtils(project: project.windows, fileSystem: globals.fs).updatePlugins(nativePlugins);
}
for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
if (!project.isModule && (!checkProjects || subproject.existsSync())) {
......
......@@ -80,21 +80,24 @@
"type": "object",
"additionalProperties": false,
"properties": {
"pluginClass": {"type": "string"}
"pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
}
},
"macos": {
"type": "object",
"additionalProperties": false,
"properties": {
"pluginClass": {"type": "string"}
"pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
}
},
"windows": {
"type": "object",
"additionalProperties": false,
"properties": {
"pluginClass": {"type": "string"}
"pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
}
}
}
......
......@@ -122,6 +122,33 @@ void main() {
expect(windowsPlugin.pluginClass, 'WinSamplePlugin');
});
test('Allow for Dart-only plugins without a pluginClass', () {
/// This is currently supported only on macOS, linux, Windows.
const String pluginYamlRaw = 'implements: same_plugin\n' // this should be ignored by the tool
'platforms:\n'
' linux:\n'
' dartPluginClass: LSamplePlugin\n'
' macos:\n'
' dartPluginClass: MSamplePlugin\n'
' windows:\n'
' dartPluginClass: WinSamplePlugin\n';
final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin =
Plugin.fromYaml(_kTestPluginName, _kTestPluginPath, pluginYaml as YamlMap, const <String>[]);
final LinuxPlugin linuxPlugin = plugin.platforms[LinuxPlugin.kConfigKey] as LinuxPlugin;
final MacOSPlugin macOSPlugin = plugin.platforms[MacOSPlugin.kConfigKey] as MacOSPlugin;
final WindowsPlugin windowsPlugin = plugin.platforms[WindowsPlugin.kConfigKey] as WindowsPlugin;
expect(linuxPlugin.pluginClass, isNull);
expect(macOSPlugin.pluginClass, isNull);
expect(windowsPlugin.pluginClass, isNull);
expect(linuxPlugin.dartPluginClass, 'LSamplePlugin');
expect(macOSPlugin.dartPluginClass, 'MSamplePlugin');
expect(windowsPlugin.dartPluginClass, 'WinSamplePlugin');
});
test('Legacy Format and Multi-Platform Format together is not allowed and error message contains plugin name', () {
const String pluginYamlRaw = 'androidPackage: com.flutter.dev\n'
'platforms:\n'
......
......@@ -6,6 +6,7 @@ import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/dart/package_map.dart';
import 'package:flutter_tools/src/features.dart';
......@@ -56,6 +57,8 @@ void main() {
when(flutterProject.macos).thenReturn(macosProject);
when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock'));
final Directory macosManagedDirectory = flutterProject.directory.childDirectory('macos').childDirectory('Flutter');
when(macosProject.managedDirectory).thenReturn(macosManagedDirectory);
when(macosProject.pluginConfigKey).thenReturn('macos');
when(macosProject.existsSync()).thenReturn(false);
androidProject = MockAndroidProject();
......@@ -129,7 +132,6 @@ void main() {
''');
}
void createNewJavaPlugin1() {
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
......@@ -879,6 +881,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated macos registrant, but does not include Dart-only plugins', () async {
when(macosProject.existsSync()).thenReturn(true);
when(featureFlags.isMacOSEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(true);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
flutter:
plugin:
platforms:
macos:
dartPluginClass: SomePlugin
''');
await injectPlugins(flutterProject, checkProjects: true);
final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift');
expect(registrantFile, exists);
expect(registrantFile, isNot(contains('SomePlugin')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux registrant', () async {
when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
......@@ -899,6 +928,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux registrant, but does not include Dart-only plugins', () async {
when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
flutter:
plugin:
platforms:
linux:
dartPluginClass: SomePlugin
''');
await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = linuxProject.managedDirectory.childFile('generated_plugin_registrant.cc');
expect(registrantImpl, exists);
expect(registrantImpl, isNot(contains('SomePlugin')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Linux plugin makefile', () async {
when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true);
......@@ -946,6 +1002,36 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Windows registrant, but does not include Dart-only plugins', () async {
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
// Create a plugin without a pluginClass.
dummyPackageDirectory.parent.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
flutter:
plugin:
platforms:
windows:
dartPluginClass: SomePlugin
''');
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
expect(registrantImpl, exists);
expect(registrantImpl, isNot(contains('SomePlugin')));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting creates generated Windows plugin properties', () async {
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
......
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