From b94c1a41ca63df3b2efb41ea6c8ed0f9bcf669af Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 4 Nov 2019 13:37:51 -0800 Subject: [PATCH] Exit tool if a plugin supports the embedding v2 but the app doesn't (#44026) --- .../lib/src/platform_plugins.dart | 21 +++- packages/flutter_tools/lib/src/plugins.dart | 23 +++- .../test/general.shard/plugins_test.dart | 105 ++++++++++++++++++ 3 files changed, 137 insertions(+), 12 deletions(-) diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index cfadd8f6431..4b22c3c77cd 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart @@ -64,17 +64,20 @@ class AndroidPlugin extends PluginPlatform { 'name': name, 'package': package, 'class': pluginClass, - 'usesEmbedding2': _embeddingVersion == '2', + // Mustache doesn't support complex types. + 'supportsEmbeddingV1': _supportedEmbedings.contains('1'), + 'supportsEmbeddingV2': _supportedEmbedings.contains('2'), }; } - String _cachedEmbeddingVersion; + Set _cachedEmbeddingVersion; /// Returns the version of the Android embedding. - String get _embeddingVersion => _cachedEmbeddingVersion ??= _getEmbeddingVersion(); + Set get _supportedEmbedings => _cachedEmbeddingVersion ??= _getSupportedEmbeddings(); - String _getEmbeddingVersion() { + Set _getSupportedEmbeddings() { assert(pluginPath != null); + final Set supportedEmbeddings = {}; final String baseMainPath = fs.path.join( pluginPath, 'android', @@ -113,9 +116,15 @@ class AndroidPlugin extends PluginPlatform { } if (mainClassContent .contains('io.flutter.embedding.engine.plugins.FlutterPlugin')) { - return '2'; + supportedEmbeddings.add('2'); + } else { + supportedEmbeddings.add('1'); } - return '1'; + if (mainClassContent.contains('registerWith(Registrar registrar)') || + mainClassContent.contains('registerWith(registrar: Registrar)')) { + supportedEmbeddings.add('1'); + } + return supportedEmbeddings; } } diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 4c183979da0..da994b414ef 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -338,12 +338,14 @@ public final class GeneratedPluginRegistrant { ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); {{/needsShim}} {{#plugins}} - {{#usesEmbedding2}} + {{#supportsEmbeddingV2}} flutterEngine.getPlugins().add(new {{package}}.{{class}}()); - {{/usesEmbedding2}} - {{^usesEmbedding2}} - {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}")); - {{/usesEmbedding2}} + {{/supportsEmbeddingV2}} + {{^supportsEmbeddingV2}} + {{#supportsEmbeddingV1}} + {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}")); + {{/supportsEmbeddingV1}} + {{/supportsEmbeddingV2}} {{/plugins}} } } @@ -417,7 +419,7 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List // If a plugin is using an embedding version older than 2.0 and the app is using 2.0, // then add shim for the old plugins. for (Map plugin in androidPlugins) { - if (!plugin['usesEmbedding2']) { + if (plugin['supportsEmbeddingV1'] && !plugin['supportsEmbeddingV2']) { templateContext['needsShim'] = true; break; } @@ -425,6 +427,15 @@ Future _writeAndroidPluginRegistrant(FlutterProject project, List templateContent = _androidPluginRegistryTemplateNewEmbedding; break; case '1': + for (Map plugin in androidPlugins) { + if (!plugin['supportsEmbeddingV1'] && plugin['supportsEmbeddingV2']) { + throwToolExit( + 'The plugin `${plugin['name']}` requires your app to be migrated to ' + 'the Android embedding v2. Follow the steps on https://flutter.dev/go/android-project-migration ' + 'and re-run this command.' + ); + } + } templateContent = _androidPluginRegistryTemplateOldEmbedding; break; default: diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 3bde6ec6697..59c19c40270 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -299,6 +299,111 @@ plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()} XcodeProjectInterpreter: () => xcodeProjectInterpreter, }); + testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async { + when(flutterProject.isModule).thenReturn(false); + + final File androidManifest = flutterProject.directory + .childDirectory('android') + .childFile('AndroidManifest.xml') + ..createSync(recursive: true) + ..writeAsStringSync(kAndroidManifestUsingOldEmbedding); + when(androidProject.appManifestFile).thenReturn(androidManifest); + + final Directory pluginUsingJavaAndNewEmbeddingDir = + fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); + pluginUsingJavaAndNewEmbeddingDir + .childFile('pubspec.yaml') + .writeAsStringSync(''' + flutter: + plugin: + androidPackage: plugin1 + pluginClass: UseNewEmbedding + '''); + pluginUsingJavaAndNewEmbeddingDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('main') + .childDirectory('java') + .childDirectory('plugin1') + .childFile('UseNewEmbedding.java') + ..createSync(recursive: true) + ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;'); + + flutterProject.directory + .childFile('.packages') + .writeAsStringSync(''' +plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()} +'''); + await expectLater( + () async { + await injectPlugins(flutterProject); + }, + throwsToolExit( + message: 'The plugin `plugin1` requires your app to be migrated to the Android embedding v2. ' + 'Follow the steps on https://flutter.dev/go/android-project-migration and re-run this command.' + ), + ); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + + testUsingContext('allows app use a plugin that supports v1 and v2 embedding', () async { + when(flutterProject.isModule).thenReturn(false); + + final File androidManifest = flutterProject.directory + .childDirectory('android') + .childFile('AndroidManifest.xml') + ..createSync(recursive: true) + ..writeAsStringSync(kAndroidManifestUsingOldEmbedding); + when(androidProject.appManifestFile).thenReturn(androidManifest); + + final Directory pluginUsingJavaAndNewEmbeddingDir = + fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); + pluginUsingJavaAndNewEmbeddingDir + .childFile('pubspec.yaml') + .writeAsStringSync(''' + flutter: + plugin: + androidPackage: plugin1 + pluginClass: UseNewEmbedding + '''); + pluginUsingJavaAndNewEmbeddingDir + .childDirectory('android') + .childDirectory('src') + .childDirectory('main') + .childDirectory('java') + .childDirectory('plugin1') + .childFile('UseNewEmbedding.java') + ..createSync(recursive: true) + ..writeAsStringSync( + 'import io.flutter.embedding.engine.plugins.FlutterPlugin;' + 'registerWith(Registrar registrar)' + ); + + flutterProject.directory + .childFile('.packages') + .writeAsStringSync(''' +plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()} +'''); + await injectPlugins(flutterProject); + + final File registrant = flutterProject.directory + .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins')) + .childFile('GeneratedPluginRegistrant.java'); + + expect(registrant.existsSync(), isTrue); + expect(registrant.readAsStringSync(), contains('package io.flutter.plugins')); + expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant')); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + FeatureFlags: () => featureFlags, + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async { when(flutterProject.isModule).thenReturn(false);