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'; ...@@ -9,6 +9,12 @@ import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'globals.dart' as globals; 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. /// Marker interface for all platform specific plugin config impls.
abstract class PluginPlatform { abstract class PluginPlatform {
const PluginPlatform(); const PluginPlatform();
...@@ -16,6 +22,12 @@ abstract class PluginPlatform { ...@@ -16,6 +22,12 @@ abstract class PluginPlatform {
Map<String, dynamic> toMap(); 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. /// Contains parameters to template an Android plugin.
/// ///
/// The required fields include: [name] of the plugin, [package] of the plugin and /// The required fields include: [name] of the plugin, [package] of the plugin and
...@@ -191,19 +203,21 @@ class IOSPlugin extends PluginPlatform { ...@@ -191,19 +203,21 @@ class IOSPlugin extends PluginPlatform {
/// Contains the parameters to template a macOS plugin. /// Contains the parameters to template a macOS plugin.
/// ///
/// The required fields include: [name] of the plugin, and [pluginClass] that will /// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// be the entry point to the plugin's native code. /// [pluginClass] will be the entry point to the plugin's native code.
class MacOSPlugin extends PluginPlatform { class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
const MacOSPlugin({ const MacOSPlugin({
@required this.name, @required this.name,
@required this.pluginClass, this.pluginClass,
this.dartPluginClass,
}); });
factory MacOSPlugin.fromYaml(String name, YamlMap yaml) { factory MacOSPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml)); assert(validate(yaml));
return MacOSPlugin( return MacOSPlugin(
name: name, 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 { ...@@ -211,38 +225,45 @@ class MacOSPlugin extends PluginPlatform {
if (yaml == null) { if (yaml == null) {
return false; return false;
} }
return yaml['pluginClass'] is String; return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
} }
static const String kConfigKey = 'macos'; static const String kConfigKey = 'macos';
final String name; final String name;
final String pluginClass; final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override @override
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{
'name': name, 'name': name,
'class': pluginClass, if (pluginClass != null) 'class': pluginClass,
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
}; };
} }
} }
/// Contains the parameters to template a Windows plugin. /// Contains the parameters to template a Windows plugin.
/// ///
/// The required fields include: [name] of the plugin, and [pluginClass] that will /// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// be the entry point to the plugin's native code. /// [pluginClass] will be the entry point to the plugin's native code.
class WindowsPlugin extends PluginPlatform { class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
const WindowsPlugin({ const WindowsPlugin({
@required this.name, @required this.name,
@required this.pluginClass, this.pluginClass,
}); this.dartPluginClass,
}) : assert(pluginClass != null || dartPluginClass != null);
factory WindowsPlugin.fromYaml(String name, YamlMap yaml) { factory WindowsPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml)); assert(validate(yaml));
return WindowsPlugin( return WindowsPlugin(
name: name, 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 { ...@@ -250,39 +271,46 @@ class WindowsPlugin extends PluginPlatform {
if (yaml == null) { if (yaml == null) {
return false; return false;
} }
return yaml['pluginClass'] is String; return yaml[kDartPluginClass] is String || yaml[kPluginClass] is String;
} }
static const String kConfigKey = 'windows'; static const String kConfigKey = 'windows';
final String name; final String name;
final String pluginClass; final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override @override
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{
'name': name, 'name': name,
'class': pluginClass, if (pluginClass != null) 'class': pluginClass,
'filename': _filenameForCppClass(pluginClass), if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
}; };
} }
} }
/// Contains the parameters to template a Linux plugin. /// Contains the parameters to template a Linux plugin.
/// ///
/// The required fields include: [name] of the plugin, and [pluginClass] that will /// The [name] of the plugin is required. Either [dartPluginClass] or [pluginClass] are required.
/// be the entry point to the plugin's native code. /// [pluginClass] will be the entry point to the plugin's native code.
class LinuxPlugin extends PluginPlatform { class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
const LinuxPlugin({ const LinuxPlugin({
@required this.name, @required this.name,
@required this.pluginClass, this.pluginClass,
}); this.dartPluginClass,
}) : assert(pluginClass != null || dartPluginClass != null);
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) { factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml)); assert(validate(yaml));
return LinuxPlugin( return LinuxPlugin(
name: name, 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 { ...@@ -290,20 +318,25 @@ class LinuxPlugin extends PluginPlatform {
if (yaml == null) { if (yaml == null) {
return false; return false;
} }
return yaml['pluginClass'] is String; return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
} }
static const String kConfigKey = 'linux'; static const String kConfigKey = 'linux';
final String name; final String name;
final String pluginClass; final String pluginClass;
final String dartPluginClass;
@override
bool isNative() => pluginClass != null;
@override @override
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{
'name': name, 'name': name,
'class': pluginClass, if (pluginClass != null) 'class': pluginClass,
'filename': _filenameForCppClass(pluginClass), if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
}; };
} }
} }
......
...@@ -871,7 +871,8 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug ...@@ -871,7 +871,8 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
} }
Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async { 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 // 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 // included by the main makefile, so relative paths must be relative to that
// file's directory. // file's directory.
...@@ -901,7 +902,8 @@ Future<void> _writeLinuxPluginMakefile(Directory destination, Map<String, dynami ...@@ -901,7 +902,8 @@ Future<void> _writeLinuxPluginMakefile(Directory destination, Map<String, dynami
} }
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { 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>{ final Map<String, dynamic> context = <String, dynamic>{
'os': 'macos', 'os': 'macos',
'framework': 'FlutterMacOS', 'framework': 'FlutterMacOS',
...@@ -915,8 +917,25 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl ...@@ -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 { 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>{ final Map<String, dynamic> context = <String, dynamic>{
'plugins': windowsPlugins, 'plugins': windowsPlugins,
}; };
...@@ -1092,7 +1111,9 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) ...@@ -1092,7 +1111,9 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
} }
if (featureFlags.isWindowsEnabled && project.windows.existsSync()) { if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
await _writeWindowsPluginFiles(project, plugins); 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]) { for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
if (!project.isModule && (!checkProjects || subproject.existsSync())) { if (!project.isModule && (!checkProjects || subproject.existsSync())) {
......
...@@ -80,21 +80,24 @@ ...@@ -80,21 +80,24 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"pluginClass": {"type": "string"} "pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
} }
}, },
"macos": { "macos": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"pluginClass": {"type": "string"} "pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
} }
}, },
"windows": { "windows": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"pluginClass": {"type": "string"} "pluginClass": {"type": "string"},
"dartPluginClass": {"type": "string"}
} }
} }
} }
......
...@@ -122,6 +122,33 @@ void main() { ...@@ -122,6 +122,33 @@ void main() {
expect(windowsPlugin.pluginClass, 'WinSamplePlugin'); 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', () { 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' const String pluginYamlRaw = 'androidPackage: com.flutter.dev\n'
'platforms:\n' 'platforms:\n'
......
...@@ -6,6 +6,7 @@ import 'dart:convert'; ...@@ -6,6 +6,7 @@ import 'dart:convert';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.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/base/time.dart';
import 'package:flutter_tools/src/dart/package_map.dart'; import 'package:flutter_tools/src/dart/package_map.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
...@@ -56,6 +57,8 @@ void main() { ...@@ -56,6 +57,8 @@ void main() {
when(flutterProject.macos).thenReturn(macosProject); when(flutterProject.macos).thenReturn(macosProject);
when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile')); when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock')); 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.pluginConfigKey).thenReturn('macos');
when(macosProject.existsSync()).thenReturn(false); when(macosProject.existsSync()).thenReturn(false);
androidProject = MockAndroidProject(); androidProject = MockAndroidProject();
...@@ -129,7 +132,6 @@ void main() { ...@@ -129,7 +132,6 @@ void main() {
'''); ''');
} }
void createNewJavaPlugin1() { void createNewJavaPlugin1() {
final Directory pluginUsingJavaAndNewEmbeddingDir = final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
...@@ -879,6 +881,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr ...@@ -879,6 +881,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags, 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 { testUsingContext('Injecting creates generated Linux registrant', () async {
when(linuxProject.existsSync()).thenReturn(true); when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true); when(featureFlags.isLinuxEnabled).thenReturn(true);
...@@ -899,6 +928,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr ...@@ -899,6 +928,33 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags, 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 { testUsingContext('Injecting creates generated Linux plugin makefile', () async {
when(linuxProject.existsSync()).thenReturn(true); when(linuxProject.existsSync()).thenReturn(true);
when(featureFlags.isLinuxEnabled).thenReturn(true); when(featureFlags.isLinuxEnabled).thenReturn(true);
...@@ -946,6 +1002,36 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr ...@@ -946,6 +1002,36 @@ web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri.toStr
FeatureFlags: () => featureFlags, 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 { testUsingContext('Injecting creates generated Windows plugin properties', () async {
when(windowsProject.existsSync()).thenReturn(true); when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).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