Unverified Commit 65c6e882 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Revert "FFI plugins (#94101)" (#96122)

This reverts commit 5257f029.
parent 5257f029
......@@ -6,9 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
......@@ -17,8 +14,3 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
......@@ -48,12 +48,8 @@ class CreateCommand extends CreateBase {
flutterProjectTypeToString(FlutterProjectType.package): 'Generate a shareable Flutter project containing modular '
'Dart code.',
flutterProjectTypeToString(FlutterProjectType.plugin): 'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
'Linux, macOS, Windows, web, or any combination of these.',
flutterProjectTypeToString(FlutterProjectType.ffiPlugin):
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, Windows, or any combination of these.',
'in Dart code with a platform-specific implementation for Android, for iOS code, or '
'for both.',
flutterProjectTypeToString(FlutterProjectType.module): 'Generate a project to add a Flutter module to an '
'existing Android or iOS application.',
},
......@@ -163,17 +159,18 @@ class CreateCommand extends CreateBase {
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
if (argResults['template'] != null) {
template = stringToProjectType(stringArg('template'));
}
// If the project directory exists and isn't empty, then try to determine the template
// type from the project directory.
if (projectDir.existsSync() && projectDir.listSync().isNotEmpty) {
detectedProjectType = determineTemplateType();
if (detectedProjectType == null && metadataExists) {
// We can only be definitive that this is the wrong type if the .metadata file
// exists and contains a type that we don't understand, or doesn't contain a type.
throwToolExit('Sorry, unable to detect the type of project to recreate. '
'Try creating a fresh project and migrating your existing code to '
'the new project manually.');
} else {
// If the project directory exists and isn't empty, then try to determine the template
// type from the project directory.
if (projectDir.existsSync() && projectDir.listSync().isNotEmpty) {
detectedProjectType = determineTemplateType();
if (detectedProjectType == null && metadataExists) {
// We can only be definitive that this is the wrong type if the .metadata file
// exists and contains a type that we don't understand, or doesn't contain a type.
throwToolExit('Sorry, unable to detect the type of project to recreate. '
'Try creating a fresh project and migrating your existing code to '
'the new project manually.');
}
}
}
template ??= detectedProjectType ?? FlutterProjectType.app;
......@@ -209,8 +206,7 @@ class CreateCommand extends CreateBase {
final FlutterProjectType template = _getProjectType(projectDir);
final bool generateModule = template == FlutterProjectType.module;
final bool generateMethodChannelsPlugin = template == FlutterProjectType.plugin;
final bool generateFfiPlugin = template == FlutterProjectType.ffiPlugin;
final bool generatePlugin = template == FlutterProjectType.plugin;
final bool generatePackage = template == FlutterProjectType.package;
final List<String> platforms = stringsArg('platforms');
......@@ -224,23 +220,6 @@ class CreateCommand extends CreateBase {
} else if (platforms == null || platforms.isEmpty) {
throwToolExit('Must specify at least one platform using --platforms',
exitCode: 2);
} else if (generateFfiPlugin && argResults.wasParsed('platforms') && platforms.contains('web')) {
throwToolExit(
'The web platform is not supported in plugin_ffi template.',
exitCode: 2,
);
} else if (generateFfiPlugin && argResults.wasParsed('ios-language')) {
throwToolExit(
'The "ios-language" option is not supported with the plugin_ffi '
'template: the language will always be C or C++.',
exitCode: 2,
);
} else if (generateFfiPlugin && argResults.wasParsed('android-language')) {
throwToolExit(
'The "android-language" option is not supported with the plugin_ffi '
'template: the language will always be C or C++.',
exitCode: 2,
);
}
final String organization = await getOrganization();
......@@ -278,8 +257,7 @@ class CreateCommand extends CreateBase {
titleCaseProjectName: titleCaseProjectName,
projectDescription: stringArg('description'),
flutterRoot: flutterRoot,
withPlatformChannelPluginHook: generateMethodChannelsPlugin,
withFfiPluginHook: generateFfiPlugin,
withPluginHook: generatePlugin,
androidLanguage: stringArg('android-language'),
iosLanguage: stringArg('ios-language'),
iosDevelopmentTeam: developmentTeam,
......@@ -348,15 +326,7 @@ class CreateCommand extends CreateBase {
);
break;
case FlutterProjectType.plugin:
generatedFileCount += await _generateMethodChannelPlugin(
relativeDir,
templateContext,
overwrite: overwrite,
printStatusWhenWriting: !creatingNewProject,
);
break;
case FlutterProjectType.ffiPlugin:
generatedFileCount += await _generateFfiPlugin(
generatedFileCount += await _generatePlugin(
relativeDir,
templateContext,
overwrite: overwrite,
......@@ -384,7 +354,7 @@ class CreateCommand extends CreateBase {
'main.dart',
));
globals.printStatus('Your module code is in $relativeMainPath.');
} else if (generateMethodChannelsPlugin) {
} else if (generatePlugin) {
final String relativePluginPath = globals.fs.path.normalize(globals.fs.path.relative(projectDirPath));
final List<String> requestedPlatforms = _getUserRequestedPlatforms();
final String platformsString = requestedPlatforms.join(', ');
......@@ -490,7 +460,7 @@ Your $application code is in $relativeAppMain.
return generatedCount;
}
Future<int> _generateMethodChannelPlugin(
Future<int> _generatePlugin(
Directory directory,
Map<String, dynamic> templateContext, {
bool overwrite = false,
......@@ -517,8 +487,8 @@ Your $application code is in $relativeAppMain.
? stringArg('description')
: 'A new flutter plugin project.';
templateContext['description'] = description;
generatedCount += await renderMerged(
<String>['plugin', 'plugin_shared'],
generatedCount += await renderTemplate(
'plugin',
directory,
templateContext,
overwrite: overwrite,
......@@ -565,82 +535,6 @@ Your $application code is in $relativeAppMain.
return generatedCount;
}
Future<int> _generateFfiPlugin(
Directory directory,
Map<String, dynamic> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
}) async {
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults.wasParsed('platforms')) {
for (final String platform in kAllCreatePlatforms) {
templateContext[platform] = false;
}
}
final List<String> platformsToAdd =
_getSupportedPlatformsFromTemplateContext(templateContext);
final List<String> existingPlatforms =
_getSupportedPlatformsInPlugin(directory);
for (final String existingPlatform in existingPlatforms) {
// re-generate files for existing platforms
templateContext[existingPlatform] = true;
}
final bool willAddPlatforms = platformsToAdd.isNotEmpty;
templateContext['no_platforms'] = !willAddPlatforms;
int generatedCount = 0;
final String description = argResults.wasParsed('description')
? stringArg('description')
: 'A new Flutter FFI plugin project.';
templateContext['description'] = description;
generatedCount += await renderMerged(
<String>['plugin_ffi', 'plugin_shared'],
directory,
templateContext,
overwrite: overwrite,
printStatusWhenWriting: printStatusWhenWriting,
);
if (boolArg('pub')) {
await pub.get(
context: PubContext.createPlugin,
directory: directory.path,
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
}
final FlutterProject project = FlutterProject.fromDirectory(directory);
final bool generateAndroid = templateContext['android'] == true;
if (generateAndroid) {
gradle.updateLocalProperties(project: project, requireAndroidSdk: false);
}
final String projectName = templateContext['projectName'] as String;
final String organization = templateContext['organization'] as String;
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String;
final String exampleProjectName = '${projectName}_example';
templateContext['projectName'] = exampleProjectName;
templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName);
templateContext['iosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName);
templateContext['macosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName);
templateContext['windowsIdentifier'] = CreateBase.createWindowsIdentifier(organization, exampleProjectName);
templateContext['description'] = 'Demonstrates how to use the $projectName plugin.';
templateContext['pluginProjectName'] = projectName;
templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
generatedCount += await generateApp(
'app',
project.example.directory,
templateContext,
overwrite: overwrite,
pluginExampleApp: true,
printStatusWhenWriting: printStatusWhenWriting,
);
return generatedCount;
}
// Takes an application template and replaces the main.dart with one from the
// documentation website in sampleCode. Returns the difference in the number
// of files after applying the sample, since it also deletes the application's
......
......@@ -339,8 +339,7 @@ abstract class CreateBase extends FlutterCommand {
String agpVersion,
String kotlinVersion,
String gradleVersion,
bool withPlatformChannelPluginHook = false,
bool withFfiPluginHook = false,
bool withPluginHook = false,
bool ios = false,
bool android = false,
bool web = false,
......@@ -385,9 +384,7 @@ abstract class CreateBase extends FlutterCommand {
'pluginClassCapitalSnakeCase': pluginClassCapitalSnakeCase,
'pluginDartClass': pluginDartClass,
'pluginProjectUUID': const Uuid().v4().toUpperCase(),
'withFfiPluginHook': withFfiPluginHook,
'withPlatformChannelPluginHook': withPlatformChannelPluginHook,
'withPluginHook': withFfiPluginHook || withPlatformChannelPluginHook,
'withPluginHook': withPluginHook,
'androidLanguage': androidLanguage,
'iosLanguage': iosLanguage,
'hasIosDevelopmentTeam': iosDevelopmentTeam != null && iosDevelopmentTeam.isNotEmpty,
......
......@@ -259,9 +259,9 @@ const String _androidPluginRegistryTemplateOldEmbedding = '''
package io.flutter.plugins;
import io.flutter.plugin.common.PluginRegistry;
{{#methodChannelPlugins}}
{{#plugins}}
import {{package}}.{{class}};
{{/methodChannelPlugins}}
{{/plugins}}
/**
* Generated file. Do not edit.
......@@ -271,9 +271,9 @@ public final class GeneratedPluginRegistrant {
if (alreadyRegisteredWith(registry)) {
return;
}
{{#methodChannelPlugins}}
{{#plugins}}
{{class}}.registerWith(registry.registrarFor("{{package}}.{{class}}"));
{{/methodChannelPlugins}}
{{/plugins}}
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
......@@ -311,7 +311,7 @@ public final class GeneratedPluginRegistrant {
{{#needsShim}}
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
{{/needsShim}}
{{#methodChannelPlugins}}
{{#plugins}}
{{#supportsEmbeddingV2}}
try {
flutterEngine.getPlugins().add(new {{package}}.{{class}}());
......@@ -328,7 +328,7 @@ public final class GeneratedPluginRegistrant {
}
{{/supportsEmbeddingV1}}
{{/supportsEmbeddingV2}}
{{/methodChannelPlugins}}
{{/plugins}}
}
}
''';
......@@ -352,11 +352,12 @@ AndroidEmbeddingVersion _getAndroidEmbeddingVersion(FlutterProject project) {
}
Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, AndroidPlugin.kConfigKey);
final List<Map<String, Object?>> androidPlugins = _extractPlatformMaps(methodChannelPlugins, AndroidPlugin.kConfigKey);
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, AndroidPlugin.kConfigKey);
final List<Map<String, Object?>> androidPlugins =
_extractPlatformMaps(nativePlugins, AndroidPlugin.kConfigKey);
final Map<String, Object> templateContext = <String, Object>{
'methodChannelPlugins': androidPlugins,
'plugins': androidPlugins,
'androidX': isAppUsingAndroidX(project.android.hostAppGradleRoot),
};
final String javaSourcePath = globals.fs.path.join(
......@@ -473,20 +474,20 @@ const String _objcPluginRegistryImplementationTemplate = '''
#import "GeneratedPluginRegistrant.h"
{{#methodChannelPlugins}}
{{#plugins}}
#if __has_include(<{{name}}/{{class}}.h>)
#import <{{name}}/{{class}}.h>
#else
@import {{name}};
#endif
{{/methodChannelPlugins}}
{{/plugins}}
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
{{#methodChannelPlugins}}
{{#plugins}}
[{{prefix}}{{class}} registerWithRegistrar:[registry registrarForPlugin:@"{{prefix}}{{class}}"]];
{{/methodChannelPlugins}}
{{/plugins}}
}
@end
......@@ -500,14 +501,14 @@ const String _swiftPluginRegistryTemplate = '''
import {{framework}}
import Foundation
{{#methodChannelPlugins}}
{{#plugins}}
import {{name}}
{{/methodChannelPlugins}}
{{/plugins}}
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
{{#methodChannelPlugins}}
{{#plugins}}
{{class}}.register(with: registry.registrar(forPlugin: "{{class}}"))
{{/methodChannelPlugins}}
{{/plugins}}
}
''';
......@@ -533,9 +534,9 @@ Depends on all your plugins, and provides a function to register them.
s.static_framework = true
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.dependency '{{framework}}'
{{#methodChannelPlugins}}
{{#plugins}}
s.dependency '{{name}}'
{{/methodChannelPlugins}}
{{/plugins}}
end
''';
......@@ -547,17 +548,17 @@ const String _dartPluginRegistryTemplate = '''
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars
{{#methodChannelPlugins}}
{{#plugins}}
import 'package:{{name}}/{{file}}';
{{/methodChannelPlugins}}
{{/plugins}}
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
{{#methodChannelPlugins}}
{{#plugins}}
{{class}}.registerWith(registrar);
{{/methodChannelPlugins}}
{{/plugins}}
registrar.registerMessageHandler();
}
''';
......@@ -589,15 +590,15 @@ const String _cppPluginRegistryImplementationTemplate = '''
#include "generated_plugin_registrant.h"
{{#methodChannelPlugins}}
{{#plugins}}
#include <{{name}}/{{filename}}.h>
{{/methodChannelPlugins}}
{{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) {
{{#methodChannelPlugins}}
{{#plugins}}
{{class}}RegisterWithRegistrar(
registry->GetRegistrarForPlugin("{{class}}"));
{{/methodChannelPlugins}}
{{/plugins}}
}
''';
......@@ -628,16 +629,16 @@ const String _linuxPluginRegistryImplementationTemplate = '''
#include "generated_plugin_registrant.h"
{{#methodChannelPlugins}}
{{#plugins}}
#include <{{name}}/{{filename}}.h>
{{/methodChannelPlugins}}
{{/plugins}}
void fl_register_plugins(FlPluginRegistry* registry) {
{{#methodChannelPlugins}}
{{#plugins}}
g_autoptr(FlPluginRegistrar) {{name}}_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "{{class}}");
{{filename}}_register_with_registrar({{name}}_registrar);
{{/methodChannelPlugins}}
{{/plugins}}
}
''';
......@@ -647,15 +648,9 @@ const String _pluginCmakefileTemplate = r'''
#
list(APPEND FLUTTER_PLUGIN_LIST
{{#methodChannelPlugins}}
{{#plugins}}
{{name}}
{{/methodChannelPlugins}}
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
{{#ffiPlugins}}
{{name}}
{{/ffiPlugins}}
{{/plugins}}
)
set(PLUGIN_BUNDLED_LIBRARIES)
......@@ -666,11 +661,6 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory({{pluginsDir}}/${ffi_plugin}/{{os}} plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
''';
const String _dartPluginRegisterWith = r'''
......@@ -759,13 +749,13 @@ void main(List<String> args) {
''';
Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, IOSPlugin.kConfigKey);
final List<Map<String, Object?>> iosPlugins = _extractPlatformMaps(methodChannelPlugins, IOSPlugin.kConfigKey);
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, IOSPlugin.kConfigKey);
final List<Map<String, Object?>> iosPlugins = _extractPlatformMaps(nativePlugins, IOSPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'os': 'ios',
'deploymentTarget': '9.0',
'framework': 'Flutter',
'methodChannelPlugins': iosPlugins,
'plugins': iosPlugins,
};
if (project.isModule) {
final Directory registryDirectory = project.ios.pluginRegistrantHost;
......@@ -809,14 +799,11 @@ String _cmakeRelativePluginSymlinkDirectoryPath(CmakeBasedProject project) {
}
Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, LinuxPlugin.kConfigKey);
final List<Map<String, Object?>> linuxMethodChannelPlugins = _extractPlatformMaps(methodChannelPlugins, LinuxPlugin.kConfigKey);
final List<Plugin> ffiPlugins = _filterFfiPlugins(plugins, LinuxPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains);
final List<Map<String, Object?>> linuxFfiPlugins = _extractPlatformMaps(ffiPlugins, LinuxPlugin.kConfigKey);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, LinuxPlugin.kConfigKey);
final List<Map<String, Object?>> linuxPlugins = _extractPlatformMaps(nativePlugins, LinuxPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'os': 'linux',
'methodChannelPlugins': linuxMethodChannelPlugins,
'ffiPlugins': linuxFfiPlugins,
'plugins': linuxPlugins,
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.linux),
};
await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
......@@ -848,12 +835,12 @@ Future<void> _writePluginCmakefile(File destinationFile, Map<String, Object> tem
}
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, MacOSPlugin.kConfigKey);
final List<Map<String, Object?>> macosMethodChannelPlugins = _extractPlatformMaps(methodChannelPlugins, MacOSPlugin.kConfigKey);
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, MacOSPlugin.kConfigKey);
final List<Map<String, Object?>> macosPlugins = _extractPlatformMaps(nativePlugins, MacOSPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'os': 'macos',
'framework': 'FlutterMacOS',
'methodChannelPlugins': macosMethodChannelPlugins,
'plugins': macosPlugins,
};
_renderTemplateToFile(
_swiftPluginRegistryTemplate,
......@@ -863,15 +850,15 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl
);
}
/// Filters out any plugins that don't use method channels, and thus shouldn't be added to the native generated registrants.
List<Plugin> _filterMethodChannelPlugins(List<Plugin> plugins, String platformKey) {
/// 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).hasMethodChannel();
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.
......@@ -879,23 +866,6 @@ List<Plugin> _filterMethodChannelPlugins(List<Plugin> plugins, String platformKe
}).toList();
}
/// Filters out Dart-only and method channel plugins.
///
/// FFI Plugins do not need to be registered, but their binaries need to be bundled.
List<Plugin> _filterFfiPlugins(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) {
final NativeOrDartPlugin plugin_ = plugin as NativeOrDartPlugin;
return plugin_.hasFfi();
}
return false;
}).toList();
}
/// Returns only the plugins with the given platform variant.
List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, PluginPlatformVariant variant) {
return plugins.where((Plugin element) {
......@@ -911,15 +881,12 @@ List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, P
@visibleForTesting
Future<void> writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins, TemplateRenderer templateRenderer) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, WindowsPlugin.kConfigKey);
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);
final List<Map<String, Object?>> windowsFfiPlugins = _extractPlatformMaps(ffiPlugins, WindowsPlugin.kConfigKey);
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
final List<Plugin> win32Plugins = _filterPluginsByVariant(nativePlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.win32);
final List<Map<String, Object?>> pluginInfo = _extractPlatformMaps(win32Plugins, WindowsPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'os': 'windows',
'methodChannelPlugins': windowsMethodChannelPlugins,
'ffiPlugins': windowsFfiPlugins,
'plugins': pluginInfo,
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windows),
};
await _writeCppPluginRegistrant(project.windows.managedDirectory, context, templateRenderer);
......@@ -930,15 +897,12 @@ Future<void> writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugin
/// filtering, for the purposes of tooling support and initial UWP bootstrap.
@visibleForTesting
Future<void> writeWindowsUwpPluginFiles(FlutterProject project, List<Plugin> plugins, TemplateRenderer templateRenderer) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, WindowsPlugin.kConfigKey);
final List<Plugin> uwpPlugins = _filterPluginsByVariant(methodChannelPlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.winuwp);
final List<Map<String, Object?>> windowsMethodChannelPlugins = _extractPlatformMaps(uwpPlugins, WindowsPlugin.kConfigKey);
final List<Plugin> ffiPlugins = _filterFfiPlugins(plugins, WindowsPlugin.kConfigKey)..removeWhere(methodChannelPlugins.contains);
final List<Map<String, Object?>> windowsFfiPlugins = _extractPlatformMaps(ffiPlugins, WindowsPlugin.kConfigKey);
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
final List<Plugin> uwpPlugins = _filterPluginsByVariant(nativePlugins, WindowsPlugin.kConfigKey, PluginPlatformVariant.winuwp);
final List<Map<String, Object?>> pluginInfo = _extractPlatformMaps(uwpPlugins, WindowsPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'os': 'windows',
'methodChannelPlugins': windowsMethodChannelPlugins,
'ffiPlugins': windowsFfiPlugins,
'plugins': pluginInfo,
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windowsUwp),
};
await _writeCppPluginRegistrant(project.windowsUwp.managedDirectory, context, templateRenderer);
......@@ -963,7 +927,7 @@ Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, Object
Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, Object?>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
final Map<String, Object> context = <String, Object>{
'methodChannelPlugins': webPlugins,
'plugins': webPlugins,
};
final File pluginFile = project.web.libDirectory.childFile('generated_plugin_registrant.dart');
if (webPlugins.isEmpty) {
......
......@@ -23,14 +23,9 @@ enum FlutterProjectType {
package,
/// This is a native plugin project.
plugin,
/// This is an FFI native plugin project.
ffiPlugin,
}
String flutterProjectTypeToString(FlutterProjectType type) {
if (type == FlutterProjectType.ffiPlugin) {
return 'plugin_ffi';
}
return getEnumName(type);
}
......
......@@ -10,12 +10,9 @@ import 'base/file_system.dart';
/// Constant for 'pluginClass' key in plugin maps.
const String kPluginClass = 'pluginClass';
/// Constant for 'dartPluginClass' key in plugin maps.
/// Constant for 'pluginClass' key in plugin maps.
const String kDartPluginClass = 'dartPluginClass';
/// Constant for 'ffiPlugin' key in plugin maps.
const String kFfiPlugin = 'ffiPlugin';
// Constant for 'defaultPackage' key in plugin maps.
const String kDefaultPackage = 'default_package';
......@@ -45,14 +42,9 @@ abstract class VariantPlatformPlugin {
}
abstract class NativeOrDartPlugin {
/// Determines whether the plugin has a Dart implementation.
bool hasDart();
/// Determines whether the plugin has a FFI implementation.
bool hasFfi();
/// Determines whether the plugin has a method channel implementation.
bool hasMethodChannel();
/// 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.
......@@ -72,7 +64,6 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
this.package,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
required FileSystem fileSystem,
}) : _fileSystem = fileSystem;
......@@ -84,7 +75,6 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
package: yaml['package'] as String?,
pluginClass: yaml[kPluginClass] as String?,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
pluginPath: pluginPath,
fileSystem: fileSystem,
......@@ -94,22 +84,15 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
final FileSystem _fileSystem;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin == true;
@override
bool hasDart() => dartPluginClass != null;
bool isNative() => pluginClass != null;
static bool validate(YamlMap yaml) {
if (yaml == null) {
return false;
}
return (yaml['package'] is String && yaml[kPluginClass] is String) ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
return (yaml['package'] is String && yaml['pluginClass'] is String)||
yaml[kDartPluginClass] is String ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'android';
......@@ -126,9 +109,6 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
/// The Dart plugin main class defined in pubspec.yaml, if any.
final String? dartPluginClass;
/// Is FFI plugin defined in pubspec.yaml, if any.
final bool? ffiPlugin;
/// The default implementation package defined in pubspec.yaml, if any.
final String? defaultPackage;
......@@ -234,7 +214,6 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
required this.classPrefix,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
});
......@@ -245,7 +224,6 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
classPrefix: '',
pluginClass: yaml[kPluginClass] as String?,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
);
}
......@@ -255,9 +233,8 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
return false;
}
return yaml[kPluginClass] is String ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
yaml[kDartPluginClass] is String ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'ios';
......@@ -269,17 +246,10 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
final String classPrefix;
final String? pluginClass;
final String? dartPluginClass;
final bool? ffiPlugin;
final String? defaultPackage;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin == true;
@override
bool hasDart() => dartPluginClass != null;
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
......@@ -288,7 +258,6 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
'prefix': classPrefix,
if (pluginClass != null) 'class': pluginClass,
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
if (ffiPlugin != null) kFfiPlugin: ffiPlugin,
if (defaultPackage != null) kDefaultPackage : defaultPackage,
};
}
......@@ -296,15 +265,13 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
/// Contains the parameters to template a macOS plugin.
///
/// The [name] of the plugin is required. Either [dartPluginClass] or
/// [pluginClass] or [ffiPlugin] are required.
/// 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,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
});
......@@ -319,7 +286,6 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
name: name,
pluginClass: pluginClass,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
);
}
......@@ -329,9 +295,8 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
return false;
}
return yaml[kPluginClass] is String ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
yaml[kDartPluginClass] is String ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'macos';
......@@ -339,26 +304,18 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
final String name;
final String? pluginClass;
final String? dartPluginClass;
final bool? ffiPlugin;
final String? defaultPackage;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin == true;
@override
bool hasDart() => dartPluginClass != null;
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
if (pluginClass != null) 'class': pluginClass,
if (dartPluginClass != null) kDartPluginClass: dartPluginClass,
if (ffiPlugin != null) kFfiPlugin: ffiPlugin,
if (defaultPackage != null) kDefaultPackage: defaultPackage,
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
if (defaultPackage != null) kDefaultPackage : defaultPackage,
};
}
}
......@@ -367,13 +324,11 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
///
/// 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, VariantPlatformPlugin {
class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, VariantPlatformPlugin {
const WindowsPlugin({
required this.name,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
this.variants = const <PluginPlatformVariant>{},
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
......@@ -408,7 +363,6 @@ class WindowsPlugin extends PluginPlatform
name: name,
pluginClass: pluginClass,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
variants: variants,
);
......@@ -420,9 +374,8 @@ class WindowsPlugin extends PluginPlatform
}
return yaml[kPluginClass] is String ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
yaml[kDartPluginClass] is String ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'windows';
......@@ -430,7 +383,6 @@ class WindowsPlugin extends PluginPlatform
final String name;
final String? pluginClass;
final String? dartPluginClass;
final bool? ffiPlugin;
final String? defaultPackage;
final Set<PluginPlatformVariant> variants;
......@@ -438,13 +390,7 @@ class WindowsPlugin extends PluginPlatform
Set<PluginPlatformVariant> get supportedVariants => variants;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin == true;
@override
bool hasDart() => dartPluginClass != null;
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
......@@ -467,12 +413,8 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
required this.name,
this.pluginClass,
this.dartPluginClass,
this.ffiPlugin,
this.defaultPackage,
}) : assert(pluginClass != null ||
dartPluginClass != null ||
ffiPlugin == true ||
defaultPackage != null);
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
assert(validate(yaml));
......@@ -485,7 +427,6 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
name: name,
pluginClass: pluginClass,
dartPluginClass: yaml[kDartPluginClass] as String?,
ffiPlugin: yaml[kFfiPlugin] as bool?,
defaultPackage: yaml[kDefaultPackage] as String?,
);
}
......@@ -495,9 +436,8 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
return false;
}
return yaml[kPluginClass] is String ||
yaml[kDartPluginClass] is String ||
yaml[kFfiPlugin] == true ||
yaml[kDefaultPackage] is String;
yaml[kDartPluginClass] is String ||
yaml[kDefaultPackage] is String;
}
static const String kConfigKey = 'linux';
......@@ -505,17 +445,10 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
final String name;
final String? pluginClass;
final String? dartPluginClass;
final bool? ffiPlugin;
final String? defaultPackage;
@override
bool hasMethodChannel() => pluginClass != null;
@override
bool hasFfi() => ffiPlugin == true;
@override
bool hasDart() => dartPluginClass != null;
bool isNative() => pluginClass != null;
@override
Map<String, dynamic> toMap() {
......
......@@ -47,17 +47,12 @@ class Plugin {
/// package: io.flutter.plugins.sample
/// pluginClass: SamplePlugin
/// ios:
/// # A plugin implemented through method channels.
/// pluginClass: SamplePlugin
/// linux:
/// # A plugin implemented purely in Dart code.
/// dartPluginClass: SamplePlugin
/// pluginClass: SamplePlugin
/// macos:
/// # A plugin implemented `dart:ffi`.
/// ffiPlugin: true
/// pluginClass: SamplePlugin
/// windows:
/// # A plugin using platform-specific Dart and method channels.
/// dartPluginClass: SamplePlugin
/// pluginClass: SamplePlugin
factory Plugin.fromYaml(
String name,
......
This directory contains templates for `flutter create`.
The `*_shared` subdirectories provide files for multiple templates.
* `app_shared` for `app` and `skeleton`.
* `plugin_shared` for (method channel) `plugin` and `plugin_ffi`.
For example, there are two app templates: `app` (the counter app)
The `app_shared` subdirectory is special. It provides files for all app
templates (as opposed to plugin or module templates).
As of May 2021, there are two app templates: `app` (the counter app)
and `skeleton` (the more advanced list view/detail view app).
```plain
......
import 'package:flutter/material.dart';
{{#withPlatformChannelPluginHook}}
{{#withPluginHook}}
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart';
{{/withPlatformChannelPluginHook}}
{{#withFfiPluginHook}}
import 'dart:async';
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart' as {{pluginProjectName}};
{{/withFfiPluginHook}}
{{/withPluginHook}}
void main() {
runApp(const MyApp());
......@@ -126,7 +121,7 @@ class _MyHomePageState extends State<MyHomePage> {
}
}
{{/withPluginHook}}
{{#withPlatformChannelPluginHook}}
{{#withPluginHook}}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
......@@ -179,71 +174,4 @@ class _MyAppState extends State<MyApp> {
);
}
}
{{/withPlatformChannelPluginHook}}
{{#withFfiPluginHook}}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late int sumResult;
late Future<int> sumAsyncResult;
@override
void initState() {
super.initState();
sumResult = {{pluginProjectName}}.sum(1, 2);
sumAsyncResult = {{pluginProjectName}}.sumAsync(3, 4);
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Native Packages'),
),
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
children: [
const Text(
'This calls a native function through FFI that is shipped as source in the package. '
'The native code is built as part of the Flutter Runner build.',
style: textStyle,
textAlign: TextAlign.center,
),
spacerSmall,
Text(
'sum(1, 2) = $sumResult',
style: textStyle,
textAlign: TextAlign.center,
),
spacerSmall,
FutureBuilder<int>(
future: sumAsyncResult,
builder: (BuildContext context, AsyncSnapshot<int> value) {
final displayValue =
(value.hasData) ? value.data : 'loading';
return Text(
'await sumAsync(3, 4) = $displayValue',
style: textStyle,
textAlign: TextAlign.center,
);
},
),
],
),
),
),
),
);
}
}
{{/withFfiPluginHook}}
{{/withPluginHook}}
......@@ -4,7 +4,7 @@ description: {{description}}
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
{{^withPlatformChannelPluginHook}}
{{^withPluginHook}}
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
......@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
{{/withPlatformChannelPluginHook}}
{{/withPluginHook}}
environment:
sdk: {{dartSdkVersionBounds}}
......
......@@ -8,9 +8,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
{{^withFfiPluginHook}}
import 'package:{{projectName}}/main.dart';
{{/withFfiPluginHook}}
{{^withPluginHook}}
void main() {
......@@ -32,7 +30,7 @@ void main() {
});
}
{{/withPluginHook}}
{{#withPlatformChannelPluginHook}}
{{#withPluginHook}}
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
......@@ -48,4 +46,4 @@ void main() {
);
});
}
{{/withPlatformChannelPluginHook}}
{{/withPluginHook}}
......@@ -94,11 +94,11 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
......
import 'package:flutter/material.dart';
{{#withPlatformChannelPluginHook}}
{{#withPluginHook}}
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart';
{{/withPlatformChannelPluginHook}}
{{/withPluginHook}}
void main() => runApp(const MyApp());
{{^withPlatformChannelPluginHook}}
{{^withPluginHook}}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
......@@ -117,8 +117,8 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
}
{{/withPlatformChannelPluginHook}}
{{#withPlatformChannelPluginHook}}
{{/withPluginHook}}
{{#withPluginHook}}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
......@@ -171,4 +171,4 @@ class _MyAppState extends State<MyApp> {
);
}
}
{{/withPlatformChannelPluginHook}}
{{/withPluginHook}}
......@@ -8,9 +8,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
{{^withFfiPluginHook}}
import 'package:{{projectName}}/main.dart';
{{/withFfiPluginHook}}
{{^withPluginHook}}
void main() {
......@@ -32,7 +30,7 @@ void main() {
});
}
{{/withPluginHook}}
{{#withPlatformChannelPluginHook}}
{{#withPluginHook}}
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
......@@ -48,4 +46,4 @@ void main() {
);
});
}
{{/withPlatformChannelPluginHook}}
{{/withPluginHook}}
<component name="libraryTable">
<library name="Dart SDK">
<CLASSES>
<root url="file://{{{dartSdk}}}/lib/async" />
<root url="file://{{{dartSdk}}}/lib/collection" />
<root url="file://{{{dartSdk}}}/lib/convert" />
<root url="file://{{{dartSdk}}}/lib/core" />
<root url="file://{{{dartSdk}}}/lib/developer" />
<root url="file://{{{dartSdk}}}/lib/html" />
<root url="file://{{{dartSdk}}}/lib/io" />
<root url="file://{{{dartSdk}}}/lib/isolate" />
<root url="file://{{{dartSdk}}}/lib/math" />
<root url="file://{{{dartSdk}}}/lib/mirrors" />
<root url="file://{{{dartSdk}}}/lib/typed_data" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/{{projectName}}.iml" filepath="$PROJECT_DIR$/{{projectName}}.iml" />
<module fileurl="file://$PROJECT_DIR$/android/{{projectName}}_android.iml" filepath="$PROJECT_DIR$/android/{{projectName}}_android.iml" />
<module fileurl="file://$PROJECT_DIR$/example/android/{{projectName}}_example_android.iml" filepath="$PROJECT_DIR$/example/android/{{projectName}}_example_android.iml" />
</modules>
</component>
</project>
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="example/lib/main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/example/lib/main.dart" />
<method />
</configuration>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="{{projectName}}.dart" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/lib/{{projectName}}.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="main.dart" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="ToolWindowManager">
<editor active="true" />
<layout>
<window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
</layout>
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
</navigator>
<panes>
<pane id="ProjectPane">
<option name="show-excluded-files" value="false" />
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="dart.analysis.tool.window.force.activate" value="true" />
<property name="show.migrate.to.gradle.popup" value="false" />
</component>
</project>
......@@ -7,9 +7,4 @@ version:
revision: {{flutterRevision}}
channel: {{flutterChannel}}
{{#withFfiPluginHook}}
project_type: plugin_ffi
{{/withFfiPluginHook}}
{{#withPlatformChannelPluginHook}}
project_type: plugin
{{/withPlatformChannelPluginHook}}
......@@ -15,6 +15,6 @@ samples, guidance on mobile development, and a full API reference.
{{#no_platforms}}
The plugin project was generated without specifying the `--platforms` flag, no platforms are currently supported.
To add platforms, run `flutter create -t plugin --platforms <platforms> .` in this directory.
You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
directory. You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
{{/no_platforms}}
......@@ -18,7 +18,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
# List of absolute paths to libraries that should be bundled with the plugin.
# List of absolute paths to libraries that should be bundled with the plugin
set({{projectName}}_bundled_libraries
""
PARENT_SCOPE
......
......@@ -12,13 +12,6 @@ Pod::Spec.new do |s|
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
{{#withFfiPluginHook}}
# This will ensure the source files in Classes/ are included in the native
# builds of apps using this FFI plugin. Podspec does not support relative
# paths, so Classes contains a forwarder C file that relatively imports
# `../src/*` so that the C sources can be shared among all target platforms.
{{/withFfiPluginHook}}
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'FlutterMacOS'
......
......@@ -16,4 +16,4 @@
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
</component>
</module>
</module>
\ No newline at end of file
......@@ -16,9 +16,6 @@ dependencies:
{{/web}}
dev_dependencies:
{{#withFfiPluginHook}}
ffigen: ^4.1.2
{{/withFfiPluginHook}}
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
......@@ -29,52 +26,9 @@ dev_dependencies:
# The following section is specific to Flutter.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, ObjectiveC)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# The 'pluginClass' and Android 'package' identifiers should not ordinarily
# be modified. They are used by the tooling to maintain consistency when
# adding or updating assets for this project.
{{#withFfiPluginHook}}
#
# Please refer to README.md for a detailed explanation.
plugin:
platforms:
{{#no_platforms}}
# This FFI plugin project was generated without specifying any
# platforms with the `--platform` argument. If you see the `some_platform` map below, remove it and
# then add platforms following the instruction here:
# https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms
# -------------------
some_platform:
ffiPlugin: true
# -------------------
{{/no_platforms}}
{{#android}}
android:
ffiPlugin: true
{{/android}}
{{#ios}}
ios:
ffiPlugin: true
{{/ios}}
{{#linux}}
linux:
ffiPlugin: true
{{/linux}}
{{#macos}}
macos:
ffiPlugin: true
{{/macos}}
{{#windows}}
windows:
ffiPlugin: true
{{/windows}}
{{/withFfiPluginHook}}
{{#withPlatformChannelPluginHook}}
plugin:
platforms:
{{#no_platforms}}
......@@ -113,7 +67,6 @@ flutter:
pluginClass: {{pluginDartClass}}Web
fileName: {{projectName}}_web.dart
{{/web}}
{{/withPlatformChannelPluginHook}}
# To add assets to your plugin package, add an assets section, like this:
# assets:
......
......@@ -17,7 +17,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
# List of absolute paths to libraries that should be bundled with the plugin.
# List of absolute paths to libraries that should be bundled with the plugin
set({{projectName}}_bundled_libraries
""
PARENT_SCOPE
......
# {{projectName}}
{{description}}
## Getting Started
This project is a starting point for a Flutter
[FFI plugin](https://docs.flutter.dev/development/platform-integration/c-interop),
a specialized package that includes native code directly invoked with Dart FFI.
## Project stucture
This template uses the following structure:
* `src`: Contains the native source code, and a CmakeFile.txt file for building
that source code into a dynamic library
* `lib`: Contains the Dart code that defines the API of the plugin, and which
calls into the native code using `dart:ffi`
* platform folders (`android`, `ios`, `windows`, etc.): Contains the build files
for building and bundling the native code library with the platform application.
## Buidling and bundling native code
The `pubspec.yaml` specifies FFI plugins as follows.
```yaml
plugin:
platforms:
some_platform:
ffiPlugin: true
```
This configuration invokes the native build for the various target platforms
and bundles the binaries in Flutter applications using these FFI plugins.
A plugin can have both FFI and method channels:
```yaml
plugin:
platforms:
some_platform:
pluginClass: SomeName
ffiPlugin: true
```
The native build systems that are invoked by FFI (and method channel) plugins are:
* For Android: Gradle, which invokes the Android NDK for native builds.
* See the documentation in android/build.gradle.
* For iOS and MacOS: Xcode, via CocoaPods.
* See the documentation in ios/{{projectName}}.podspec.
* See the documentation in macos/{{projectName}}.podspec.
* For Linux and Windows: CMake.
* See the documentation in linux/CMakeLists.txt.
* See the documentation in windows/CMakeLists.txt.
## Binding to native code
To use the native code, bindings in Dart are needed.
To avoid writing these by hand, they are generated from the header file
(`src/{{projectName}}.h`) by `package:ffigen`.
Regenerate the bindings by running `flutter pub run ffigen --config ffigen.yaml`.
## Invoking native code
Very short-running native functions can be directly invoked from any isolate.
For example, see `sum` in `lib/{{projectName}}.dart`.
Longer-running functions should be invoked on a helper isolate to avoid
dropping frames in Flutter applications.
For example, see `sumAsync` in `lib/{{projectName}}.dart`.
## Flutter help
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
{{#no_platforms}}
The plugin project was generated without specifying the `--platforms` flag, so no platforms are currently supported.
To add platforms, run `flutter create -t plugin_ffi --platforms <platforms> .` in this directory.
You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
{{/no_platforms}}
// The Android Gradle Plugin builds the native code with the Android NDK.
group '{{androidIdentifier}}'
version '1.0'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
// The Android Gradle Plugin knows how to build native code with the NDK.
classpath 'com.android.tools.build:gradle:4.1.0'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 31
// Invoke the shared CMake build with the Android Gradle Plugin.
//
// We do _not_ specify the NDK and CMake version here. This way we will
// automatically get the default versions from the Android Gradle Plugin.
//
// Android Gradle Plugin 4.1.0: Android NDK 21.1.6352462.
// https://developer.android.com/studio/projects/install-ndk#default-ndk-per-agp
// CMake 3.10.2.
// https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
//
// You are strongly encouraged to not ship FFI plugins requiring newer
// versions of the NDK or CMake, because your clients will not be able to
// use them.
externalNativeBuild {
cmake {
path "../src/CMakeLists.txt"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 16
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API {{androidSdkVersion}} Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
</component>
</module>
# Run with `dart run ffigen --config ffigen.yaml`.
name: {{pluginDartClass}}Bindings
description: |
Bindings for `src/{{projectName}}.h`.
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
output: 'lib/{{projectName}}_bindings_generated.dart'
headers:
entry-points:
- 'src/{{projectName}}.h'
include-directives:
- 'src/{{projectName}}.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full
// Relative import to be able to reuse the C sources.
#include "../../src/{{projectName}}.c"
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint {{projectName}}.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = '{{projectName}}'
s.version = '0.0.1'
s.summary = '{{description}}'
s.description = <<-DESC
{{description}}
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
# This will ensure the source files in Classes/ are included in the native
# builds of apps using this FFI plugin. Podspec does not support relative
# paths, so Classes contains a forwarder C file that relatively imports
# `../src/*` so that the C sources can be shared among all target platforms.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '9.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end
{{#no_platforms}}
// You have generated a new plugin project without specifying the `--platforms`
// flag. An FFI plugin project that supports no platforms is generated.
// To add platforms, run `flutter create -t plugin_ffi --platforms <platforms> .`
// in this directory. You can also find a detailed instruction on how to
// add platforms in the `pubspec.yaml` at
// https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms.
{{/no_platforms}}
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import '{{projectName}}_bindings_generated.dart';
/// A very short-lived native function.
///
/// For very short-lived functions, it is fine to call them on the main isolate.
/// They will block the Dart execution while running the native function, so
/// only do this for native functions which are guaranteed to be short-lived.
int sum(int a, int b) => _bindings.sum(a, b);
/// A longer lived native function, which occupies the thread calling it.
///
/// Do not call these kind of native functions in the main isolate. They will
/// block Dart execution. This will cause dropped frames in Flutter applications.
/// Instead, call these native functions on a separate isolate.
///
/// Modify this to suit your own use case. Example use cases:
///
/// 1. Reuse a single isolate for various different kinds of requests.
/// 2. Use multiple helper isolates for parallel execution.
Future<int> sumAsync(int a, int b) async {
final SendPort helperIsolateSendPort = await _helperIsolateSendPort;
final int requestId = _nextSumRequestId++;
final _SumRequest request = _SumRequest(requestId, a, b);
final Completer<int> completer = Completer<int>();
_sumRequests[requestId] = completer;
helperIsolateSendPort.send(request);
return completer.future;
}
const String _libName = '{{projectName}}';
/// The dynamic library in which the symbols for [{{pluginDartClass}}Bindings] can be found.
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
/// The bindings to the native functions in [_dylib].
final {{pluginDartClass}}Bindings _bindings = {{pluginDartClass}}Bindings(_dylib);
/// A request to compute `sum`.
///
/// Typically sent from one isolate to another.
class _SumRequest {
final int id;
final int a;
final int b;
const _SumRequest(this.id, this.a, this.b);
}
/// A response with the result of `sum`.
///
/// Typically sent from one isolate to another.
class _SumResponse {
final int id;
final int result;
const _SumResponse(this.id, this.result);
}
/// Counter to identify [_SumRequest]s and [_SumResponse]s.
int _nextSumRequestId = 0;
/// Mapping from [_SumRequest] `id`s to the completers corresponding to the correct future of the pending request.
final Map<int, Completer<int>> _sumRequests = <int, Completer<int>>{};
/// The SendPort belonging to the helper isolate.
Future<SendPort> _helperIsolateSendPort = () async {
// The helper isolate is going to send us back a SendPort, which we want to
// wait for.
final Completer<SendPort> completer = Completer<SendPort>();
// Receive port on the main isolate to receive messages from the helper.
// We receive two types of messages:
// 1. A port to send messages on.
// 2. Responses to requests we sent.
final ReceivePort receivePort = ReceivePort()
..listen((dynamic data) {
if (data is SendPort) {
// The helper isolate sent us the port on which we can sent it requests.
completer.complete(data);
return;
}
if (data is _SumResponse) {
// The helper isolate sent us a response to a request we sent.
final Completer<int> completer = _sumRequests[data.id]!;
_sumRequests.remove(data.id);
completer.complete(data.result);
return;
}
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
});
// Start the helper isolate.
await Isolate.spawn((SendPort sendPort) async {
final ReceivePort helperReceivePort = ReceivePort()
..listen((dynamic data) {
// On the helper isolate listen to requests and respond to them.
if (data is _SumRequest) {
final int result = _bindings.sum_long_running(data.a, data.b);
final _SumResponse response = _SumResponse(data.id, result);
sendPort.send(response);
return;
}
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
});
// Send the the port to the main isolate on which we can receive requests.
sendPort.send(helperReceivePort.sendPort);
}, receivePort.sendPort);
// Wait until the helper isolate has sent us back the SendPort on which we
// can start sending requests.
return completer.future;
}();
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
import 'dart:ffi' as ffi;
/// Bindings for `src/{{projectName}}.h`.
///
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
///
class {{pluginDartClass}}Bindings {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
{{pluginDartClass}}Bindings(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;
/// The symbols are looked up with [lookup].
{{pluginDartClass}}Bindings.fromLookup(
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
lookup)
: _lookup = lookup;
/// A very short-lived native function.
///
/// For very short-lived functions, it is fine to call them on the main isolate.
/// They will block the Dart execution while running the native function, so
/// only do this for native functions which are guaranteed to be short-lived.
int sum(
int a,
int b,
) {
return _sum(
a,
b,
);
}
late final _sumPtr =
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>(
'sum');
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
/// A longer lived native function, which occupies the thread calling it.
///
/// Calling these kind of native functions in the main isolate will
/// block Dart execution and cause dropped frames in Flutter applications.
/// Consider calling such native functions from a separate isolate.
int sum_long_running(
int a,
int b,
) {
return _sum_long_running(
a,
b,
);
}
late final _sum_long_runningPtr =
_lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr)>>(
'sum_long_running');
late final _sum_long_running =
_sum_long_runningPtr.asFunction<int Function(int, int)>();
}
cmake_minimum_required(VERSION 3.10)
set(PROJECT_NAME "{{projectName}}")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
# List of absolute paths to libraries that should be bundled with the plugin.
set({{projectName}}_bundled_libraries
# Defined in ../src/CMakeLists.txt.
$<TARGET_FILE:{{projectName}}>
PARENT_SCOPE
)
// Relative import to be able to reuse the C sources.
#include "../../src/{{projectName}}.c"
# We use the CMake version connected to the version of the Android Gradle Plugin.
#
# Android Gradle Plugin 4.1.0: Android NDK 21.1.6352462.
# https://developer.android.com/studio/projects/install-ndk#default-ndk-per-agp
# CMake 3.10.2.
# https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
#
# You are strongly encouraged to not ship FFI plugins requiring newer versions
# of the NDK or CMake, because your clients will not be able to use them.
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project({{projectName}}_library VERSION 0.0.1 LANGUAGES C)
add_library(
{{projectName}}
SHARED
"{{projectName}}.c"
)
set_target_properties({{projectName}} PROPERTIES
PUBLIC_HEADER {{projectName}}.h
OUTPUT_NAME "{{projectName}}"
)
target_compile_definitions({{projectName}} PUBLIC DART_SHARED_LIB)
#include "{{projectName}}.h"
// A very short-lived native function.
//
// For very short-lived functions, it is fine to call them on the main isolate.
// They will block the Dart execution while running the native function, so
// only do this for native functions which are guaranteed to be short-lived.
MYLIB_EXPORT intptr_t sum(intptr_t a, intptr_t b) { return a + b; }
// A longer-lived native function, which occupies the thread calling it.
//
// Do not call these kind of native functions in the main isolate. They will
// block Dart execution. This will cause dropped frames in Flutter applications.
// Instead, call these native functions on a separate isolate.
MYLIB_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b) {
// Simulate work.
#if _WIN32
Sleep(5000);
#else
usleep(5000 * 1000);
#endif
return a + b;
}
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#if _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <unistd.h>
#endif
#if _WIN32
#define MYLIB_EXPORT __declspec(dllexport)
#else
#define MYLIB_EXPORT
#endif
// A very short-lived native function.
//
// For very short-lived functions, it is fine to call them on the main isolate.
// They will block the Dart execution while running the native function, so
// only do this for native functions which are guaranteed to be short-lived.
MYLIB_EXPORT intptr_t sum(intptr_t a, intptr_t b);
// A longer lived native function, which occupies the thread calling it.
//
// Do not call these kind of native functions in the main isolate. They will
// block Dart execution. This will cause dropped frames in Flutter applications.
// Instead, call these native functions on a separate isolate.
MYLIB_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b);
cmake_minimum_required(VERSION 3.14)
set(PROJECT_NAME "{{projectName}}")
project(${PROJECT_NAME} LANGUAGES CXX)
# Invoke the build for native code shared with the other target platforms.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
# List of absolute paths to libraries that should be bundled with the plugin.
set({{projectName}}_bundled_libraries
# Defined in ../src/CMakeLists.txt.
$<TARGET_FILE:{{projectName}}>
PARENT_SCOPE
)
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{androidIdentifier}}">
</manifest>
......@@ -304,6 +304,13 @@
"templates/package/README.md.tmpl",
"templates/package/test/projectName_test.dart.tmpl",
"templates/plugin/.gitignore.tmpl",
"templates/plugin/.idea/libraries/Dart_SDK.xml.tmpl",
"templates/plugin/.idea/modules.xml.tmpl",
"templates/plugin/.idea/runConfigurations/example_lib_main_dart.xml.tmpl",
"templates/plugin/.idea/workspace.xml.tmpl",
"templates/plugin/.metadata.tmpl",
"templates/plugin/analysis_options.yaml.tmpl",
"templates/plugin/android-java.tmpl/build.gradle.tmpl",
"templates/plugin/android-java.tmpl/projectName_android.iml.tmpl",
"templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl",
......@@ -315,6 +322,7 @@
"templates/plugin/android.tmpl/gradle.properties.tmpl",
"templates/plugin/android.tmpl/settings.gradle.tmpl",
"templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl",
"templates/plugin/CHANGELOG.md.tmpl",
"templates/plugin/ios-objc.tmpl/Classes/pluginClass.h.tmpl",
"templates/plugin/ios-objc.tmpl/Classes/pluginClass.m.tmpl",
"templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl",
......@@ -325,51 +333,22 @@
"templates/plugin/ios.tmpl/.gitignore",
"templates/plugin/ios.tmpl/Assets/.gitkeep",
"templates/plugin/lib/projectName.dart.tmpl",
"templates/plugin/LICENSE.tmpl",
"templates/plugin/linux.tmpl/CMakeLists.txt.tmpl",
"templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
"templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl",
"templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl",
"templates/plugin/macos.tmpl/projectName.podspec.tmpl",
"templates/plugin/projectName.iml.tmpl",
"templates/plugin/pubspec.yaml.tmpl",
"templates/plugin/README.md.tmpl",
"templates/plugin/test/projectName_test.dart.tmpl",
"templates/plugin/windows.tmpl/.gitignore",
"templates/plugin/windows.tmpl/CMakeLists.txt.tmpl",
"templates/plugin/windows.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
"templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl",
"templates/plugin/lib/projectName_web.dart.tmpl",
"templates/plugin_ffi/android.tmpl/build.gradle.tmpl",
"templates/plugin_ffi/android.tmpl/projectName_android.iml.tmpl",
"templates/plugin_ffi/ffigen.yaml.tmpl",
"templates/plugin_ffi/ios.tmpl/.gitignore",
"templates/plugin_ffi/ios.tmpl/Classes/projectName.c.tmpl",
"templates/plugin_ffi/ios.tmpl/projectName.podspec.tmpl",
"templates/plugin_ffi/lib/projectName_bindings_generated.dart.tmpl",
"templates/plugin_ffi/lib/projectName.dart.tmpl",
"templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl",
"templates/plugin_ffi/linux.tmpl/include/projectName.tmpl/plugin_ffiClassSnakeCase.h.tmpl",
"templates/plugin_ffi/macos.tmpl/Classes/projectName.c.tmpl",
"templates/plugin_ffi/README.md.tmpl",
"templates/plugin_ffi/src.tmpl/CMakeLists.txt.tmpl",
"templates/plugin_ffi/src.tmpl/projectName.c.tmpl",
"templates/plugin_ffi/src.tmpl/projectName.h.tmpl",
"templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl",
"templates/plugin_shared/.gitignore.tmpl",
"templates/plugin_shared/.idea/libraries/Dart_SDK.xml.tmpl",
"templates/plugin_shared/.idea/modules.xml.tmpl",
"templates/plugin_shared/.idea/runConfigurations/example_lib_main_dart.xml.tmpl",
"templates/plugin_shared/.idea/workspace.xml.tmpl",
"templates/plugin_shared/.metadata.tmpl",
"templates/plugin_shared/analysis_options.yaml.tmpl",
"templates/plugin_shared/android.tmpl/.gitignore",
"templates/plugin_shared/android.tmpl/settings.gradle.tmpl",
"templates/plugin_shared/android.tmpl/src/main/AndroidManifest.xml.tmpl",
"templates/plugin_shared/CHANGELOG.md.tmpl",
"templates/plugin_shared/LICENSE.tmpl",
"templates/plugin_shared/macos.tmpl/projectName.podspec.tmpl",
"templates/plugin_shared/projectName.iml.tmpl",
"templates/plugin_shared/pubspec.yaml.tmpl",
"templates/plugin_shared/windows.tmpl/.gitignore",
"templates/skeleton/assets/images/2.0x/flutter_logo.png.img.tmpl",
"templates/skeleton/assets/images/3.0x/flutter_logo.png.img.tmpl",
"templates/skeleton/assets/images/flutter_logo.png.img.tmpl",
......
......@@ -48,8 +48,6 @@ void main() {
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'module', 'common'),
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'package'),
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin'),
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin_ffi'),
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin_shared'),
];
for (final String templatePath in templatePaths) {
globals.fs.directory(templatePath).createSync(recursive: true);
......@@ -98,9 +96,6 @@ void main() {
await runner.run(<String>['create', '--no-pub', '--template=plugin', 'testy']);
expect((await command.usageValues).commandCreateProjectType, 'plugin');
await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', 'testy']);
expect((await command.usageValues).commandCreateProjectType, 'plugin_ffi');
}));
testUsingContext('set iOS host language type as usage value', () => testbed.run(() async {
......
......@@ -2761,71 +2761,6 @@ void main() {
platform: globals.platform,
),
});
testUsingContext('create an FFI plugin with ios, then add macos', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platform=ios', projectDir.path]);
expect(projectDir.childDirectory('src'), exists);
expect(projectDir.childDirectory('ios'), exists);
expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
validatePubspecForPlugin(
projectDir: projectDir.absolute.path,
expectedPlatforms: const <String>[
'ios',
],
ffiPlugin: true,
unexpectedPlatforms: <String>['some_platform'],
);
await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platform=macos', projectDir.path]);
expect(projectDir.childDirectory('macos'), exists);
expect(
projectDir.childDirectory('example').childDirectory('macos'), exists);
expect(projectDir.childDirectory('ios'), exists);
expect(projectDir.childDirectory('example').childDirectory('ios'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('FFI plugins error android and ios language', () async {
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
final List<String> args = <String>[
'create',
'--no-pub',
'--template=plugin_ffi',
'-a',
'kotlin',
'--ios-language',
'swift',
'--platforms=ios,android',
projectDir.path,
];
await expectLater(
runner.run(args),
throwsToolExit(
message:
'The "ios-language" option is not supported with the plugin_ffi template: the language will always be C or C++.'));
});
testUsingContext('should show warning when disabled platforms are selected while creating an FFI plugin', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', '--platforms=android,ios,windows,macos,linux', projectDir.path]);
await runner.run(<String>['create', '--no-pub', '--template=plugin_ffi', projectDir.path]);
expect(logger.statusText, contains(_kDisabledPlatformRequestedMessage));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(),
Logger: () => logger,
});
}
Future<void> _createProject(
......
......@@ -9,43 +9,33 @@ import '../src/common.dart';
import 'test_utils.dart';
void main() {
late Directory tempDirPluginMethodChannels;
late Directory tempDirPluginFfi;
late Directory tempDir;
setUp(() async {
tempDirPluginMethodChannels = createResolvedTempDirectorySync('flutter_plugin_test.');
tempDirPluginFfi =
createResolvedTempDirectorySync('flutter_ffi_plugin_test.');
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
});
tearDown(() async {
tryToDelete(tempDirPluginMethodChannels);
tryToDelete(tempDirPluginFfi);
tryToDelete(tempDir);
});
Future<void> testPlugin({
required String template,
required Directory tempDir,
}) async {
test('plugin example can be built using current Flutter Gradle plugin', () async {
final String flutterBin = fileSystem.path.join(
getFlutterRoot(),
'bin',
'flutter',
);
final String testName = '${template}_test';
processManager.runSync(<String>[
flutterBin,
...getLocalEngineArguments(),
'create',
'--template=$template',
'--template=plugin',
'--platforms=android',
testName,
'plugin_test',
], workingDirectory: tempDir.path);
final Directory exampleAppDir =
tempDir.childDirectory(testName).childDirectory('example');
final Directory exampleAppDir = tempDir.childDirectory('plugin_test').childDirectory('example');
final File buildGradleFile = exampleAppDir.childDirectory('android').childFile('build.gradle');
expect(buildGradleFile, exists);
......@@ -78,11 +68,6 @@ void main() {
));
expect(exampleApk, exists);
if (template == 'plugin_ffi') {
// Does not support AGP 3.3.0.
return;
}
// Clean
processManager.runSync(<String>[
flutterBin,
......@@ -116,21 +101,5 @@ android.enableR8=true''');
'--target-platform=android-arm',
], workingDirectory: exampleAppDir.path);
expect(exampleApk, exists);
}
test('plugin example can be built using current Flutter Gradle plugin',
() async {
await testPlugin(
template: 'plugin',
tempDir: tempDirPluginMethodChannels,
);
});
test('FFI plugin example can be built using current Flutter Gradle plugin',
() async {
await testPlugin(
template: 'plugin_ffi',
tempDir: tempDirPluginFfi,
);
});
}
......@@ -11,23 +11,19 @@ import 'common.dart';
/// Check if the pubspec.yaml file under the `projectDir` is valid for a plugin project.
void validatePubspecForPlugin({
required String projectDir,
String? pluginClass,
bool ffiPlugin = false,
required String pluginClass,
required List<String> expectedPlatforms,
List<String> unexpectedPlatforms = const <String>[],
String? androidIdentifier,
String? webFileName,
}) {
assert(pluginClass != null || ffiPlugin);
final FlutterManifest manifest =
FlutterManifest.createFromPath('$projectDir/pubspec.yaml', fileSystem: globals.fs, logger: globals.logger)!;
final YamlMap platformMaps = YamlMap.wrap(manifest.supportedPlatforms!);
for (final String platform in expectedPlatforms) {
expect(platformMaps[platform], isNotNull);
final YamlMap platformMap = platformMaps[platform]! as YamlMap;
if (pluginClass != null) {
expect(platformMap['pluginClass'], pluginClass);
}
expect(platformMap['pluginClass'], pluginClass);
if (platform == 'android') {
expect(platformMap['package'], androidIdentifier);
}
......
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