Unverified Commit 56d68a90 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Add the beginnings of plugin support for Windows and Linux (#41015)

Adds very preliminary support for Windows and Linux plugins:
- Adds those platforms to the new plugin schema, initially supporting just a plugin class.
- Adds C++ plugin registrant generation for any Windows or Linux plugins found.

This doesn't have yet have any build tooling for either platform, so anyone using the generated registrant still needs to do manual build configuration. This reduces the manual work, however, and creates a starting point for future tooling work.

As with all Windows and Linux work at this time, this is not final, and subject to change without warning in the future (e.g., Windows could potentially switch to a C# interface, or
'linux' may change to 'gtk' or 'linux_gtk' in pubspec.yaml).
parent 5a27e1c1
...@@ -140,6 +140,86 @@ class MacOSPlugin extends PluginPlatform { ...@@ -140,6 +140,86 @@ class MacOSPlugin extends PluginPlatform {
} }
} }
/// 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 {
const WindowsPlugin({
@required this.name,
@required this.pluginClass,
});
factory WindowsPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
return WindowsPlugin(
name: name,
pluginClass: yaml['pluginClass'],
);
}
static bool validate(YamlMap yaml) {
if (yaml == null) {
return false;
}
return yaml['pluginClass'] is String;
}
static const String kConfigKey = 'windows';
final String name;
final String pluginClass;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'class': pluginClass,
'filename': _filenameForCppClass(pluginClass),
};
}
}
/// 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 {
const LinuxPlugin({
@required this.name,
@required this.pluginClass,
});
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
return LinuxPlugin(
name: name,
pluginClass: yaml['pluginClass'],
);
}
static bool validate(YamlMap yaml) {
if (yaml == null) {
return false;
}
return yaml['pluginClass'] is String;
}
static const String kConfigKey = 'linux';
final String name;
final String pluginClass;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'class': pluginClass,
'filename': _filenameForCppClass(pluginClass),
};
}
}
/// Contains the parameters to template a web plugin. /// Contains the parameters to template a web plugin.
/// ///
/// The required fields include: [name] of the plugin, the [pluginClass] that will /// The required fields include: [name] of the plugin, the [pluginClass] that will
...@@ -190,3 +270,11 @@ class WebPlugin extends PluginPlatform { ...@@ -190,3 +270,11 @@ class WebPlugin extends PluginPlatform {
}; };
} }
} }
final RegExp _internalCapitalLetterRegex = RegExp(r'(?=(?!^)[A-Z])');
String _filenameForCppClass(String className) {
return className.splitMapJoin(
_internalCapitalLetterRegex,
onMatch: (_) => '_',
onNonMatch: (String n) => n.toLowerCase());
}
...@@ -50,8 +50,12 @@ class Plugin { ...@@ -50,8 +50,12 @@ class Plugin {
/// pluginClass: SamplePlugin /// pluginClass: SamplePlugin
/// ios: /// ios:
/// pluginClass: SamplePlugin /// pluginClass: SamplePlugin
/// linux:
/// pluginClass: SamplePlugin
/// macos: /// macos:
/// pluginClass: SamplePlugin /// pluginClass: SamplePlugin
/// windows:
/// pluginClass: SamplePlugin
factory Plugin.fromYaml(String name, String path, dynamic pluginYaml) { factory Plugin.fromYaml(String name, String path, dynamic pluginYaml) {
final List<String> errors = validatePluginYaml(pluginYaml); final List<String> errors = validatePluginYaml(pluginYaml);
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
...@@ -84,6 +88,11 @@ class Plugin { ...@@ -84,6 +88,11 @@ class Plugin {
IOSPlugin.fromYaml(name, platformsYaml[IOSPlugin.kConfigKey]); IOSPlugin.fromYaml(name, platformsYaml[IOSPlugin.kConfigKey]);
} }
if (platformsYaml[LinuxPlugin.kConfigKey] != null) {
platforms[LinuxPlugin.kConfigKey] =
LinuxPlugin.fromYaml(name, platformsYaml[LinuxPlugin.kConfigKey]);
}
if (platformsYaml[MacOSPlugin.kConfigKey] != null) { if (platformsYaml[MacOSPlugin.kConfigKey] != null) {
platforms[MacOSPlugin.kConfigKey] = platforms[MacOSPlugin.kConfigKey] =
MacOSPlugin.fromYaml(name, platformsYaml[MacOSPlugin.kConfigKey]); MacOSPlugin.fromYaml(name, platformsYaml[MacOSPlugin.kConfigKey]);
...@@ -94,6 +103,11 @@ class Plugin { ...@@ -94,6 +103,11 @@ class Plugin {
WebPlugin.fromYaml(name, platformsYaml[WebPlugin.kConfigKey]); WebPlugin.fromYaml(name, platformsYaml[WebPlugin.kConfigKey]);
} }
if (platformsYaml[WindowsPlugin.kConfigKey] != null) {
platforms[WindowsPlugin.kConfigKey] =
WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey]);
}
return Plugin( return Plugin(
name: name, name: name,
path: path, path: path,
...@@ -156,10 +170,18 @@ class Plugin { ...@@ -156,10 +170,18 @@ class Plugin {
!IOSPlugin.validate(yaml[IOSPlugin.kConfigKey])) { !IOSPlugin.validate(yaml[IOSPlugin.kConfigKey])) {
errors.add('Invalid "ios" plugin specification.'); errors.add('Invalid "ios" plugin specification.');
} }
if (yaml.containsKey(LinuxPlugin.kConfigKey) &&
!LinuxPlugin.validate(yaml[LinuxPlugin.kConfigKey])) {
errors.add('Invalid "linux" plugin specification.');
}
if (yaml.containsKey(MacOSPlugin.kConfigKey) && if (yaml.containsKey(MacOSPlugin.kConfigKey) &&
!MacOSPlugin.validate(yaml[MacOSPlugin.kConfigKey])) { !MacOSPlugin.validate(yaml[MacOSPlugin.kConfigKey])) {
errors.add('Invalid "macos" plugin specification.'); errors.add('Invalid "macos" plugin specification.');
} }
if (yaml.containsKey(WindowsPlugin.kConfigKey) &&
!WindowsPlugin.validate(yaml[WindowsPlugin.kConfigKey])) {
errors.add('Invalid "windows" plugin specification.');
}
return errors; return errors;
} }
...@@ -171,9 +193,6 @@ class Plugin { ...@@ -171,9 +193,6 @@ class Plugin {
if (yaml['iosPrefix'] != null && yaml['iosPrefix'] is! String) { if (yaml['iosPrefix'] != null && yaml['iosPrefix'] is! String) {
errors.add('The "iosPrefix" must either be null or a string.'); errors.add('The "iosPrefix" must either be null or a string.');
} }
if (yaml['macosPrefix'] != null && yaml['macosPrefix'] is! String) {
errors.add('The "macosPrefix" must either be null or a string.');
}
if (yaml['pluginClass'] != null && yaml['pluginClass'] is! String) { if (yaml['pluginClass'] != null && yaml['pluginClass'] is! String) {
errors.add('The "pluginClass" must either be null or a string..'); errors.add('The "pluginClass" must either be null or a string..');
} }
...@@ -415,6 +434,39 @@ void registerPlugins(PluginRegistry registry) { ...@@ -415,6 +434,39 @@ void registerPlugins(PluginRegistry registry) {
} }
'''; ''';
const String _cppPluginRegistryHeaderTemplate = '''//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
''';
const String _cppPluginRegistryImplementationTemplate = '''//
// Generated file. Do not edit.
//
#include "generated_plugin_registrant.h"
{{#plugins}}
#include <{{filename}}.h>
{{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) {
{{#plugins}}
{{class}}RegisterWithRegistrar(
registry->GetRegistrarForPlugin("{{class}}"));
{{/plugins}}
}
''';
Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> iosPlugins = _extractPlatformMaps(plugins, IOSPlugin.kConfigKey); final List<Map<String, dynamic>> iosPlugins = _extractPlatformMaps(plugins, IOSPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
...@@ -455,6 +507,14 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug ...@@ -455,6 +507,14 @@ Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plug
} }
} }
Future<void> _writeLinuxPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> linuxPlugins = _extractPlatformMaps(plugins, LinuxPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
'plugins': linuxPlugins,
};
await _writeCppPluginRegistrant(project.linux.managedDirectory, context);
}
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<Map<String, dynamic>> macosPlugins = _extractPlatformMaps(plugins, MacOSPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
...@@ -470,6 +530,28 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl ...@@ -470,6 +530,28 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl
); );
} }
Future<void> _writeWindowsPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(plugins, WindowsPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
'plugins': windowsPlugins,
};
await _writeCppPluginRegistrant(project.windows.managedDirectory, context);
}
Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
final String registryDirectory = destination.path;
_renderTemplateToFile(
_cppPluginRegistryHeaderTemplate,
templateContext,
fs.path.join(registryDirectory, 'generated_plugin_registrant.h'),
);
_renderTemplateToFile(
_cppPluginRegistryImplementationTemplate,
templateContext,
fs.path.join(registryDirectory, 'generated_plugin_registrant.cc'),
);
}
Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey); final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
...@@ -527,12 +609,18 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) ...@@ -527,12 +609,18 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
if ((checkProjects && project.ios.existsSync()) || !checkProjects) { if ((checkProjects && project.ios.existsSync()) || !checkProjects) {
await _writeIOSPluginRegistrant(project, plugins); await _writeIOSPluginRegistrant(project, plugins);
} }
// TODO(stuartmorgan): Revisit the condition here once the plans for handling // TODO(stuartmorgan): Revisit the conditions here once the plans for handling
// desktop in existing projects are in place. For now, ignore checkProjects // desktop in existing projects are in place. For now, ignore checkProjects
// on desktop and always treat it as true. // on desktop and always treat it as true.
if (featureFlags.isLinuxEnabled && project.linux.existsSync()) {
await _writeLinuxPluginRegistrant(project, plugins);
}
if (featureFlags.isMacOSEnabled && project.macos.existsSync()) { if (featureFlags.isMacOSEnabled && project.macos.existsSync()) {
await _writeMacOSPluginRegistrant(project, plugins); await _writeMacOSPluginRegistrant(project, plugins);
} }
if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
await _writeWindowsPluginRegistrant(project, plugins);
}
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())) {
final CocoaPods cocoaPods = CocoaPods(); final CocoaPods cocoaPods = CocoaPods();
......
...@@ -207,11 +207,18 @@ class FlutterProject { ...@@ -207,11 +207,18 @@ class FlutterProject {
if ((ios.existsSync() && checkProjects) || !checkProjects) { if ((ios.existsSync() && checkProjects) || !checkProjects) {
await ios.ensureReadyForPlatformSpecificTooling(); await ios.ensureReadyForPlatformSpecificTooling();
} }
// TODO(stuartmorgan): Add checkProjects logic once a create workflow exists // TODO(stuartmorgan): Revisit conditions once there is a plan for handling
// for macOS. For now, always treat checkProjects as true for macOS. // non-default platform projects. For now, always treat checkProjects as
// true for desktop.
if (featureFlags.isLinuxEnabled && linux.existsSync()) {
await linux.ensureReadyForPlatformSpecificTooling();
}
if (featureFlags.isMacOSEnabled && macos.existsSync()) { if (featureFlags.isMacOSEnabled && macos.existsSync()) {
await macos.ensureReadyForPlatformSpecificTooling(); await macos.ensureReadyForPlatformSpecificTooling();
} }
if (featureFlags.isWindowsEnabled && windows.existsSync()) {
await windows.ensureReadyForPlatformSpecificTooling();
}
if (featureFlags.isWebEnabled && web.existsSync()) { if (featureFlags.isWebEnabled && web.existsSync()) {
await web.ensureReadyForPlatformSpecificTooling(); await web.ensureReadyForPlatformSpecificTooling();
} }
...@@ -790,6 +797,8 @@ class WindowsProject { ...@@ -790,6 +797,8 @@ class WindowsProject {
/// ///
/// Ideally this will be replaced in the future with inspection of the project. /// Ideally this will be replaced in the future with inspection of the project.
File get nameFile => ephemeralDirectory.childFile('exe_filename'); File get nameFile => ephemeralDirectory.childFile('exe_filename');
Future<void> ensureReadyForPlatformSpecificTooling() async {}
} }
/// The Linux sub project. /// The Linux sub project.
...@@ -818,6 +827,8 @@ class LinuxProject { ...@@ -818,6 +827,8 @@ class LinuxProject {
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the build. /// the build.
File get generatedMakeConfigFile => ephemeralDirectory.childFile('generated_config.mk'); File get generatedMakeConfigFile => ephemeralDirectory.childFile('generated_config.mk');
Future<void> ensureReadyForPlatformSpecificTooling() async {}
} }
/// The Fuchisa sub project /// The Fuchisa sub project
......
...@@ -76,12 +76,26 @@ ...@@ -76,12 +76,26 @@
"pluginClass": {"type": "string"} "pluginClass": {"type": "string"}
} }
}, },
"linux": {
"type": "object",
"additionalProperties": false,
"properties": {
"pluginClass": {"type": "string"}
}
},
"macos": { "macos": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"pluginClass": {"type": "string"} "pluginClass": {"type": "string"}
} }
},
"windows": {
"type": "object",
"additionalProperties": false,
"properties": {
"pluginClass": {"type": "string"}
}
} }
} }
}, },
......
...@@ -36,16 +36,20 @@ void main() { ...@@ -36,16 +36,20 @@ void main() {
test('Multi-platform Format', () { test('Multi-platform Format', () {
const String pluginYamlRaw = 'platforms:\n' const String pluginYamlRaw = 'platforms:\n'
' macos:\n'
' pluginClass: MSamplePlugin\n'
' android:\n' ' android:\n'
' package: com.flutter.dev\n' ' package: com.flutter.dev\n'
' pluginClass: ASamplePlugin\n' ' pluginClass: ASamplePlugin\n'
' ios:\n' ' ios:\n'
' pluginClass: ISamplePlugin\n' ' pluginClass: ISamplePlugin\n'
' linux:\n'
' pluginClass: LSamplePlugin\n'
' macos:\n'
' pluginClass: MSamplePlugin\n'
' web:\n' ' web:\n'
' pluginClass: WSamplePlugin\n' ' pluginClass: WebSamplePlugin\n'
' fileName: web_plugin.dart\n'; ' fileName: web_plugin.dart\n'
' windows:\n'
' pluginClass: WinSamplePlugin\n';
final dynamic pluginYaml = loadYaml(pluginYamlRaw); final dynamic pluginYaml = loadYaml(pluginYamlRaw);
final Plugin plugin = final Plugin plugin =
...@@ -53,10 +57,14 @@ void main() { ...@@ -53,10 +57,14 @@ void main() {
final AndroidPlugin androidPlugin = final AndroidPlugin androidPlugin =
plugin.platforms[AndroidPlugin.kConfigKey]; plugin.platforms[AndroidPlugin.kConfigKey];
final IOSPlugin iosPlugin = plugin.platforms[IOSPlugin.kConfigKey];
final LinuxPlugin linuxPlugin =
plugin.platforms[LinuxPlugin.kConfigKey];
final MacOSPlugin macOSPlugin = final MacOSPlugin macOSPlugin =
plugin.platforms[MacOSPlugin.kConfigKey]; plugin.platforms[MacOSPlugin.kConfigKey];
final IOSPlugin iosPlugin = plugin.platforms[IOSPlugin.kConfigKey];
final WebPlugin webPlugin = plugin.platforms[WebPlugin.kConfigKey]; final WebPlugin webPlugin = plugin.platforms[WebPlugin.kConfigKey];
final WindowsPlugin windowsPlugin =
plugin.platforms[WindowsPlugin.kConfigKey];
final String androidPluginClass = androidPlugin.pluginClass; final String androidPluginClass = androidPlugin.pluginClass;
final String iosPluginClass = iosPlugin.pluginClass; final String iosPluginClass = iosPlugin.pluginClass;
...@@ -64,9 +72,11 @@ void main() { ...@@ -64,9 +72,11 @@ void main() {
expect(androidPluginClass, 'ASamplePlugin'); expect(androidPluginClass, 'ASamplePlugin');
expect(iosPlugin.classPrefix, ''); expect(iosPlugin.classPrefix, '');
expect(androidPlugin.package, 'com.flutter.dev'); expect(androidPlugin.package, 'com.flutter.dev');
expect(linuxPlugin.pluginClass, 'LSamplePlugin');
expect(macOSPlugin.pluginClass, 'MSamplePlugin'); expect(macOSPlugin.pluginClass, 'MSamplePlugin');
expect(webPlugin.pluginClass, 'WSamplePlugin'); expect(webPlugin.pluginClass, 'WebSamplePlugin');
expect(webPlugin.fileName, 'web_plugin.dart'); expect(webPlugin.fileName, 'web_plugin.dart');
expect(windowsPlugin.pluginClass, 'WinSamplePlugin');
}); });
}); });
} }
...@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/context.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
...@@ -188,6 +189,48 @@ void main() { ...@@ -188,6 +189,48 @@ void main() {
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); expectExists(project.android.hostAppGradleRoot.childFile('local.properties'));
}); });
testUsingContext('injects plugins for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.macos.managedDirectory.childFile('GeneratedPluginRegistrant.swift'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(),
});
testUsingContext('generates Xcode configuration for macOS', () async {
final FlutterProject project = await someProject();
project.macos.managedDirectory.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.macos.generatedXcodePropertiesFile);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(),
});
testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject();
project.linux.managedDirectory.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(),
});
testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject();
project.windows.managedDirectory.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc'));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
FlutterProjectFactory: () => FlutterProjectFactory(),
});
testInMemory('creates Android library in module', () async { testInMemory('creates Android library in module', () async {
final FlutterProject project = await aModuleProject(); final FlutterProject project = await aModuleProject();
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
...@@ -512,8 +555,12 @@ flutter: ...@@ -512,8 +555,12 @@ flutter:
pluginClass: MyPlugin pluginClass: MyPlugin
ios: ios:
pluginClass: MyPlugin pluginClass: MyPlugin
linux:
pluginClass: MyPlugin
macos: macos:
pluginClass: MyPlugin pluginClass: MyPlugin
windows:
pluginClass: MyPlugin
'''; ''';
} }
directory.childFile('pubspec.yaml').writeAsStringSync(pluginPubSpec); directory.childFile('pubspec.yaml').writeAsStringSync(pluginPubSpec);
......
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