Fully support Dart-only mobile and macOS plugins (#96183)
diff --git a/dev/devicelab/bin/tasks/module_test_ios.dart b/dev/devicelab/bin/tasks/module_test_ios.dart
index ab646eb..81f69bf 100644
--- a/dev/devicelab/bin/tasks/module_test_ios.dart
+++ b/dev/devicelab/bin/tasks/module_test_ios.dart
@@ -152,14 +152,28 @@
await flutter('clean');
});
+ // Make a fake Dart-only plugin, since there are no existing examples.
+ section('Create local plugin');
+
+ const String dartPluginName = 'dartplugin';
+ await _createFakeDartPlugin(dartPluginName, tempDir);
+
section('Add plugins');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = await pubspec.readAsString();
content = content.replaceFirst(
'\ndependencies:\n',
- // One dynamic framework, one static framework, and one that does not support iOS.
- '\ndependencies:\n device_info: 2.0.3\n google_sign_in: 4.5.1\n android_alarm_manager: 0.4.5+11\n',
+ // One dynamic framework, one static framework, one Dart-only,
+ // and one that does not support iOS.
+ '''
+dependencies:
+ device_info: 2.0.3
+ google_sign_in: 4.5.1
+ android_alarm_manager: 0.4.5+11
+ $dartPluginName:
+ path: ../$dartPluginName
+''',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
@@ -191,7 +205,8 @@
|| !podfileLockOutput.contains(':path: Flutter/FlutterPluginRegistrant')
|| !podfileLockOutput.contains(':path: ".symlinks/plugins/device_info/ios"')
|| !podfileLockOutput.contains(':path: ".symlinks/plugins/google_sign_in/ios"')
- || podfileLockOutput.contains('android_alarm_manager')) {
+ || podfileLockOutput.contains('android_alarm_manager')
+ || podfileLockOutput.contains(dartPluginName)) {
print(podfileLockOutput);
return TaskResult.failure('Building ephemeral host app Podfile.lock does not contain expected pods');
}
@@ -205,6 +220,9 @@
// Android-only, no embedded framework.
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', 'android_alarm_manager.framework'));
+ // Dart-only, no embedded framework.
+ checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', '$dartPluginName.framework'));
+
section('Clean and pub get module');
await inDirectory(projectDir, () async {
@@ -243,7 +261,8 @@
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/Flutter/FlutterPluginRegistrant"')
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/.symlinks/plugins/device_info/ios"')
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/.symlinks/plugins/google_sign_in/ios"')
- || hostPodfileLockOutput.contains('android_alarm_manager')) {
+ || hostPodfileLockOutput.contains('android_alarm_manager')
+ || hostPodfileLockOutput.contains(dartPluginName)) {
print(hostPodfileLockOutput);
throw TaskResult.failure('Building host app Podfile.lock does not contain expected pods');
}
@@ -501,3 +520,46 @@
return symbolTable.contains('kDartIsolateSnapshotInstructions');
}
+
+Future<void> _createFakeDartPlugin(String name, Directory parent) async {
+ // Start from a standard plugin template.
+ await inDirectory(parent, () async {
+ await flutter(
+ 'create',
+ options: <String>[
+ '--org',
+ 'io.flutter.devicelab',
+ '--template=plugin',
+ '--platforms=ios',
+ name,
+ ],
+ );
+ });
+
+ final String pluginDir = path.join(parent.path, name);
+
+ // Convert the metadata to Dart-only.
+ final String dartPluginClass = 'DartClassFor$name';
+ final File pubspec = File(path.join(pluginDir, 'pubspec.yaml'));
+ String content = await pubspec.readAsString();
+ content = content.replaceAll(
+ RegExp(r' pluginClass: .*?\n'),
+ ' dartPluginClass: $dartPluginClass\n',
+ );
+ await pubspec.writeAsString(content, flush: true);
+
+ // Add the Dart registration hook that the build will generate a call to.
+ final File dartCode = File(path.join(pluginDir, 'lib', '$name.dart'));
+ content = await dartCode.readAsString();
+ content = '''
+$content
+
+class $dartPluginClass {
+ static void registerWith() {}
+}
+''';
+ await dartCode.writeAsString(content, flush: true);
+
+ // Remove the native plugin code.
+ await Directory(path.join(pluginDir, 'ios')).delete(recursive: true);
+}
diff --git a/dev/devicelab/bin/tasks/plugin_test.dart b/dev/devicelab/bin/tasks/plugin_test.dart
index d7b5e53..9b556cd 100644
--- a/dev/devicelab/bin/tasks/plugin_test.dart
+++ b/dev/devicelab/bin/tasks/plugin_test.dart
@@ -16,5 +16,7 @@
<String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}),
PluginTest('apk', <String>['-a', 'kotlin', '--platforms=android'], pluginCreateEnvironment:
<String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}),
+ // Test that Dart-only plugins are supported.
+ PluginTest('apk', <String>['--platforms=android'], dartOnlyPlugin: true),
]));
}
diff --git a/dev/devicelab/bin/tasks/plugin_test_ios.dart b/dev/devicelab/bin/tasks/plugin_test_ios.dart
index bff25a2..408cf1b 100644
--- a/dev/devicelab/bin/tasks/plugin_test_ios.dart
+++ b/dev/devicelab/bin/tasks/plugin_test_ios.dart
@@ -10,5 +10,8 @@
PluginTest('ios', <String>['-i', 'objc', '--platforms=ios']),
PluginTest('ios', <String>['-i', 'swift', '--platforms=ios']),
PluginTest('macos', <String>['--platforms=macos']),
+ // Test that Dart-only plugins are supported.
+ PluginTest('ios', <String>['--platforms=ios'], dartOnlyPlugin: true),
+ PluginTest('macos', <String>['--platforms=macos'], dartOnlyPlugin: true),
]));
}
diff --git a/dev/devicelab/lib/tasks/plugin_tests.dart b/dev/devicelab/lib/tasks/plugin_tests.dart
index 05fc551..4de88a2 100644
--- a/dev/devicelab/lib/tasks/plugin_tests.dart
+++ b/dev/devicelab/lib/tasks/plugin_tests.dart
@@ -26,12 +26,19 @@
/// Defines task that creates new Flutter project, adds a local and remote
/// plugin, and then builds the specified [buildTarget].
class PluginTest {
- PluginTest(this.buildTarget, this.options, { this.pluginCreateEnvironment, this.appCreateEnvironment });
+ PluginTest(
+ this.buildTarget,
+ this.options, {
+ this.pluginCreateEnvironment,
+ this.appCreateEnvironment,
+ this.dartOnlyPlugin = false,
+ });
final String buildTarget;
final List<String> options;
final Map<String, String>? pluginCreateEnvironment;
final Map<String, String>? appCreateEnvironment;
+ final bool dartOnlyPlugin;
Future<TaskResult> call() async {
final Directory tempDir =
@@ -41,6 +48,9 @@
final _FlutterProject plugin = await _FlutterProject.create(
tempDir, options, buildTarget,
name: 'plugintest', template: 'plugin', environment: pluginCreateEnvironment);
+ if (dartOnlyPlugin) {
+ await plugin.convertDefaultPluginToDartPlugin();
+ }
section('Test plugin');
await plugin.test();
section('Create Flutter app');
@@ -52,7 +62,7 @@
pluginPath: path.join('..', 'plugintest'));
await app.addPlugin('path_provider');
section('Build app');
- await app.build(buildTarget);
+ await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin);
section('Test app');
await app.test();
} finally {
@@ -76,8 +86,10 @@
String get rootPath => path.join(parent.path, name);
+ File get pubspecFile => File(path.join(rootPath, 'pubspec.yaml'));
+
Future<void> addPlugin(String plugin, {String? pluginPath}) async {
- final File pubspec = File(path.join(rootPath, 'pubspec.yaml'));
+ final File pubspec = pubspecFile;
String content = await pubspec.readAsString();
final String dependency =
pluginPath != null ? '$plugin:\n path: $pluginPath' : '$plugin:';
@@ -88,6 +100,47 @@
await pubspec.writeAsString(content, flush: true);
}
+ /// Converts a plugin created from the standard template to a Dart-only
+ /// plugin.
+ Future<void> convertDefaultPluginToDartPlugin() async {
+ final String dartPluginClass = 'DartClassFor$name';
+ // Convert the metadata.
+ final File pubspec = pubspecFile;
+ String content = await pubspec.readAsString();
+ content = content.replaceAll(
+ RegExp(r' pluginClass: .*?\n'),
+ ' dartPluginClass: $dartPluginClass\n',
+ );
+ await pubspec.writeAsString(content, flush: true);
+
+ // Add the Dart registration hook that the build will generate a call to.
+ final File dartCode = File(path.join(rootPath, 'lib', '$name.dart'));
+ content = await dartCode.readAsString();
+ content = '''
+$content
+
+class $dartPluginClass {
+ static void registerWith() {}
+}
+''';
+ await dartCode.writeAsString(content, flush: true);
+
+ // Remove any native plugin code.
+ const List<String> platforms = <String>[
+ 'android',
+ 'ios',
+ 'linux',
+ 'macos',
+ 'windows',
+ ];
+ for (final String platform in platforms) {
+ final Directory platformDir = Directory(path.join(rootPath, platform));
+ if (platformDir.existsSync()) {
+ await platformDir.delete(recursive: true);
+ }
+ }
+ }
+
Future<void> test() async {
await inDirectory(Directory(rootPath), () async {
await flutter('test');
@@ -147,7 +200,7 @@
podspec.writeAsStringSync(podspecContent, flush: true);
}
- Future<void> build(String target) async {
+ Future<void> build(String target, {bool validateNativeBuildProject = true}) async {
await inDirectory(Directory(rootPath), () async {
final String buildOutput = await evalFlutter('build', options: <String>[
target,
@@ -167,28 +220,30 @@
throw TaskResult.failure('Minimum plugin version warning present');
}
- final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'));
- if (!podsProject.existsSync()) {
- throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}');
- }
-
- final String podsProjectContent = podsProject.readAsStringSync();
- if (target == 'ios') {
- // Plugins with versions lower than the app version should not have IPHONEOS_DEPLOYMENT_TARGET set.
- // The plugintest plugin target should not have IPHONEOS_DEPLOYMENT_TARGET set since it has been lowered
- // in _reduceDarwinPluginMinimumVersion to 7, which is below the target version of 9.
- if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 7')) {
- throw TaskResult.failure('Plugin build setting IPHONEOS_DEPLOYMENT_TARGET not removed');
+ if (validateNativeBuildProject) {
+ final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'));
+ if (!podsProject.existsSync()) {
+ throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}');
}
- if (!podsProjectContent.contains(r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";')) {
- throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"');
- }
- }
- // Same for macOS deployment target, but 10.8.
- // The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set.
- if (target == 'macos' && podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) {
- throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed');
+ final String podsProjectContent = podsProject.readAsStringSync();
+ if (target == 'ios') {
+ // Plugins with versions lower than the app version should not have IPHONEOS_DEPLOYMENT_TARGET set.
+ // The plugintest plugin target should not have IPHONEOS_DEPLOYMENT_TARGET set since it has been lowered
+ // in _reduceDarwinPluginMinimumVersion to 7, which is below the target version of 9.
+ if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 7')) {
+ throw TaskResult.failure('Plugin build setting IPHONEOS_DEPLOYMENT_TARGET not removed');
+ }
+ if (!podsProjectContent.contains(r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";')) {
+ throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"');
+ }
+ }
+
+ // Same for macOS deployment target, but 10.8.
+ // The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set.
+ if (target == 'macos' && podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) {
+ throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed');
+ }
}
}
});
diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb
index 97e073d..7f11888 100644
--- a/packages/flutter_tools/bin/podhelper.rb
+++ b/packages/flutter_tools/bin/podhelper.rb
@@ -255,7 +255,8 @@
plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path']
- if (plugin_name && plugin_path)
+ has_native_build = plugin_hash.fetch('native_build', true)
+ if (plugin_name && plugin_path && has_native_build)
symlink = File.join(symlink_plugins_dir, plugin_name)
File.symlink(plugin_path, symlink)
diff --git a/packages/flutter_tools/gradle/app_plugin_loader.gradle b/packages/flutter_tools/gradle/app_plugin_loader.gradle
index f722ea8..ed92e8e 100644
--- a/packages/flutter_tools/gradle/app_plugin_loader.gradle
+++ b/packages/flutter_tools/gradle/app_plugin_loader.gradle
@@ -23,6 +23,12 @@
object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String
assert androidPlugin.path instanceof String
+ // Skip plugins that have no native build (such as a Dart-only implementation
+ // of a federated plugin).
+ def needsBuild = androidPlugin.containsKey('native_build') ? androidPlugin['native_build'] : true
+ if (!needsBuild) {
+ return
+ }
def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists()
include ":${androidPlugin.name}"
diff --git a/packages/flutter_tools/gradle/module_plugin_loader.gradle b/packages/flutter_tools/gradle/module_plugin_loader.gradle
index ebce109..d0b7287 100644
--- a/packages/flutter_tools/gradle/module_plugin_loader.gradle
+++ b/packages/flutter_tools/gradle/module_plugin_loader.gradle
@@ -20,6 +20,12 @@
object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String
assert androidPlugin.path instanceof String
+ // Skip plugins that have no native build (such as a Dart-only
+ // implementation of a federated plugin).
+ def needsBuild = androidPlugin.containsKey('native_build') ? androidPlugin['native_build'] : true
+ if (!needsBuild) {
+ return
+ }
def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists()
include ":${androidPlugin.name}"
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index 65a845e..2520df5 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -98,6 +98,7 @@
const String _kFlutterPluginsNameKey = 'name';
const String _kFlutterPluginsPathKey = 'path';
const String _kFlutterPluginsDependenciesKey = 'dependencies';
+const String _kFlutterPluginsHasNativeBuildKey = 'native_build';
/// Filters [plugins] to those supported by [platformKey].
List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String platformKey) {
@@ -108,9 +109,13 @@
final Set<String> pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet();
final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[];
for (final Plugin plugin in platformPlugins) {
+ // This is guaranteed to be non-null due to the `where` filter above.
+ final PluginPlatform platformPlugin = plugin.platforms[platformKey]!;
pluginInfo.add(<String, Object>{
_kFlutterPluginsNameKey: plugin.name,
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
+ if (platformPlugin is NativeOrDartPlugin)
+ _kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).isNative(),
_kFlutterPluginsDependenciesKey: <String>[...plugin.dependencies.where(pluginNames.contains)],
});
}
@@ -130,7 +135,8 @@
/// "dependencies": [
/// "plugin-a",
/// "plugin-b"
-/// ]
+/// ],
+/// "native_build": true
/// }
/// ],
/// "android": [],
diff --git a/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl b/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl
index 2f3d685..2dbaaaa 100644
--- a/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl
+++ b/packages/flutter_tools/templates/module/ios/library/Flutter.tmpl/podhelper.rb.tmpl
@@ -81,7 +81,8 @@
plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path']
- if (plugin_name && plugin_path)
+ has_native_build = plugin_hash.fetch('native_build', true)
+ if (plugin_name && plugin_path && has_native_build)
symlink = File.join(symlinks_dir, plugin_name)
FileUtils.rm_f(symlink)
File.symlink(plugin_path, symlink)
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index da38436..a51ffc8 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -30,6 +30,46 @@
import '../src/fakes.dart' hide FakeOperatingSystemUtils;
import '../src/pubspec_schema.dart';
+/// Information for a platform entry in the 'platforms' section of a plugin's
+/// pubspec.yaml.
+class _PluginPlatformInfo {
+ const _PluginPlatformInfo({
+ this.pluginClass,
+ this.dartPluginClass,
+ this.androidPackage,
+ this.fileName
+ }) : assert(pluginClass != null || dartPluginClass != null),
+ assert(androidPackage == null || pluginClass != null);
+
+ /// The pluginClass entry, if any.
+ final String pluginClass;
+
+ /// The dartPluginClass entry, if any.
+ final String dartPluginClass;
+
+ /// The package entry for an Android plugin implementation using pluginClass.
+ final String androidPackage;
+
+ /// The fileName entry for a web plugin implementation.
+ final String fileName;
+
+ /// Returns the body of a platform section for a plugin's pubspec, properly
+ /// indented.
+ String get indentedPubspecSection {
+ const String indentation = ' ';
+ return <String>[
+ if (pluginClass != null)
+ '${indentation}pluginClass: $pluginClass',
+ if (dartPluginClass != null)
+ '${indentation}dartPluginClass: $dartPluginClass',
+ if (androidPackage != null)
+ '${indentation}package: $androidPackage',
+ if (fileName != null)
+ '${indentation}fileName: $fileName',
+ ].join('\n');
+ }
+}
+
void main() {
group('plugins', () {
FileSystem fs;
@@ -331,7 +371,7 @@
);
}
- Directory createPluginWithDependencies({
+ Directory createLegacyPluginWithDependencies({
@required String name,
@required List<String> dependencies,
}) {
@@ -363,6 +403,44 @@
return pluginDirectory;
}
+ Directory createPlugin({
+ @required String name,
+ @required Map<String, _PluginPlatformInfo> platforms,
+ List<String> dependencies = const <String>[],
+ }) {
+ assert(name != null);
+ assert(dependencies != null);
+
+ final Iterable<String> platformSections = platforms.entries.map((MapEntry<String, _PluginPlatformInfo> entry) => '''
+ ${entry.key}:
+${entry.value.indentedPubspecSection}
+''');
+ final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('flutter_plugin.');
+ pluginDirectory
+ .childFile('pubspec.yaml')
+ .writeAsStringSync('''
+name: $name
+flutter:
+ plugin:
+ platforms:
+${platformSections.join('\n')}
+
+dependencies:
+''');
+ for (final String dependency in dependencies) {
+ pluginDirectory
+ .childFile('pubspec.yaml')
+ .writeAsStringSync(' $dependency:\n', mode: FileMode.append);
+ }
+ flutterProject.directory
+ .childFile('.packages')
+ .writeAsStringSync(
+ '$name:${pluginDirectory.childDirectory('lib').uri.toString()}\n',
+ mode: FileMode.append,
+ );
+ return pluginDirectory;
+ }
+
// Creates the files that would indicate that pod install has run for the
// given project.
void simulatePodInstallRun(XcodeBasedProject project) {
@@ -420,9 +498,9 @@
testUsingContext(
'Refreshing the plugin list modifies .flutter-plugins '
'and .flutter-plugins-dependencies when there are plugins', () async {
- final Directory pluginA = createPluginWithDependencies(name: 'plugin-a', dependencies: const <String>['plugin-b', 'plugin-c', 'random-package']);
- final Directory pluginB = createPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
- final Directory pluginC = createPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
+ final Directory pluginA = createLegacyPluginWithDependencies(name: 'plugin-a', dependencies: const <String>['plugin-b', 'plugin-c', 'random-package']);
+ final Directory pluginB = createLegacyPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
+ final Directory pluginC = createLegacyPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
iosProject.testExists = true;
final DateTime dateCreated = DateTime(1970);
@@ -449,22 +527,25 @@
<String, dynamic> {
'name': 'plugin-a',
'path': '${pluginA.path}/',
+ 'native_build': true,
'dependencies': <String>[
'plugin-b',
'plugin-c'
- ]
+ ],
},
<String, dynamic> {
'name': 'plugin-b',
'path': '${pluginB.path}/',
+ 'native_build': true,
'dependencies': <String>[
'plugin-c'
- ]
+ ],
},
<String, dynamic> {
'name': 'plugin-c',
'path': '${pluginC.path}/',
- 'dependencies': <String>[]
+ 'native_build': true,
+ 'dependencies': <String>[],
},
];
expect(plugins['ios'], expectedPlugins);
@@ -514,6 +595,51 @@
FlutterVersion: () => flutterVersion
});
+ testUsingContext(
+ '.flutter-plugins-dependencies indicates native build inclusion', () async {
+ createPlugin(
+ name: 'plugin-a',
+ platforms: const <String, _PluginPlatformInfo>{
+ // Native-only; should include native build.
+ 'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
+ // Hybrid native and Dart; should include native build.
+ 'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar'),
+ // Web; should not have the native build key at all since it doesn't apply.
+ 'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
+ // Dart-only; should not include native build.
+ 'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
+ });
+ iosProject.testExists = true;
+
+ final DateTime dateCreated = DateTime(1970);
+ systemClock.currentTime = dateCreated;
+
+ await refreshPluginsList(flutterProject);
+
+ expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true);
+ final String pluginsString = flutterProject.flutterPluginsDependenciesFile.readAsStringSync();
+ final Map<String, dynamic> jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
+ final Map<String, dynamic> plugins = jsonContent['plugins'] as Map<String, dynamic>;
+
+ // Extracts the native_build key (if any) from the first plugin for the
+ // given platform.
+ bool getNativeBuildValue(String platform) {
+ final List<Map<String, dynamic>> platformPlugins = (plugins[platform]
+ as List<dynamic>).cast<Map<String, dynamic>>();
+ expect(platformPlugins.length, 1);
+ return platformPlugins[0]['native_build'] as bool;
+ }
+ expect(getNativeBuildValue('android'), true);
+ expect(getNativeBuildValue('ios'), true);
+ expect(getNativeBuildValue('web'), null);
+ expect(getNativeBuildValue('windows'), false);
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ ProcessManager: () => FakeProcessManager.any(),
+ SystemClock: () => systemClock,
+ FlutterVersion: () => flutterVersion
+ });
+
testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async {
simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject);