mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
FFI plugins (#96225)
This commit is contained in:
parent
f15dd780d4
commit
0e2f51dfd0
@ -18,5 +18,7 @@ Future<void> main() async {
|
||||
<String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}),
|
||||
// Test that Dart-only plugins are supported.
|
||||
PluginTest('apk', <String>['--platforms=android'], dartOnlyPlugin: true),
|
||||
// Test that FFI plugins are supported.
|
||||
PluginTest('apk', <String>['--platforms=android'], template: 'plugin_ffi'),
|
||||
]));
|
||||
}
|
||||
|
@ -13,5 +13,8 @@ Future<void> main() async {
|
||||
// Test that Dart-only plugins are supported.
|
||||
PluginTest('ios', <String>['--platforms=ios'], dartOnlyPlugin: true),
|
||||
PluginTest('macos', <String>['--platforms=macos'], dartOnlyPlugin: true),
|
||||
// Test that FFI plugins are supported.
|
||||
PluginTest('ios', <String>['--platforms=ios'], template: 'plugin_ffi'),
|
||||
PluginTest('macos', <String>['--platforms=macos'], template: 'plugin_ffi'),
|
||||
]));
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class PluginTest {
|
||||
this.pluginCreateEnvironment,
|
||||
this.appCreateEnvironment,
|
||||
this.dartOnlyPlugin = false,
|
||||
this.template = 'plugin',
|
||||
});
|
||||
|
||||
final String buildTarget;
|
||||
@ -39,20 +40,27 @@ class PluginTest {
|
||||
final Map<String, String>? pluginCreateEnvironment;
|
||||
final Map<String, String>? appCreateEnvironment;
|
||||
final bool dartOnlyPlugin;
|
||||
final String template;
|
||||
|
||||
Future<TaskResult> call() async {
|
||||
final Directory tempDir =
|
||||
Directory.systemTemp.createTempSync('flutter_devicelab_plugin_test.');
|
||||
// FFI plugins do not have support for `flutter test`.
|
||||
// `flutter test` does not do a native build.
|
||||
// Supporting `flutter test` would require invoking a native build.
|
||||
final bool runFlutterTest = template != 'plugin_ffi';
|
||||
try {
|
||||
section('Create plugin');
|
||||
final _FlutterProject plugin = await _FlutterProject.create(
|
||||
tempDir, options, buildTarget,
|
||||
name: 'plugintest', template: 'plugin', environment: pluginCreateEnvironment);
|
||||
name: 'plugintest', template: template, environment: pluginCreateEnvironment);
|
||||
if (dartOnlyPlugin) {
|
||||
await plugin.convertDefaultPluginToDartPlugin();
|
||||
}
|
||||
section('Test plugin');
|
||||
await plugin.test();
|
||||
if (runFlutterTest) {
|
||||
await plugin.test();
|
||||
}
|
||||
section('Create Flutter app');
|
||||
final _FlutterProject app = await _FlutterProject.create(tempDir, options, buildTarget,
|
||||
name: 'plugintestapp', template: 'app', environment: appCreateEnvironment);
|
||||
@ -63,8 +71,10 @@ class PluginTest {
|
||||
await app.addPlugin('path_provider');
|
||||
section('Build app');
|
||||
await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin);
|
||||
section('Test app');
|
||||
await app.test();
|
||||
if (runFlutterTest) {
|
||||
section('Test app');
|
||||
await app.test();
|
||||
}
|
||||
} finally {
|
||||
await plugin.delete();
|
||||
await app.delete();
|
||||
|
@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
@ -14,3 +17,8 @@ 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)
|
||||
|
@ -38,6 +38,12 @@ class FlutterExtension {
|
||||
/** Sets the targetSdkVersion used by default in Flutter app projects. */
|
||||
static int targetSdkVersion = 31
|
||||
|
||||
/**
|
||||
* Sets the ndkVersion used by default in Flutter app projects.
|
||||
* Chosen as default version of the AGP version below.
|
||||
*/
|
||||
static String ndkVersion = "21.1.6352462"
|
||||
|
||||
/**
|
||||
* Specifies the relative directory to the Flutter project directory.
|
||||
* In an app project, this is ../.. since the app's build.gradle is under android/app.
|
||||
@ -54,6 +60,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
/* When bumping, also update ndkVersion above. */
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
}
|
||||
}
|
||||
@ -409,11 +416,45 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints error message and fix for any plugin compileSdkVersion that are higher than the project. */
|
||||
private void detectLowCompileSdkVersion() {
|
||||
/**
|
||||
* Compares semantic versions ignoring labels.
|
||||
*
|
||||
* If the versions are equal (ignoring labels), returns one of the two strings arbitrarily.
|
||||
*
|
||||
* If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero.
|
||||
* If the provided versions in both are equal, the longest version string is returned.
|
||||
* For example, "2.8.0" vs "2.8" will always consider "2.8.0" to be the most recent version.
|
||||
*/
|
||||
static String mostRecentSemanticVersion(String version1, String version2) {
|
||||
List version1Tokenized = version1.tokenize('.')
|
||||
List version2Tokenized = version2.tokenize('.')
|
||||
def version1numTokens = version1Tokenized.size()
|
||||
def version2numTokens = version2Tokenized.size()
|
||||
def minNumTokens = Math.min(version1numTokens, version2numTokens)
|
||||
for (int i = 0; i < minNumTokens; i++) {
|
||||
def num1 = version1Tokenized[i].toInteger()
|
||||
def num2 = version2Tokenized[i].toInteger()
|
||||
if (num1 > num2) {
|
||||
return version1
|
||||
}
|
||||
if (num2 > num1) {
|
||||
return version2
|
||||
}
|
||||
}
|
||||
if (version1numTokens > version2numTokens) {
|
||||
return version1
|
||||
}
|
||||
return version2
|
||||
}
|
||||
|
||||
/** Prints error message and fix for any plugin compileSdkVersion or ndkVersion that are higher than the project. */
|
||||
private void detectLowCompileSdkVersionOrNdkVersion() {
|
||||
project.afterEvaluate {
|
||||
int projectCompileSdkVersion = project.android.compileSdkVersion.substring(8) as int
|
||||
int maxPluginCompileSdkVersion = projectCompileSdkVersion
|
||||
String ndkVersionIfUnspecified = "21.1.6352462" /* The default for AGP 4.1.0 used in old templates. */
|
||||
String projectNdkVersion = project.android.ndkVersion ?: ndkVersionIfUnspecified
|
||||
String maxPluginNdkVersion = projectNdkVersion
|
||||
int numProcessedPlugins = getPluginList().size()
|
||||
|
||||
getPluginList().each { plugin ->
|
||||
@ -421,12 +462,17 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
pluginProject.afterEvaluate {
|
||||
int pluginCompileSdkVersion = pluginProject.android.compileSdkVersion.substring(8) as int
|
||||
maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion)
|
||||
String pluginNdkVersion = pluginProject.android.ndkVersion ?: ndkVersionIfUnspecified
|
||||
maxPluginNdkVersion = mostRecentSemanticVersion(pluginNdkVersion, maxPluginNdkVersion)
|
||||
|
||||
numProcessedPlugins--
|
||||
if (numProcessedPlugins == 0) {
|
||||
if (maxPluginCompileSdkVersion > projectCompileSdkVersion) {
|
||||
project.logger.error("One or more plugins require a higher Android SDK version.\nFix this issue by adding the following to ${project.projectDir}${File.separator}build.gradle:\nandroid {\n compileSdkVersion ${maxPluginCompileSdkVersion}\n ...\n}\n")
|
||||
}
|
||||
if (maxPluginNdkVersion != projectNdkVersion) {
|
||||
project.logger.error("One or more plugins require a higher Android NDK version.\nFix this issue by adding the following to ${project.projectDir}${File.separator}build.gradle:\nandroid {\n ndkVersion ${maxPluginNdkVersion}\n ...\n}\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -963,7 +1009,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
configurePlugins()
|
||||
detectLowCompileSdkVersion()
|
||||
detectLowCompileSdkVersionOrNdkVersion()
|
||||
return
|
||||
}
|
||||
// Flutter host module project (Add-to-app).
|
||||
@ -1015,7 +1061,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
configurePlugins()
|
||||
detectLowCompileSdkVersion()
|
||||
detectLowCompileSdkVersionOrNdkVersion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,8 +48,12 @@ 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 for Android, for iOS code, or '
|
||||
'for both.',
|
||||
'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.',
|
||||
flutterProjectTypeToString(FlutterProjectType.module): 'Generate a project to add a Flutter module to an '
|
||||
'existing Android or iOS application.',
|
||||
},
|
||||
@ -159,18 +163,17 @@ class CreateCommand extends CreateBase {
|
||||
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
|
||||
if (argResults['template'] != null) {
|
||||
template = stringToProjectType(stringArg('template'));
|
||||
} 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.');
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
@ -206,7 +209,8 @@ class CreateCommand extends CreateBase {
|
||||
|
||||
final FlutterProjectType template = _getProjectType(projectDir);
|
||||
final bool generateModule = template == FlutterProjectType.module;
|
||||
final bool generatePlugin = template == FlutterProjectType.plugin;
|
||||
final bool generateMethodChannelsPlugin = template == FlutterProjectType.plugin;
|
||||
final bool generateFfiPlugin = template == FlutterProjectType.ffiPlugin;
|
||||
final bool generatePackage = template == FlutterProjectType.package;
|
||||
|
||||
final List<String> platforms = stringsArg('platforms');
|
||||
@ -220,6 +224,23 @@ 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();
|
||||
@ -257,7 +278,8 @@ class CreateCommand extends CreateBase {
|
||||
titleCaseProjectName: titleCaseProjectName,
|
||||
projectDescription: stringArg('description'),
|
||||
flutterRoot: flutterRoot,
|
||||
withPluginHook: generatePlugin,
|
||||
withPlatformChannelPluginHook: generateMethodChannelsPlugin,
|
||||
withFfiPluginHook: generateFfiPlugin,
|
||||
androidLanguage: stringArg('android-language'),
|
||||
iosLanguage: stringArg('ios-language'),
|
||||
iosDevelopmentTeam: developmentTeam,
|
||||
@ -293,7 +315,7 @@ class CreateCommand extends CreateBase {
|
||||
switch (template) {
|
||||
case FlutterProjectType.app:
|
||||
generatedFileCount += await generateApp(
|
||||
'app',
|
||||
<String>['app', 'app_test_widget'],
|
||||
relativeDir,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
@ -302,7 +324,7 @@ class CreateCommand extends CreateBase {
|
||||
break;
|
||||
case FlutterProjectType.skeleton:
|
||||
generatedFileCount += await generateApp(
|
||||
'skeleton',
|
||||
<String>['skeleton'],
|
||||
relativeDir,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
@ -326,7 +348,15 @@ class CreateCommand extends CreateBase {
|
||||
);
|
||||
break;
|
||||
case FlutterProjectType.plugin:
|
||||
generatedFileCount += await _generatePlugin(
|
||||
generatedFileCount += await _generateMethodChannelPlugin(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
printStatusWhenWriting: !creatingNewProject,
|
||||
);
|
||||
break;
|
||||
case FlutterProjectType.ffiPlugin:
|
||||
generatedFileCount += await _generateFfiPlugin(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
@ -354,7 +384,7 @@ class CreateCommand extends CreateBase {
|
||||
'main.dart',
|
||||
));
|
||||
globals.printStatus('Your module code is in $relativeMainPath.');
|
||||
} else if (generatePlugin) {
|
||||
} else if (generateMethodChannelsPlugin) {
|
||||
final String relativePluginPath = globals.fs.path.normalize(globals.fs.path.relative(projectDirPath));
|
||||
final List<String> requestedPlatforms = _getUserRequestedPlatforms();
|
||||
final String platformsString = requestedPlatforms.join(', ');
|
||||
@ -460,7 +490,7 @@ Your $application code is in $relativeAppMain.
|
||||
return generatedCount;
|
||||
}
|
||||
|
||||
Future<int> _generatePlugin(
|
||||
Future<int> _generateMethodChannelPlugin(
|
||||
Directory directory,
|
||||
Map<String, dynamic> templateContext, {
|
||||
bool overwrite = false,
|
||||
@ -487,8 +517,8 @@ Your $application code is in $relativeAppMain.
|
||||
? stringArg('description')
|
||||
: 'A new flutter plugin project.';
|
||||
templateContext['description'] = description;
|
||||
generatedCount += await renderTemplate(
|
||||
'plugin',
|
||||
generatedCount += await renderMerged(
|
||||
<String>['plugin', 'plugin_shared'],
|
||||
directory,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
@ -525,7 +555,83 @@ Your $application code is in $relativeAppMain.
|
||||
templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
|
||||
|
||||
generatedCount += await generateApp(
|
||||
'app',
|
||||
<String>['app', 'app_test_widget'],
|
||||
project.example.directory,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
pluginExampleApp: true,
|
||||
printStatusWhenWriting: printStatusWhenWriting,
|
||||
);
|
||||
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(
|
||||
<String>['app'],
|
||||
project.example.directory,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
|
@ -5,6 +5,7 @@
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../android/android.dart' as android_common;
|
||||
@ -110,7 +111,7 @@ abstract class CreateBase extends FlutterCommand {
|
||||
abbr: 'i',
|
||||
defaultsTo: 'swift',
|
||||
allowed: <String>['objc', 'swift'],
|
||||
help: 'The language to use for iOS-specific code, either ObjectiveC (legacy) or Swift (recommended).'
|
||||
help: 'The language to use for iOS-specific code, either Objective-C (legacy) or Swift (recommended).'
|
||||
);
|
||||
argParser.addOption(
|
||||
'android-language',
|
||||
@ -339,7 +340,8 @@ abstract class CreateBase extends FlutterCommand {
|
||||
String agpVersion,
|
||||
String kotlinVersion,
|
||||
String gradleVersion,
|
||||
bool withPluginHook = false,
|
||||
bool withPlatformChannelPluginHook = false,
|
||||
bool withFfiPluginHook = false,
|
||||
bool ios = false,
|
||||
bool android = false,
|
||||
bool web = false,
|
||||
@ -366,6 +368,12 @@ abstract class CreateBase extends FlutterCommand {
|
||||
// https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid
|
||||
final String linuxIdentifier = androidIdentifier;
|
||||
|
||||
// TODO(dacoharkes): Replace with hardcoded version in template when Flutter 2.11 is released.
|
||||
final Version ffiPluginStableRelease = Version(2, 11, 0);
|
||||
final String minFrameworkVersionFfiPlugin = Version.parse(globals.flutterVersion.frameworkVersion) < ffiPluginStableRelease
|
||||
? globals.flutterVersion.frameworkVersion
|
||||
: ffiPluginStableRelease.toString();
|
||||
|
||||
return <String, Object>{
|
||||
'organization': organization,
|
||||
'projectName': projectName,
|
||||
@ -384,13 +392,16 @@ abstract class CreateBase extends FlutterCommand {
|
||||
'pluginClassCapitalSnakeCase': pluginClassCapitalSnakeCase,
|
||||
'pluginDartClass': pluginDartClass,
|
||||
'pluginProjectUUID': const Uuid().v4().toUpperCase(),
|
||||
'withPluginHook': withPluginHook,
|
||||
'withFfiPluginHook': withFfiPluginHook,
|
||||
'withPlatformChannelPluginHook': withPlatformChannelPluginHook,
|
||||
'withPluginHook': withFfiPluginHook || withPlatformChannelPluginHook,
|
||||
'androidLanguage': androidLanguage,
|
||||
'iosLanguage': iosLanguage,
|
||||
'hasIosDevelopmentTeam': iosDevelopmentTeam != null && iosDevelopmentTeam.isNotEmpty,
|
||||
'iosDevelopmentTeam': iosDevelopmentTeam ?? '',
|
||||
'flutterRevision': globals.flutterVersion.frameworkRevision,
|
||||
'flutterChannel': globals.flutterVersion.channel,
|
||||
'minFrameworkVersionFfiPlugin': minFrameworkVersionFfiPlugin,
|
||||
'ios': ios,
|
||||
'android': android,
|
||||
'web': web,
|
||||
@ -468,7 +479,7 @@ abstract class CreateBase extends FlutterCommand {
|
||||
/// If `overwrite` is true, overwrites existing files, `overwrite` defaults to `false`.
|
||||
@protected
|
||||
Future<int> generateApp(
|
||||
String templateName,
|
||||
List<String> templateNames,
|
||||
Directory directory,
|
||||
Map<String, Object> templateContext, {
|
||||
bool overwrite = false,
|
||||
@ -477,7 +488,7 @@ abstract class CreateBase extends FlutterCommand {
|
||||
}) async {
|
||||
int generatedCount = 0;
|
||||
generatedCount += await renderMerged(
|
||||
<String>[templateName, 'app_shared'],
|
||||
<String>[...templateNames, 'app_shared'],
|
||||
directory,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
|
@ -120,7 +120,7 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String
|
||||
_kFlutterPluginsNameKey: plugin.name,
|
||||
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
|
||||
if (platformPlugin is NativeOrDartPlugin)
|
||||
_kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).isNative(),
|
||||
_kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).hasMethodChannel() || (platformPlugin as NativeOrDartPlugin).hasFfi(),
|
||||
_kFlutterPluginsDependenciesKey: <String>[...plugin.dependencies.where(pluginNames.contains)],
|
||||
});
|
||||
}
|
||||
@ -270,9 +270,9 @@ const String _androidPluginRegistryTemplateOldEmbedding = '''
|
||||
package io.flutter.plugins;
|
||||
|
||||
import io.flutter.plugin.common.PluginRegistry;
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
import {{package}}.{{class}};
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
|
||||
/**
|
||||
* Generated file. Do not edit.
|
||||
@ -282,9 +282,9 @@ public final class GeneratedPluginRegistrant {
|
||||
if (alreadyRegisteredWith(registry)) {
|
||||
return;
|
||||
}
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{class}}.registerWith(registry.registrarFor("{{package}}.{{class}}"));
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
|
||||
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
|
||||
@ -322,7 +322,7 @@ public final class GeneratedPluginRegistrant {
|
||||
{{#needsShim}}
|
||||
ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
|
||||
{{/needsShim}}
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{#supportsEmbeddingV2}}
|
||||
try {
|
||||
flutterEngine.getPlugins().add(new {{package}}.{{class}}());
|
||||
@ -339,7 +339,7 @@ public final class GeneratedPluginRegistrant {
|
||||
}
|
||||
{{/supportsEmbeddingV1}}
|
||||
{{/supportsEmbeddingV2}}
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
}
|
||||
''';
|
||||
@ -363,12 +363,11 @@ AndroidEmbeddingVersion _getAndroidEmbeddingVersion(FlutterProject project) {
|
||||
}
|
||||
|
||||
Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
|
||||
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, AndroidPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> androidPlugins =
|
||||
_extractPlatformMaps(nativePlugins, AndroidPlugin.kConfigKey);
|
||||
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, AndroidPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> androidPlugins = _extractPlatformMaps(methodChannelPlugins, AndroidPlugin.kConfigKey);
|
||||
|
||||
final Map<String, Object> templateContext = <String, Object>{
|
||||
'plugins': androidPlugins,
|
||||
'methodChannelPlugins': androidPlugins,
|
||||
'androidX': isAppUsingAndroidX(project.android.hostAppGradleRoot),
|
||||
};
|
||||
final String javaSourcePath = globals.fs.path.join(
|
||||
@ -485,20 +484,20 @@ const String _objcPluginRegistryImplementationTemplate = '''
|
||||
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
#if __has_include(<{{name}}/{{class}}.h>)
|
||||
#import <{{name}}/{{class}}.h>
|
||||
#else
|
||||
@import {{name}};
|
||||
#endif
|
||||
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
@implementation GeneratedPluginRegistrant
|
||||
|
||||
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
[{{prefix}}{{class}} registerWithRegistrar:[registry registrarForPlugin:@"{{prefix}}{{class}}"]];
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -512,14 +511,14 @@ const String _swiftPluginRegistryTemplate = '''
|
||||
import {{framework}}
|
||||
import Foundation
|
||||
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
import {{name}}
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{class}}.register(with: registry.registrar(forPlugin: "{{class}}"))
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
''';
|
||||
|
||||
@ -545,9 +544,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}}'
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
s.dependency '{{name}}'
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
end
|
||||
''';
|
||||
|
||||
@ -559,17 +558,17 @@ const String _dartPluginRegistryTemplate = '''
|
||||
// ignore_for_file: directives_ordering
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
import 'package:{{name}}/{{file}}';
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
void registerPlugins(Registrar registrar) {
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{class}}.registerWith(registrar);
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
registrar.registerMessageHandler();
|
||||
}
|
||||
''';
|
||||
@ -601,15 +600,15 @@ const String _cppPluginRegistryImplementationTemplate = '''
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
#include <{{name}}/{{filename}}.h>
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{class}}RegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("{{class}}"));
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
''';
|
||||
|
||||
@ -640,16 +639,16 @@ const String _linuxPluginRegistryImplementationTemplate = '''
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
#include <{{name}}/{{filename}}.h>
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
g_autoptr(FlPluginRegistrar) {{name}}_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "{{class}}");
|
||||
{{filename}}_register_with_registrar({{name}}_registrar);
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
}
|
||||
''';
|
||||
|
||||
@ -659,9 +658,15 @@ const String _pluginCmakefileTemplate = r'''
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
{{#plugins}}
|
||||
{{#methodChannelPlugins}}
|
||||
{{name}}
|
||||
{{/plugins}}
|
||||
{{/methodChannelPlugins}}
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
{{#ffiPlugins}}
|
||||
{{name}}
|
||||
{{/ffiPlugins}}
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
@ -672,6 +677,11 @@ 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'''
|
||||
@ -760,13 +770,13 @@ void main(List<String> args) {
|
||||
''';
|
||||
|
||||
Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
|
||||
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, IOSPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> iosPlugins = _extractPlatformMaps(nativePlugins, IOSPlugin.kConfigKey);
|
||||
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, IOSPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> iosPlugins = _extractPlatformMaps(methodChannelPlugins, IOSPlugin.kConfigKey);
|
||||
final Map<String, Object> context = <String, Object>{
|
||||
'os': 'ios',
|
||||
'deploymentTarget': '9.0',
|
||||
'framework': 'Flutter',
|
||||
'plugins': iosPlugins,
|
||||
'methodChannelPlugins': iosPlugins,
|
||||
};
|
||||
if (project.isModule) {
|
||||
final Directory registryDirectory = project.ios.pluginRegistrantHost;
|
||||
@ -810,11 +820,14 @@ String _cmakeRelativePluginSymlinkDirectoryPath(CmakeBasedProject project) {
|
||||
}
|
||||
|
||||
Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
|
||||
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, LinuxPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> linuxPlugins = _extractPlatformMaps(nativePlugins, LinuxPlugin.kConfigKey);
|
||||
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 Map<String, Object> context = <String, Object>{
|
||||
'os': 'linux',
|
||||
'plugins': linuxPlugins,
|
||||
'methodChannelPlugins': linuxMethodChannelPlugins,
|
||||
'ffiPlugins': linuxFfiPlugins,
|
||||
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.linux),
|
||||
};
|
||||
await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
|
||||
@ -846,12 +859,12 @@ Future<void> _writePluginCmakefile(File destinationFile, Map<String, Object> tem
|
||||
}
|
||||
|
||||
Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
|
||||
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, MacOSPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> macosPlugins = _extractPlatformMaps(nativePlugins, MacOSPlugin.kConfigKey);
|
||||
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(plugins, MacOSPlugin.kConfigKey);
|
||||
final List<Map<String, Object?>> macosMethodChannelPlugins = _extractPlatformMaps(methodChannelPlugins, MacOSPlugin.kConfigKey);
|
||||
final Map<String, Object> context = <String, Object>{
|
||||
'os': 'macos',
|
||||
'framework': 'FlutterMacOS',
|
||||
'plugins': macosPlugins,
|
||||
'methodChannelPlugins': macosMethodChannelPlugins,
|
||||
};
|
||||
_renderTemplateToFile(
|
||||
_swiftPluginRegistryTemplate,
|
||||
@ -861,15 +874,15 @@ Future<void> _writeMacOSPluginRegistrant(FlutterProject project, List<Plugin> pl
|
||||
);
|
||||
}
|
||||
|
||||
/// Filters out Dart-only plugins, which shouldn't be added to the native generated registrants.
|
||||
List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
|
||||
/// 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) {
|
||||
return plugins.where((Plugin element) {
|
||||
final PluginPlatform? plugin = element.platforms[platformKey];
|
||||
if (plugin == null) {
|
||||
return false;
|
||||
}
|
||||
if (plugin is NativeOrDartPlugin) {
|
||||
return (plugin as NativeOrDartPlugin).isNative();
|
||||
return (plugin as NativeOrDartPlugin).hasMethodChannel();
|
||||
}
|
||||
// Not all platforms have the ability to create Dart-only plugins. Therefore, any plugin that doesn't
|
||||
// implement NativeOrDartPlugin is always native.
|
||||
@ -877,6 +890,23 @@ List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Filters out Dart-only and method channel plugins.
|
||||
///
|
||||
/// FFI plugins do not need native code registration, 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) {
|
||||
@ -892,12 +922,15 @@ List<Plugin> _filterPluginsByVariant(List<Plugin> plugins, String platformKey, P
|
||||
|
||||
@visibleForTesting
|
||||
Future<void> writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins, TemplateRenderer templateRenderer) async {
|
||||
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 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 Map<String, Object> context = <String, Object>{
|
||||
'os': 'windows',
|
||||
'plugins': pluginInfo,
|
||||
'methodChannelPlugins': windowsMethodChannelPlugins,
|
||||
'ffiPlugins': windowsFfiPlugins,
|
||||
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windows),
|
||||
};
|
||||
await _writeCppPluginRegistrant(project.windows.managedDirectory, context, templateRenderer);
|
||||
@ -908,12 +941,15 @@ 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> 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 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 Map<String, Object> context = <String, Object>{
|
||||
'os': 'windows',
|
||||
'plugins': pluginInfo,
|
||||
'methodChannelPlugins': windowsMethodChannelPlugins,
|
||||
'ffiPlugins': windowsFfiPlugins,
|
||||
'pluginsDir': _cmakeRelativePluginSymlinkDirectoryPath(project.windowsUwp),
|
||||
};
|
||||
await _writeCppPluginRegistrant(project.windowsUwp.managedDirectory, context, templateRenderer);
|
||||
@ -938,7 +974,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>{
|
||||
'plugins': webPlugins,
|
||||
'methodChannelPlugins': webPlugins,
|
||||
};
|
||||
final File pluginFile = project.web.libDirectory.childFile('generated_plugin_registrant.dart');
|
||||
if (webPlugins.isEmpty) {
|
||||
|
@ -23,9 +23,14 @@ 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,9 +10,12 @@ import 'base/file_system.dart';
|
||||
/// Constant for 'pluginClass' key in plugin maps.
|
||||
const String kPluginClass = 'pluginClass';
|
||||
|
||||
/// Constant for 'pluginClass' key in plugin maps.
|
||||
/// Constant for 'dartPluginClass' 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';
|
||||
|
||||
@ -42,9 +45,14 @@ abstract class VariantPlatformPlugin {
|
||||
}
|
||||
|
||||
abstract class NativeOrDartPlugin {
|
||||
/// Determines whether the plugin has a native implementation or if it's a
|
||||
/// Dart-only plugin.
|
||||
bool isNative();
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/// Contains parameters to template an Android plugin.
|
||||
@ -64,9 +72,11 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
this.package,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
bool? ffiPlugin,
|
||||
this.defaultPackage,
|
||||
required FileSystem fileSystem,
|
||||
}) : _fileSystem = fileSystem;
|
||||
}) : _fileSystem = fileSystem,
|
||||
ffiPlugin = ffiPlugin ?? false;
|
||||
|
||||
factory AndroidPlugin.fromYaml(String name, YamlMap yaml, String pluginPath, FileSystem fileSystem) {
|
||||
assert(validate(yaml));
|
||||
@ -75,6 +85,7 @@ 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,
|
||||
@ -84,15 +95,22 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
final FileSystem _fileSystem;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
bool hasMethodChannel() => pluginClass != null;
|
||||
|
||||
@override
|
||||
bool hasFfi() => ffiPlugin;
|
||||
|
||||
@override
|
||||
bool hasDart() => dartPluginClass != null;
|
||||
|
||||
static bool validate(YamlMap yaml) {
|
||||
if (yaml == null) {
|
||||
return false;
|
||||
}
|
||||
return (yaml['package'] is String && yaml['pluginClass'] is String)||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
return (yaml['package'] is String && yaml[kPluginClass] is String) ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kFfiPlugin] == true ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'android';
|
||||
@ -109,6 +127,9 @@ 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.
|
||||
final bool ffiPlugin;
|
||||
|
||||
/// The default implementation package defined in pubspec.yaml, if any.
|
||||
final String? defaultPackage;
|
||||
|
||||
@ -122,6 +143,7 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
if (package != null) 'package': package,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
|
||||
if (ffiPlugin) kFfiPlugin: true,
|
||||
if (defaultPackage != null) kDefaultPackage : defaultPackage,
|
||||
// Mustache doesn't support complex types.
|
||||
'supportsEmbeddingV1': _supportedEmbeddings.contains('1'),
|
||||
@ -214,8 +236,9 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
required this.classPrefix,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
bool? ffiPlugin,
|
||||
this.defaultPackage,
|
||||
});
|
||||
}) : ffiPlugin = ffiPlugin ?? false;
|
||||
|
||||
factory IOSPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml)); // TODO(zanderso): https://github.com/flutter/flutter/issues/67241
|
||||
@ -224,6 +247,7 @@ 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?,
|
||||
);
|
||||
}
|
||||
@ -233,8 +257,9 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
return false;
|
||||
}
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kFfiPlugin] == true ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'ios';
|
||||
@ -246,10 +271,17 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
final String classPrefix;
|
||||
final String? pluginClass;
|
||||
final String? dartPluginClass;
|
||||
final bool ffiPlugin;
|
||||
final String? defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
bool hasMethodChannel() => pluginClass != null;
|
||||
|
||||
@override
|
||||
bool hasFfi() => ffiPlugin;
|
||||
|
||||
@override
|
||||
bool hasDart() => dartPluginClass != null;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
@ -258,6 +290,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
'prefix': classPrefix,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
|
||||
if (ffiPlugin) kFfiPlugin: true,
|
||||
if (defaultPackage != null) kDefaultPackage : defaultPackage,
|
||||
};
|
||||
}
|
||||
@ -265,15 +298,17 @@ 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] are required.
|
||||
/// The [name] of the plugin is required. Either [dartPluginClass] or
|
||||
/// [pluginClass] or [ffiPlugin] 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,
|
||||
bool? ffiPlugin,
|
||||
this.defaultPackage,
|
||||
});
|
||||
}) : ffiPlugin = ffiPlugin ?? false;
|
||||
|
||||
factory MacOSPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml));
|
||||
@ -286,6 +321,7 @@ 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?,
|
||||
);
|
||||
}
|
||||
@ -295,8 +331,9 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
return false;
|
||||
}
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kFfiPlugin] == true ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'macos';
|
||||
@ -304,18 +341,26 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
final String name;
|
||||
final String? pluginClass;
|
||||
final String? dartPluginClass;
|
||||
final bool ffiPlugin;
|
||||
final String? defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
bool hasMethodChannel() => pluginClass != null;
|
||||
|
||||
@override
|
||||
bool hasFfi() => ffiPlugin;
|
||||
|
||||
@override
|
||||
bool hasDart() => dartPluginClass != null;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'name': name,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
|
||||
if (defaultPackage != null) kDefaultPackage : defaultPackage,
|
||||
if (dartPluginClass != null) kDartPluginClass: dartPluginClass,
|
||||
if (ffiPlugin) kFfiPlugin: true,
|
||||
if (defaultPackage != null) kDefaultPackage: defaultPackage,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -324,14 +369,17 @@ 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,
|
||||
bool? ffiPlugin,
|
||||
this.defaultPackage,
|
||||
this.variants = const <PluginPlatformVariant>{},
|
||||
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
|
||||
}) : ffiPlugin = ffiPlugin ?? false,
|
||||
assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
|
||||
|
||||
factory WindowsPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml));
|
||||
@ -363,6 +411,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, Varian
|
||||
name: name,
|
||||
pluginClass: pluginClass,
|
||||
dartPluginClass: yaml[kDartPluginClass] as String?,
|
||||
ffiPlugin: yaml[kFfiPlugin] as bool?,
|
||||
defaultPackage: yaml[kDefaultPackage] as String?,
|
||||
variants: variants,
|
||||
);
|
||||
@ -374,8 +423,9 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, Varian
|
||||
}
|
||||
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kFfiPlugin] == true ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'windows';
|
||||
@ -383,6 +433,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, Varian
|
||||
final String name;
|
||||
final String? pluginClass;
|
||||
final String? dartPluginClass;
|
||||
final bool ffiPlugin;
|
||||
final String? defaultPackage;
|
||||
final Set<PluginPlatformVariant> variants;
|
||||
|
||||
@ -390,7 +441,13 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, Varian
|
||||
Set<PluginPlatformVariant> get supportedVariants => variants;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
bool hasMethodChannel() => pluginClass != null;
|
||||
|
||||
@override
|
||||
bool hasFfi() => ffiPlugin;
|
||||
|
||||
@override
|
||||
bool hasDart() => dartPluginClass != null;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
@ -399,6 +456,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin, Varian
|
||||
if (pluginClass != null) 'class': pluginClass!,
|
||||
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass!),
|
||||
if (dartPluginClass != null) kDartPluginClass: dartPluginClass!,
|
||||
if (ffiPlugin) kFfiPlugin: true,
|
||||
if (defaultPackage != null) kDefaultPackage: defaultPackage!,
|
||||
};
|
||||
}
|
||||
@ -413,8 +471,10 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
required this.name,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
bool? ffiPlugin,
|
||||
this.defaultPackage,
|
||||
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
|
||||
}) : ffiPlugin = ffiPlugin ?? false,
|
||||
assert(pluginClass != null || dartPluginClass != null || ffiPlugin == true || defaultPackage != null);
|
||||
|
||||
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml));
|
||||
@ -427,6 +487,7 @@ 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?,
|
||||
);
|
||||
}
|
||||
@ -436,8 +497,9 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
return false;
|
||||
}
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kFfiPlugin] == true ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'linux';
|
||||
@ -445,10 +507,17 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
final String name;
|
||||
final String? pluginClass;
|
||||
final String? dartPluginClass;
|
||||
final bool ffiPlugin;
|
||||
final String? defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
bool hasMethodChannel() => pluginClass != null;
|
||||
|
||||
@override
|
||||
bool hasFfi() => ffiPlugin;
|
||||
|
||||
@override
|
||||
bool hasDart() => dartPluginClass != null;
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
@ -457,6 +526,7 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
||||
if (pluginClass != null) 'class': pluginClass!,
|
||||
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass!),
|
||||
if (dartPluginClass != null) kDartPluginClass: dartPluginClass!,
|
||||
if (ffiPlugin) kFfiPlugin: true,
|
||||
if (defaultPackage != null) kDefaultPackage: defaultPackage!,
|
||||
};
|
||||
}
|
||||
|
@ -49,12 +49,17 @@ class Plugin {
|
||||
/// package: io.flutter.plugins.sample
|
||||
/// pluginClass: SamplePlugin
|
||||
/// ios:
|
||||
/// # A plugin implemented through method channels.
|
||||
/// pluginClass: SamplePlugin
|
||||
/// linux:
|
||||
/// pluginClass: SamplePlugin
|
||||
/// # A plugin implemented purely in Dart code.
|
||||
/// dartPluginClass: SamplePlugin
|
||||
/// macos:
|
||||
/// pluginClass: SamplePlugin
|
||||
/// # A plugin implemented with `dart:ffi`.
|
||||
/// ffiPlugin: true
|
||||
/// windows:
|
||||
/// # A plugin using platform-specific Dart and method channels.
|
||||
/// dartPluginClass: SamplePlugin
|
||||
/// pluginClass: SamplePlugin
|
||||
factory Plugin.fromYaml(
|
||||
String name,
|
||||
|
@ -1,8 +1,11 @@
|
||||
This directory contains templates for `flutter create`.
|
||||
|
||||
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)
|
||||
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)
|
||||
and `skeleton` (the more advanced list view/detail view app).
|
||||
|
||||
```plain
|
||||
|
@ -1,10 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
{{#withPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart';
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
{{#withFfiPluginHook}}
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart' as {{pluginProjectName}};
|
||||
{{/withFfiPluginHook}}
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@ -121,7 +126,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
}
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{#withPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@ -174,4 +179,71 @@ class _MyAppState extends State<MyApp> {
|
||||
);
|
||||
}
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{/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}}
|
||||
|
@ -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
|
||||
{{^withPluginHook}}
|
||||
{{^withPlatformChannelPluginHook}}
|
||||
|
||||
# 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
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
||||
environment:
|
||||
sdk: {{dartSdkVersionBounds}}
|
||||
|
@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -116,11 +116,11 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||
install(FILES "${bundled_library}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
endforeach(bundled_library)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
|
@ -30,7 +30,7 @@ void main() {
|
||||
});
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{#withPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
@ -46,4 +46,4 @@ void main() {
|
||||
);
|
||||
});
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
@ -31,6 +31,7 @@ version '1.0'
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -1,14 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
{{#withPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:{{pluginProjectName}}/{{pluginProjectName}}.dart';
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
{{^withPluginHook}}
|
||||
{{^withPlatformChannelPluginHook}}
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@ -117,8 +117,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{#withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@ -171,4 +171,4 @@ class _MyAppState extends State<MyApp> {
|
||||
);
|
||||
}
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
@ -8,7 +8,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
{{^withFfiPluginHook}}
|
||||
import 'package:{{projectName}}/main.dart';
|
||||
{{/withFfiPluginHook}}
|
||||
|
||||
{{^withPluginHook}}
|
||||
void main() {
|
||||
@ -30,7 +32,7 @@ void main() {
|
||||
});
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{#withPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
@ -46,4 +48,4 @@ void main() {
|
||||
);
|
||||
});
|
||||
}
|
||||
{{/withPluginHook}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
@ -1,19 +0,0 @@
|
||||
<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>
|
@ -1,10 +0,0 @@
|
||||
<?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>
|
@ -1,6 +0,0 @@
|
||||
<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>
|
@ -1,45 +0,0 @@
|
||||
<?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>
|
@ -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> .` 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.
|
||||
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.
|
||||
{{/no_platforms}}
|
||||
|
97
packages/flutter_tools/templates/plugin_ffi/README.md.tmpl
Normal file
97
packages/flutter_tools/templates/plugin_ffi/README.md.tmpl
Normal file
@ -0,0 +1,97 @@
|
||||
# {{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.
|
||||
|
||||
This can be combined with dartPluginClass, such as when FFI is used for the
|
||||
implementation of one platform in a federated plugin:
|
||||
|
||||
```yaml
|
||||
plugin:
|
||||
implements: some_other_plugin
|
||||
platforms:
|
||||
some_platform:
|
||||
dartPluginClass: SomeClass
|
||||
ffiPlugin: true
|
||||
```
|
||||
|
||||
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}}
|
@ -0,0 +1,59 @@
|
||||
// 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 {
|
||||
// Bumping the plugin compileSdkVersion requires all clients of this plugin
|
||||
// to bump the version in their app.
|
||||
compileSdkVersion 31
|
||||
|
||||
// Bumping the plugin ndkVersion requires all clients of this plugin to bump
|
||||
// the version in their app and to download a newer version of the NDK.
|
||||
ndkVersion "21.1.6352462"
|
||||
|
||||
// Invoke the shared CMake build with the Android Gradle Plugin.
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "../src/CMakeLists.txt"
|
||||
|
||||
// The default CMake version for the Android Gradle Plugin is 3.10.2.
|
||||
// https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
|
||||
//
|
||||
// The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
// installed. You should not increase this version, as doing so will cause
|
||||
// the plugin to fail to compile for some customers of the plugin.
|
||||
// version "3.10.2"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?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>
|
19
packages/flutter_tools/templates/plugin_ffi/ffigen.yaml.tmpl
Normal file
19
packages/flutter_tools/templates/plugin_ffi/ffigen.yaml.tmpl
Normal file
@ -0,0 +1,19 @@
|
||||
# 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
|
@ -0,0 +1,3 @@
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
#include "../../src/{{projectName}}.c"
|
@ -0,0 +1,28 @@
|
||||
#
|
||||
# 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
|
@ -0,0 +1,139 @@
|
||||
{{#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;
|
||||
}();
|
@ -0,0 +1,69 @@
|
||||
// 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)>();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
# installed. You should not increase this version, as doing so will cause
|
||||
# the plugin to fail to compile for some customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Project-level configuration.
|
||||
set(PROJECT_NAME "{{projectName}}")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
# Invoke the build for native code shared with the other target platforms.
|
||||
# This can be changed to accomodate different builds.
|
||||
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.
|
||||
# This list could contain prebuilt libraries, or libraries created by an
|
||||
# external build triggered from this build file.
|
||||
set({{projectName}}_bundled_libraries
|
||||
# Defined in ../src/CMakeLists.txt.
|
||||
# This can be changed to accomodate different builds.
|
||||
$<TARGET_FILE:{{projectName}}>
|
||||
PARENT_SCOPE
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
#include "../../src/{{projectName}}.c"
|
@ -0,0 +1,17 @@
|
||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
# installed. You should not increase this version, as doing so will cause
|
||||
# the plugin to fail to compile for some customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
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)
|
@ -0,0 +1,23 @@
|
||||
#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.
|
||||
FFI_PLUGIN_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.
|
||||
FFI_PLUGIN_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;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#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 FFI_PLUGIN_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define FFI_PLUGIN_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.
|
||||
FFI_PLUGIN_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.
|
||||
FFI_PLUGIN_EXPORT intptr_t sum_long_running(intptr_t a, intptr_t b);
|
@ -0,0 +1,23 @@
|
||||
# The Flutter tooling requires that developers have a version of Visual Studio
|
||||
# installed that includes CMake 3.14 or later. You should not increase this
|
||||
# version, as doing so will cause the plugin to fail to compile for some
|
||||
# customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
# Project-level configuration.
|
||||
set(PROJECT_NAME "{{projectName}}")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
# Invoke the build for native code shared with the other target platforms.
|
||||
# This can be changed to accomodate different builds.
|
||||
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.
|
||||
# This list could contain prebuilt libraries, or libraries created by an
|
||||
# external build triggered from this build file.
|
||||
set({{projectName}}_bundled_libraries
|
||||
# Defined in ../src/CMakeLists.txt.
|
||||
# This can be changed to accomodate different builds.
|
||||
$<TARGET_FILE:{{projectName}}>
|
||||
PARENT_SCOPE
|
||||
)
|
@ -7,4 +7,9 @@ version:
|
||||
revision: {{flutterRevision}}
|
||||
channel: {{flutterChannel}}
|
||||
|
||||
{{#withFfiPluginHook}}
|
||||
project_type: plugin_ffi
|
||||
{{/withFfiPluginHook}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
project_type: plugin
|
||||
{{/withPlatformChannelPluginHook}}
|
9
packages/flutter_tools/templates/plugin_shared/android.tmpl/.gitignore
vendored
Normal file
9
packages/flutter_tools/templates/plugin_shared/android.tmpl/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
@ -0,0 +1 @@
|
||||
rootProject.name = '{{projectName}}'
|
@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="{{androidIdentifier}}">
|
||||
</manifest>
|
@ -12,6 +12,13 @@ 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>
|
@ -5,7 +5,12 @@ homepage:
|
||||
|
||||
environment:
|
||||
sdk: {{dartSdkVersionBounds}}
|
||||
{{#withPlatformChannelPluginHook}}
|
||||
flutter: ">=2.5.0"
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
{{#withFfiPluginHook}}
|
||||
flutter: ">={{minFrameworkVersionFfiPlugin}}"
|
||||
{{/withFfiPluginHook}}
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@ -16,6 +21,9 @@ dependencies:
|
||||
{{/web}}
|
||||
|
||||
dev_dependencies:
|
||||
{{#withFfiPluginHook}}
|
||||
ffigen: ^4.1.2
|
||||
{{/withFfiPluginHook}}
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^1.0.0
|
||||
@ -26,9 +34,52 @@ dev_dependencies:
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' and Android 'package' identifiers should not ordinarily
|
||||
# be modified. They are used by the tooling to maintain consistency when
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# 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
|
||||
# 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}}
|
||||
@ -67,6 +118,7 @@ flutter:
|
||||
pluginClass: {{pluginDartClass}}Web
|
||||
fileName: {{projectName}}_web.dart
|
||||
{{/web}}
|
||||
{{/withPlatformChannelPluginHook}}
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
@ -5,7 +5,6 @@
|
||||
"templates/app/lib/main.dart.tmpl",
|
||||
"templates/app/pubspec.yaml.tmpl",
|
||||
"templates/app/README.md.tmpl",
|
||||
"templates/app/test/widget_test.dart.tmpl",
|
||||
"templates/app/winuwp.tmpl/.gitignore",
|
||||
|
||||
"templates/app_shared/.gitignore.tmpl",
|
||||
@ -201,6 +200,8 @@
|
||||
"templates/app_shared/winuwp.tmpl/runner_uwp/resources.pri.img.tmpl",
|
||||
"templates/app_shared/winuwp.tmpl/runner_uwp/Windows_TemporaryKey.pfx.img.tmpl",
|
||||
|
||||
"templates/app_test_widget/test/widget_test.dart.tmpl",
|
||||
|
||||
"templates/cocoapods/Podfile-ios-objc",
|
||||
"templates/cocoapods/Podfile-ios-swift",
|
||||
"templates/cocoapods/Podfile-macos",
|
||||
@ -304,13 +305,6 @@
|
||||
"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",
|
||||
@ -322,7 +316,6 @@
|
||||
"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",
|
||||
@ -333,22 +326,51 @@
|
||||
"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",
|
||||
|
@ -43,11 +43,14 @@ void main() {
|
||||
final List<String> templatePaths = <String>[
|
||||
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'app'),
|
||||
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'app_shared'),
|
||||
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'app_test_widget'),
|
||||
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'cocoapods'),
|
||||
globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'skeleton'),
|
||||
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);
|
||||
@ -96,6 +99,9 @@ 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 {
|
||||
|
@ -2492,6 +2492,7 @@ void main() {
|
||||
final String buildContent = await globals.fs.file('${projectDir.path}/android/app/build.gradle').readAsString();
|
||||
|
||||
expect(buildContent.contains('compileSdkVersion flutter.compileSdkVersion'), true);
|
||||
expect(buildContent.contains('ndkVersion flutter.ndkVersion'), true);
|
||||
expect(buildContent.contains('targetSdkVersion flutter.targetSdkVersion'), true);
|
||||
});
|
||||
|
||||
@ -2761,6 +2762,104 @@ 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 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',
|
||||
'--platforms=android',
|
||||
projectDir.path,
|
||||
];
|
||||
|
||||
await expectLater(
|
||||
runner.run(args),
|
||||
throwsToolExit(message: 'The "android-language" option is not supported with the plugin_ffi template: the language will always be C or C++.'),
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('FFI plugins error ios language', () async {
|
||||
final CreateCommand command = CreateCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
final List<String> args = <String>[
|
||||
'create',
|
||||
'--no-pub',
|
||||
'--template=plugin_ffi',
|
||||
'--ios-language',
|
||||
'swift',
|
||||
'--platforms=ios',
|
||||
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('FFI plugins error web platform', () async {
|
||||
final CreateCommand command = CreateCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
final List<String> args = <String>[
|
||||
'create',
|
||||
'--no-pub',
|
||||
'--template=plugin_ffi',
|
||||
'--platforms=web',
|
||||
projectDir.path,
|
||||
];
|
||||
|
||||
await expectLater(
|
||||
runner.run(args),
|
||||
throwsToolExit(message: 'The web platform is not supported in plugin_ffi template.'),
|
||||
);
|
||||
});
|
||||
|
||||
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,33 +9,43 @@ import '../src/common.dart';
|
||||
import 'test_utils.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
late Directory tempDirPluginMethodChannels;
|
||||
late Directory tempDirPluginFfi;
|
||||
|
||||
setUp(() async {
|
||||
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
|
||||
tempDirPluginMethodChannels = createResolvedTempDirectorySync('flutter_plugin_test.');
|
||||
tempDirPluginFfi =
|
||||
createResolvedTempDirectorySync('flutter_ffi_plugin_test.');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
tryToDelete(tempDir);
|
||||
tryToDelete(tempDirPluginMethodChannels);
|
||||
tryToDelete(tempDirPluginFfi);
|
||||
});
|
||||
|
||||
test('plugin example can be built using current Flutter Gradle plugin', () async {
|
||||
Future<void> testPlugin({
|
||||
required String template,
|
||||
required Directory tempDir,
|
||||
}) async {
|
||||
final String flutterBin = fileSystem.path.join(
|
||||
getFlutterRoot(),
|
||||
'bin',
|
||||
'flutter',
|
||||
);
|
||||
|
||||
final String testName = '${template}_test';
|
||||
|
||||
processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--template=plugin',
|
||||
'--template=$template',
|
||||
'--platforms=android',
|
||||
'plugin_test',
|
||||
testName,
|
||||
], workingDirectory: tempDir.path);
|
||||
|
||||
final Directory exampleAppDir = tempDir.childDirectory('plugin_test').childDirectory('example');
|
||||
final Directory exampleAppDir =
|
||||
tempDir.childDirectory(testName).childDirectory('example');
|
||||
|
||||
final File buildGradleFile = exampleAppDir.childDirectory('android').childFile('build.gradle');
|
||||
expect(buildGradleFile, exists);
|
||||
@ -68,6 +78,11 @@ void main() {
|
||||
));
|
||||
expect(exampleApk, exists);
|
||||
|
||||
if (template == 'plugin_ffi') {
|
||||
// Does not support AGP 3.3.0.
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean
|
||||
processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
@ -101,5 +116,21 @@ 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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import 'test_utils.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
|
||||
setUp(() {
|
||||
Cache.flutterRoot = getFlutterRoot();
|
||||
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
test('error logged when plugin Android ndkVersion higher than project', () async {
|
||||
final String flutterBin = fileSystem.path.join(
|
||||
getFlutterRoot(),
|
||||
'bin',
|
||||
'flutter',
|
||||
);
|
||||
|
||||
// Create dummy plugin
|
||||
processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'create',
|
||||
'--template=plugin_ffi',
|
||||
'--platforms=android',
|
||||
'test_plugin',
|
||||
], workingDirectory: tempDir.path);
|
||||
|
||||
final Directory pluginAppDir = tempDir.childDirectory('test_plugin');
|
||||
final File pluginGradleFile = pluginAppDir.childDirectory('android').childFile('build.gradle');
|
||||
expect(pluginGradleFile, exists);
|
||||
|
||||
final String pluginBuildGradle = pluginGradleFile.readAsStringSync();
|
||||
|
||||
// Bump up plugin ndkVersion to 21.4.7075529.
|
||||
final RegExp androidNdkVersionRegExp = RegExp(r'ndkVersion (\"[0-9\.]+\"|flutter.ndkVersion)');
|
||||
final String newPluginGradleFile = pluginBuildGradle.replaceAll(androidNdkVersionRegExp, 'ndkVersion "21.4.7075529"');
|
||||
expect(newPluginGradleFile, contains('21.4.7075529'));
|
||||
pluginGradleFile.writeAsStringSync(newPluginGradleFile);
|
||||
|
||||
final Directory pluginExampleAppDir = pluginAppDir.childDirectory('example');
|
||||
|
||||
final File projectGradleFile = pluginExampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
|
||||
expect(projectGradleFile, exists);
|
||||
|
||||
final String projectBuildGradle = projectGradleFile.readAsStringSync();
|
||||
|
||||
// Bump down plugin example app ndkVersion to 21.1.6352462.
|
||||
final String newProjectGradleFile = projectBuildGradle.replaceAll(androidNdkVersionRegExp, 'ndkVersion "21.1.6352462"');
|
||||
expect(newProjectGradleFile, contains('21.1.6352462'));
|
||||
projectGradleFile.writeAsStringSync(newProjectGradleFile);
|
||||
|
||||
// Run flutter build apk to build plugin example project
|
||||
final ProcessResult result = processManager.runSync(<String>[
|
||||
flutterBin,
|
||||
...getLocalEngineArguments(),
|
||||
'build',
|
||||
'apk',
|
||||
'--target-platform=android-arm',
|
||||
], workingDirectory: pluginExampleAppDir.path);
|
||||
|
||||
// Check that an error message is thrown.
|
||||
expect(result.stderr, contains('''
|
||||
One or more plugins require a higher Android NDK version.
|
||||
Fix this issue by adding the following to ${projectGradleFile.path}:
|
||||
android {
|
||||
ndkVersion 21.4.7075529
|
||||
...
|
||||
}
|
||||
|
||||
'''));
|
||||
});
|
||||
}
|
@ -176,6 +176,7 @@ class BasicDeferredComponentsConfig extends DeferredComponentsConfig {
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
@ -11,19 +11,23 @@ import 'common.dart';
|
||||
/// Check if the pubspec.yaml file under the `projectDir` is valid for a plugin project.
|
||||
void validatePubspecForPlugin({
|
||||
required String projectDir,
|
||||
required String pluginClass,
|
||||
String? pluginClass,
|
||||
bool ffiPlugin = false,
|
||||
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;
|
||||
expect(platformMap['pluginClass'], pluginClass);
|
||||
if (pluginClass != null) {
|
||||
expect(platformMap['pluginClass'], pluginClass);
|
||||
}
|
||||
if (platform == 'android') {
|
||||
expect(platformMap['package'], androidIdentifier);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user