Unverified Commit 2df7dca8 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Fully support Dart-only mobile and macOS plugins (#96183)

parent f18d4d03
...@@ -152,14 +152,28 @@ Future<void> main() async { ...@@ -152,14 +152,28 @@ Future<void> main() async {
await flutter('clean'); 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'); section('Add plugins');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml')); final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = await pubspec.readAsString(); String content = await pubspec.readAsString();
content = content.replaceFirst( content = content.replaceFirst(
'\ndependencies:\n', '\ndependencies:\n',
// One dynamic framework, one static framework, and one that does not support iOS. // One dynamic framework, one static framework, one Dart-only,
'\ndependencies:\n device_info: 2.0.3\n google_sign_in: 4.5.1\n android_alarm_manager: 0.4.5+11\n', // 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 pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async { await inDirectory(projectDir, () async {
...@@ -191,7 +205,8 @@ Future<void> main() async { ...@@ -191,7 +205,8 @@ Future<void> main() async {
|| !podfileLockOutput.contains(':path: Flutter/FlutterPluginRegistrant') || !podfileLockOutput.contains(':path: Flutter/FlutterPluginRegistrant')
|| !podfileLockOutput.contains(':path: ".symlinks/plugins/device_info/ios"') || !podfileLockOutput.contains(':path: ".symlinks/plugins/device_info/ios"')
|| !podfileLockOutput.contains(':path: ".symlinks/plugins/google_sign_in/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); print(podfileLockOutput);
return TaskResult.failure('Building ephemeral host app Podfile.lock does not contain expected pods'); return TaskResult.failure('Building ephemeral host app Podfile.lock does not contain expected pods');
} }
...@@ -205,6 +220,9 @@ Future<void> main() async { ...@@ -205,6 +220,9 @@ Future<void> main() async {
// Android-only, no embedded framework. // Android-only, no embedded framework.
checkDirectoryNotExists(path.join(ephemeralIOSHostApp.path, 'Frameworks', 'android_alarm_manager.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'); section('Clean and pub get module');
await inDirectory(projectDir, () async { await inDirectory(projectDir, () async {
...@@ -243,7 +261,8 @@ Future<void> main() async { ...@@ -243,7 +261,8 @@ Future<void> main() async {
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/Flutter/FlutterPluginRegistrant"') || !hostPodfileLockOutput.contains(':path: "../hello/.ios/Flutter/FlutterPluginRegistrant"')
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/.symlinks/plugins/device_info/ios"') || !hostPodfileLockOutput.contains(':path: "../hello/.ios/.symlinks/plugins/device_info/ios"')
|| !hostPodfileLockOutput.contains(':path: "../hello/.ios/.symlinks/plugins/google_sign_in/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); print(hostPodfileLockOutput);
throw TaskResult.failure('Building host app Podfile.lock does not contain expected pods'); throw TaskResult.failure('Building host app Podfile.lock does not contain expected pods');
} }
...@@ -501,3 +520,46 @@ Future<bool> _isAppAotBuild(Directory app) async { ...@@ -501,3 +520,46 @@ Future<bool> _isAppAotBuild(Directory app) async {
return symbolTable.contains('kDartIsolateSnapshotInstructions'); 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);
}
...@@ -16,5 +16,7 @@ Future<void> main() async { ...@@ -16,5 +16,7 @@ Future<void> main() async {
<String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}), <String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}),
PluginTest('apk', <String>['-a', 'kotlin', '--platforms=android'], pluginCreateEnvironment: PluginTest('apk', <String>['-a', 'kotlin', '--platforms=android'], pluginCreateEnvironment:
<String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}), <String, String>{'ENABLE_ANDROID_EMBEDDING_V2': 'true'}),
// Test that Dart-only plugins are supported.
PluginTest('apk', <String>['--platforms=android'], dartOnlyPlugin: true),
])); ]));
} }
...@@ -10,5 +10,8 @@ Future<void> main() async { ...@@ -10,5 +10,8 @@ Future<void> main() async {
PluginTest('ios', <String>['-i', 'objc', '--platforms=ios']), PluginTest('ios', <String>['-i', 'objc', '--platforms=ios']),
PluginTest('ios', <String>['-i', 'swift', '--platforms=ios']), PluginTest('ios', <String>['-i', 'swift', '--platforms=ios']),
PluginTest('macos', <String>['--platforms=macos']), 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),
])); ]));
} }
...@@ -26,12 +26,19 @@ TaskFunction combine(List<TaskFunction> tasks) { ...@@ -26,12 +26,19 @@ TaskFunction combine(List<TaskFunction> tasks) {
/// Defines task that creates new Flutter project, adds a local and remote /// Defines task that creates new Flutter project, adds a local and remote
/// plugin, and then builds the specified [buildTarget]. /// plugin, and then builds the specified [buildTarget].
class PluginTest { 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 String buildTarget;
final List<String> options; final List<String> options;
final Map<String, String>? pluginCreateEnvironment; final Map<String, String>? pluginCreateEnvironment;
final Map<String, String>? appCreateEnvironment; final Map<String, String>? appCreateEnvironment;
final bool dartOnlyPlugin;
Future<TaskResult> call() async { Future<TaskResult> call() async {
final Directory tempDir = final Directory tempDir =
...@@ -41,6 +48,9 @@ class PluginTest { ...@@ -41,6 +48,9 @@ class PluginTest {
final _FlutterProject plugin = await _FlutterProject.create( final _FlutterProject plugin = await _FlutterProject.create(
tempDir, options, buildTarget, tempDir, options, buildTarget,
name: 'plugintest', template: 'plugin', environment: pluginCreateEnvironment); name: 'plugintest', template: 'plugin', environment: pluginCreateEnvironment);
if (dartOnlyPlugin) {
await plugin.convertDefaultPluginToDartPlugin();
}
section('Test plugin'); section('Test plugin');
await plugin.test(); await plugin.test();
section('Create Flutter app'); section('Create Flutter app');
...@@ -52,7 +62,7 @@ class PluginTest { ...@@ -52,7 +62,7 @@ class PluginTest {
pluginPath: path.join('..', 'plugintest')); pluginPath: path.join('..', 'plugintest'));
await app.addPlugin('path_provider'); await app.addPlugin('path_provider');
section('Build app'); section('Build app');
await app.build(buildTarget); await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin);
section('Test app'); section('Test app');
await app.test(); await app.test();
} finally { } finally {
...@@ -76,8 +86,10 @@ class _FlutterProject { ...@@ -76,8 +86,10 @@ class _FlutterProject {
String get rootPath => path.join(parent.path, name); 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 { 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(); String content = await pubspec.readAsString();
final String dependency = final String dependency =
pluginPath != null ? '$plugin:\n path: $pluginPath' : '$plugin:'; pluginPath != null ? '$plugin:\n path: $pluginPath' : '$plugin:';
...@@ -88,6 +100,47 @@ class _FlutterProject { ...@@ -88,6 +100,47 @@ class _FlutterProject {
await pubspec.writeAsString(content, flush: true); 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 { Future<void> test() async {
await inDirectory(Directory(rootPath), () async { await inDirectory(Directory(rootPath), () async {
await flutter('test'); await flutter('test');
...@@ -147,7 +200,7 @@ class _FlutterProject { ...@@ -147,7 +200,7 @@ class _FlutterProject {
podspec.writeAsStringSync(podspecContent, flush: true); 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 { await inDirectory(Directory(rootPath), () async {
final String buildOutput = await evalFlutter('build', options: <String>[ final String buildOutput = await evalFlutter('build', options: <String>[
target, target,
...@@ -167,28 +220,30 @@ class _FlutterProject { ...@@ -167,28 +220,30 @@ class _FlutterProject {
throw TaskResult.failure('Minimum plugin version warning present'); throw TaskResult.failure('Minimum plugin version warning present');
} }
final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj')); if (validateNativeBuildProject) {
if (!podsProject.existsSync()) { final File podsProject = File(path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'));
throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}'); 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 (!podsProjectContent.contains(r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";')) {
throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"'); 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. // Same for macOS deployment target, but 10.8.
// The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set. // The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set.
if (target == 'macos' && podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) { if (target == 'macos' && podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) {
throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed'); throw TaskResult.failure('Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed');
}
} }
} }
}); });
......
...@@ -255,7 +255,8 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl ...@@ -255,7 +255,8 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl
plugin_pods.each do |plugin_hash| plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name'] plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path'] 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) symlink = File.join(symlink_plugins_dir, plugin_name)
File.symlink(plugin_path, symlink) File.symlink(plugin_path, symlink)
......
...@@ -23,6 +23,12 @@ assert object.plugins.android instanceof List ...@@ -23,6 +23,12 @@ assert object.plugins.android instanceof List
object.plugins.android.each { androidPlugin -> object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String assert androidPlugin.name instanceof String
assert androidPlugin.path 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') def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists() assert pluginDirectory.exists()
include ":${androidPlugin.name}" include ":${androidPlugin.name}"
......
...@@ -20,6 +20,12 @@ if (pluginsFile.exists()) { ...@@ -20,6 +20,12 @@ if (pluginsFile.exists()) {
object.plugins.android.each { androidPlugin -> object.plugins.android.each { androidPlugin ->
assert androidPlugin.name instanceof String assert androidPlugin.name instanceof String
assert androidPlugin.path 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') def pluginDirectory = new File(androidPlugin.path, 'android')
assert pluginDirectory.exists() assert pluginDirectory.exists()
include ":${androidPlugin.name}" include ":${androidPlugin.name}"
......
...@@ -98,6 +98,7 @@ const String _kFlutterPluginsPluginListKey = 'plugins'; ...@@ -98,6 +98,7 @@ const String _kFlutterPluginsPluginListKey = 'plugins';
const String _kFlutterPluginsNameKey = 'name'; const String _kFlutterPluginsNameKey = 'name';
const String _kFlutterPluginsPathKey = 'path'; const String _kFlutterPluginsPathKey = 'path';
const String _kFlutterPluginsDependenciesKey = 'dependencies'; const String _kFlutterPluginsDependenciesKey = 'dependencies';
const String _kFlutterPluginsHasNativeBuildKey = 'native_build';
/// Filters [plugins] to those supported by [platformKey]. /// Filters [plugins] to those supported by [platformKey].
List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String platformKey) { List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String platformKey) {
...@@ -108,9 +109,13 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String ...@@ -108,9 +109,13 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String
final Set<String> pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet(); final Set<String> pluginNames = platformPlugins.map((Plugin plugin) => plugin.name).toSet();
final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[]; final List<Map<String, Object>> pluginInfo = <Map<String, Object>>[];
for (final Plugin plugin in platformPlugins) { 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>{ pluginInfo.add(<String, Object>{
_kFlutterPluginsNameKey: plugin.name, _kFlutterPluginsNameKey: plugin.name,
_kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path), _kFlutterPluginsPathKey: globals.fsUtils.escapePath(plugin.path),
if (platformPlugin is NativeOrDartPlugin)
_kFlutterPluginsHasNativeBuildKey: (platformPlugin as NativeOrDartPlugin).isNative(),
_kFlutterPluginsDependenciesKey: <String>[...plugin.dependencies.where(pluginNames.contains)], _kFlutterPluginsDependenciesKey: <String>[...plugin.dependencies.where(pluginNames.contains)],
}); });
} }
...@@ -130,7 +135,8 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String ...@@ -130,7 +135,8 @@ List<Map<String, Object>> _filterPluginsByPlatform(List<Plugin> plugins, String
/// "dependencies": [ /// "dependencies": [
/// "plugin-a", /// "plugin-a",
/// "plugin-b" /// "plugin-b"
/// ] /// ],
/// "native_build": true
/// } /// }
/// ], /// ],
/// "android": [], /// "android": [],
......
...@@ -81,7 +81,8 @@ def install_flutter_plugin_pods(flutter_application_path) ...@@ -81,7 +81,8 @@ def install_flutter_plugin_pods(flutter_application_path)
plugin_pods.each do |plugin_hash| plugin_pods.each do |plugin_hash|
plugin_name = plugin_hash['name'] plugin_name = plugin_hash['name']
plugin_path = plugin_hash['path'] 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) symlink = File.join(symlinks_dir, plugin_name)
FileUtils.rm_f(symlink) FileUtils.rm_f(symlink)
File.symlink(plugin_path, symlink) File.symlink(plugin_path, symlink)
......
...@@ -30,6 +30,46 @@ import '../src/context.dart'; ...@@ -30,6 +30,46 @@ import '../src/context.dart';
import '../src/fakes.dart' hide FakeOperatingSystemUtils; import '../src/fakes.dart' hide FakeOperatingSystemUtils;
import '../src/pubspec_schema.dart'; 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() { void main() {
group('plugins', () { group('plugins', () {
FileSystem fs; FileSystem fs;
...@@ -331,7 +371,7 @@ flutter: ...@@ -331,7 +371,7 @@ flutter:
); );
} }
Directory createPluginWithDependencies({ Directory createLegacyPluginWithDependencies({
@required String name, @required String name,
@required List<String> dependencies, @required List<String> dependencies,
}) { }) {
...@@ -347,6 +387,44 @@ flutter: ...@@ -347,6 +387,44 @@ flutter:
plugin: plugin:
androidPackage: plugin2 androidPackage: plugin2
pluginClass: UseNewEmbedding pluginClass: UseNewEmbedding
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;
}
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: dependencies:
'''); ''');
for (final String dependency in dependencies) { for (final String dependency in dependencies) {
...@@ -420,9 +498,9 @@ dependencies: ...@@ -420,9 +498,9 @@ dependencies:
testUsingContext( testUsingContext(
'Refreshing the plugin list modifies .flutter-plugins ' 'Refreshing the plugin list modifies .flutter-plugins '
'and .flutter-plugins-dependencies when there are plugins', () async { '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 pluginA = createLegacyPluginWithDependencies(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 pluginB = createLegacyPluginWithDependencies(name: 'plugin-b', dependencies: const <String>['plugin-c']);
final Directory pluginC = createPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]); final Directory pluginC = createLegacyPluginWithDependencies(name: 'plugin-c', dependencies: const <String>[]);
iosProject.testExists = true; iosProject.testExists = true;
final DateTime dateCreated = DateTime(1970); final DateTime dateCreated = DateTime(1970);
...@@ -449,22 +527,25 @@ dependencies: ...@@ -449,22 +527,25 @@ dependencies:
<String, dynamic> { <String, dynamic> {
'name': 'plugin-a', 'name': 'plugin-a',
'path': '${pluginA.path}/', 'path': '${pluginA.path}/',
'native_build': true,
'dependencies': <String>[ 'dependencies': <String>[
'plugin-b', 'plugin-b',
'plugin-c' 'plugin-c'
] ],
}, },
<String, dynamic> { <String, dynamic> {
'name': 'plugin-b', 'name': 'plugin-b',
'path': '${pluginB.path}/', 'path': '${pluginB.path}/',
'native_build': true,
'dependencies': <String>[ 'dependencies': <String>[
'plugin-c' 'plugin-c'
] ],
}, },
<String, dynamic> { <String, dynamic> {
'name': 'plugin-c', 'name': 'plugin-c',
'path': '${pluginC.path}/', 'path': '${pluginC.path}/',
'dependencies': <String>[] 'native_build': true,
'dependencies': <String>[],
}, },
]; ];
expect(plugins['ios'], expectedPlugins); expect(plugins['ios'], expectedPlugins);
...@@ -514,6 +595,51 @@ dependencies: ...@@ -514,6 +595,51 @@ dependencies:
FlutterVersion: () => flutterVersion 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 { testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async {
simulatePodInstallRun(iosProject); simulatePodInstallRun(iosProject);
simulatePodInstallRun(macosProject); simulatePodInstallRun(macosProject);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment