Add `--platforms` to `flutter create -t plugin` command (#59507)
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index c5a36f6..b20b1e9 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -6,8 +6,9 @@ import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; +import 'package:yaml/yaml.dart'; -import '../android/android.dart' as android; +import '../android/android.dart' as android_common; import '../android/android_sdk.dart' as android_sdk; import '../android/gradle_utils.dart' as gradle; import '../base/common.dart'; @@ -15,20 +16,39 @@ import '../base/file_system.dart'; import '../base/io.dart'; import '../base/net.dart'; +import '../base/terminal.dart'; import '../base/utils.dart'; import '../cache.dart'; import '../convert.dart'; import '../dart/pub.dart'; import '../features.dart'; +import '../flutter_manifest.dart'; import '../flutter_project_metadata.dart'; import '../globals.dart' as globals; +import '../plugins.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; import '../template.dart'; +const List<String> _kAvailablePlatforms = <String>[ + 'ios', + 'android', + 'windows', + 'linux', + 'macos', + 'web', + ]; + +const String _kNoPlatformsErrorMessage = ''' +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 detailed instructions on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. +'''; + class CreateCommand extends FlutterCommand { CreateCommand() { + _addPlatformsOptions(); argParser.addFlag('pub', defaultsTo: true, help: 'Whether to run "flutter pub get" after the project has been created.', @@ -318,6 +338,19 @@ final bool generatePlugin = template == FlutterProjectType.plugin; final bool generatePackage = template == FlutterProjectType.package; + final List<String> platforms = stringsArg('platforms'); + // `--platforms` does not support module or package. + if (argResults.wasParsed('platforms') && (generateModule || generatePackage)) { + final String template = generateModule ? 'module' : 'package'; + throwToolExit( + 'The "--platforms" argument is not supported in $template template.', + exitCode: 2 + ); + } else if (platforms == null || platforms.isEmpty) { + throwToolExit('Must specify at least one platform using --platforms', + exitCode: 2); + } + String organization = stringArg('org'); if (!argResults.wasParsed('org')) { final FlutterProject project = FlutterProject.fromDirectory(projectDir); @@ -344,7 +377,7 @@ throwToolExit(error); } - final Map<String, dynamic> templateContext = _templateContext( + final Map<String, dynamic> templateContext = _createTemplateContext( organization: organization, projectName: projectName, projectDescription: stringArg('description'), @@ -353,10 +386,12 @@ withPluginHook: generatePlugin, androidLanguage: stringArg('android-language'), iosLanguage: stringArg('ios-language'), - web: featureFlags.isWebEnabled, - linux: featureFlags.isLinuxEnabled, - macos: featureFlags.isMacOSEnabled, - windows: featureFlags.isWindowsEnabled, + ios: platforms.contains('ios'), + android: platforms.contains('android'), + web: featureFlags.isWebEnabled && platforms.contains('web'), + linux: featureFlags.isLinuxEnabled && platforms.contains('linux'), + macos: featureFlags.isMacOSEnabled && platforms.contains('macos'), + windows: featureFlags.isWindowsEnabled && platforms.contains('windows'), ); final String relativeDirPath = globals.fs.path.relative(projectDirPath); @@ -417,7 +452,8 @@ if (globals.doctor.canLaunchAnything) { // Let them know a summary of the state of their tooling. await globals.doctor.summary(); - + final List<String> platforms = _getSupportedPlatformsFromTemplateContext(templateContext); + final String platformsString = platforms.join(', '); globals.printStatus(''' In order to run your $application, type: @@ -430,7 +466,7 @@ globals.printStatus(''' Your plugin code is in $relativePluginMain. -Host platform code is in the "android" and "ios" directories under $relativePluginPath. +Host platform code is in the $platformsString directories under $relativePluginPath. To edit platform code in an IDE see https://flutter.dev/developing-packages/#edit-plugin-package. '''); } @@ -467,6 +503,17 @@ return FlutterCommandResult.success(); } + void _addPlatformsOptions() { + argParser.addMultiOption('platforms', + help: 'the platforms supported by this project.' + 'This argument only works when the --template is set to app or plugin.' + 'Platform folders (e.g. android/) will be generated in the target project.' + 'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform.' + 'Adding desktop platforms requires the corresponding desktop config setting to be enabled.', + defaultsTo: _kAvailablePlatforms, + allowed: _kAvailablePlatforms); + } + Future<int> _generateModule(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async { int generatedCount = 0; final String description = argResults.wasParsed('description') @@ -504,12 +551,27 @@ } Future<int> _generatePlugin(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async { + // Plugin doesn't create any platform by default + if (!argResults.wasParsed('platforms')) { + // If the user didn't explicitly declare the platforms, we don't generate any platforms. + templateContext['ios'] = false; + templateContext['android'] = false; + templateContext['web'] = false; + templateContext['linux'] = false; + templateContext['macos'] = false; + templateContext['windows'] = false; + globals.printError(_kNoPlatformsErrorMessage); + } + final List<String> platforms = _getSupportedPlatformsFromTemplateContext(templateContext); + final bool willAddPlatforms = platforms.isNotEmpty; + templateContext['no_platforms'] = !willAddPlatforms; int generatedCount = 0; final String description = argResults.wasParsed('description') ? stringArg('description') : 'A new flutter plugin project.'; templateContext['description'] = description; generatedCount += await _renderTemplate('plugin', directory, templateContext, overwrite: overwrite); + if (boolArg('pub')) { await pub.get( context: PubContext.createPlugin, @@ -517,8 +579,45 @@ offline: boolArg('offline'), ); } + + if (willAddPlatforms) { + // If adding new platforms to an existing plugin project, prints + // a help message containing the platforms maps need to be added to the `platforms` key in the pubspec. + final String pubspecPath = globals.fs.path.join(directory.absolute.path, 'pubspec.yaml'); + final List<String> platformsToAdd = List<String>.from(platforms); + final FlutterManifest manifest = FlutterManifest.createFromPath(pubspecPath, fileSystem: globals.fs, logger: globals.logger); + final List<String> existingPlatforms = manifest.supportedPlatforms.keys.toList(); + platformsToAdd.removeWhere(existingPlatforms.contains); + final YamlMap platformsMapToPrint = Plugin.createPlatformsYamlMap(platformsToAdd, templateContext['pluginClass'] as String, templateContext['androidIdentifier'] as String); + if (platformsMapToPrint.isNotEmpty) { + String prettyYaml = ''; + for (final String platform in platformsMapToPrint.keys.toList().cast<String>()) { + prettyYaml += '$platform:\n'; + for (final String key in (platformsMapToPrint[platform] as YamlMap).keys.toList().cast<String>()) { + prettyYaml += ' $key: ${platformsMapToPrint[platform][key] as String}\n'; + } + } + globals.printStatus(''' +The `pubspec.yaml` under the project directory must be updated to support ${platformsToAdd.join(', ')}, +Add below lines to under the `platform:` key: +''', emphasis: true); + globals.printStatus(prettyYaml, emphasis: true, color: TerminalColor.blue); + globals.printStatus(''' +If the `platforms` key does not exist in the `pubspec.yaml`, it might because that the plugin project does not +use the multi-platforms plugin format. We highly recommend a migration to the multi-platforms plugin format. +For detailed instructions on how to format the pubspec.yaml to support platforms using the multi-platforms format, see: +https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms +''', emphasis: true); + } + } + + final FlutterProject project = FlutterProject.fromDirectory(directory); - gradle.updateLocalProperties(project: project, requireAndroidSdk: false); + 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; @@ -527,19 +626,22 @@ templateContext['projectName'] = exampleProjectName; templateContext['androidIdentifier'] = _createAndroidIdentifier(organization, exampleProjectName); templateContext['iosIdentifier'] = _createUTIIdentifier(organization, exampleProjectName); + templateContext['macosIdentifier'] = _createUTIIdentifier(organization, exampleProjectName); templateContext['description'] = 'Demonstrates how to use the $projectName plugin.'; templateContext['pluginProjectName'] = projectName; templateContext['androidPluginIdentifier'] = androidPluginIdentifier; - generatedCount += await _generateApp(project.example.directory, templateContext, overwrite: overwrite); + generatedCount += await _generateApp(project.example.directory, templateContext, overwrite: overwrite, pluginExampleApp: true); return generatedCount; } - Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async { + Future<int> _generateApp(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false, bool pluginExampleApp = false}) async { int generatedCount = 0; generatedCount += await _renderTemplate('app', directory, templateContext, overwrite: overwrite); final FlutterProject project = FlutterProject.fromDirectory(directory); - generatedCount += _injectGradleWrapper(project); + if (templateContext['android'] == true) { + generatedCount += _injectGradleWrapper(project); + } if (boolArg('with-driver-test')) { final Directory testDirectory = directory.childDirectory('test_driver'); @@ -548,11 +650,11 @@ if (boolArg('pub')) { await pub.get(context: PubContext.create, directory: directory.path, offline: boolArg('offline')); - await project.ensureReadyForPlatformSpecificTooling(checkProjects: false); + await project.ensureReadyForPlatformSpecificTooling(checkProjects: pluginExampleApp); } - - gradle.updateLocalProperties(project: project, requireAndroidSdk: false); - + if (templateContext['android'] == true) { + gradle.updateLocalProperties(project: project, requireAndroidSdk: false); + } return generatedCount; } @@ -570,7 +672,24 @@ return -files.length; } - Map<String, dynamic> _templateContext({ + List<String> _getSupportedPlatformsFromTemplateContext(Map<String, dynamic> templateContext) { + return <String>[ + if (templateContext['ios'] == true) + 'ios', + if (templateContext['android'] == true) + 'android', + if (templateContext['web'] == true) + 'web', + if (templateContext['linux'] == true) + 'linux', + if (templateContext['windows'] == true) + 'windows', + if (templateContext['macos'] == true) + 'macos', + ]; + } + + Map<String, dynamic> _createTemplateContext({ String organization, String projectName, String projectDescription, @@ -579,6 +698,8 @@ String flutterRoot, bool renderDriverTest = false, bool withPluginHook = false, + bool ios = false, + bool android = false, bool web = false, bool linux = false, bool macos = false, @@ -603,7 +724,7 @@ 'description': projectDescription, 'dartSdk': '$flutterRoot/bin/cache/dart-sdk', 'useAndroidEmbeddingV2': featureFlags.isAndroidEmbeddingV2Enabled, - 'androidMinApiLevel': android.minApiLevel, + 'androidMinApiLevel': android_common.minApiLevel, 'androidSdkVersion': android_sdk.minimumAndroidSdkVersion, 'withDriverTest': renderDriverTest, 'pluginClass': pluginClass, @@ -616,6 +737,8 @@ 'iosLanguage': iosLanguage, 'flutterRevision': globals.flutterVersion.frameworkRevision, 'flutterChannel': globals.flutterVersion.channel, + 'ios': ios, + 'android': android, 'web': web, 'linux': linux, 'macos': macos,
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index ec18de1..bc2d2cd 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -163,17 +163,16 @@ if (isModule) { return _flutterDescriptor['module']['androidPackage'] as String; } - if (isPlugin) { - final YamlMap plugin = _flutterDescriptor['plugin'] as YamlMap; - if (plugin.containsKey('platforms')) { - final YamlMap platforms = plugin['platforms'] as YamlMap; - - if (platforms.containsKey('android')) { - return platforms['android']['package'] as String; - } - } else { + if (supportedPlatforms == null) { + // Pre-multi-platform plugin format + if (isPlugin) { + final YamlMap plugin = _flutterDescriptor['plugin'] as YamlMap; return plugin['androidPackage'] as String; } + return null; + } + if (supportedPlatforms.containsKey('android')) { + return supportedPlatforms['android']['package'] as String; } return null; } @@ -187,6 +186,20 @@ return null; } + /// Gets the supported platforms. This only supports the new `platforms` format. + /// + /// If the plugin uses the legacy pubspec format, this method returns null. + Map<String, dynamic> get supportedPlatforms { + if (isPlugin) { + final YamlMap plugin = _flutterDescriptor['plugin'] as YamlMap; + if (plugin.containsKey('platforms')) { + final YamlMap platformsMap = plugin['platforms'] as YamlMap; + return platformsMap.value.cast<String, dynamic>(); + } + } + return null; + } + List<Map<String, dynamic>> get fontsDescriptor { return fonts.map((Font font) => font.descriptor).toList(); }
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index dea67de..79d9305 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -174,6 +174,25 @@ ); } + /// Create a YamlMap that represents the supported platforms. + /// + /// For example, if the `platforms` contains 'ios' and 'android', the return map looks like: + /// android: + /// package: io.flutter.plugins.sample + /// pluginClass: SamplePlugin + /// ios: + /// pluginClass: SamplePlugin + static YamlMap createPlatformsYamlMap(List<String> platforms, String pluginClass, String androidPackage) { + final Map<String, dynamic> map = <String, dynamic>{}; + for (final String platform in platforms) { + map[platform] = <String, String>{ + 'pluginClass': pluginClass, + ...platform == 'android' ? <String, String>{'package': androidPackage} : <String, String>{}, + }; + } + return YamlMap.wrap(map); + } + static List<String> validatePluginYaml(YamlMap yaml) { if (yaml == null) { return <String>['Invalid "plugin" specification.'];
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 2672d62..896a0fd 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -225,6 +225,9 @@ /// Generates project files necessary to make Gradle builds work on Android /// and CocoaPods+Xcode work on iOS, for app and module projects only. + // TODO(cyanglaz): The param `checkProjects` is confusing. We should give it a better name + // or add some documentation explaining what it does, or both. + // https://github.com/flutter/flutter/issues/60023 Future<void> ensureReadyForPlatformSpecificTooling({bool checkProjects = false}) async { if (!directory.existsSync() || hasExampleApp) { return; @@ -648,6 +651,7 @@ template.render( target, <String, dynamic>{ + 'ios': true, 'projectName': parent.manifest.appName, 'iosIdentifier': parent.manifest.iosBundleIdentifier, }, @@ -797,6 +801,7 @@ template.render( target, <String, dynamic>{ + 'android': true, 'projectName': parent.manifest.appName, 'androidIdentifier': parent.manifest.androidPackage, 'androidX': usesAndroidX,
diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index 6945c43..d36353d 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart
@@ -108,6 +108,16 @@ } relativeDestinationPath = relativeDestinationPath.replaceAll('$platform-$language.tmpl', platform); } + + final bool android = context['android'] as bool; + if (relativeDestinationPath.contains('android') && !android) { + return null; + } + + // TODO(cyanglaz): do not add iOS folder by default when 1.20.0 is released. + // Also need to update the flutter SDK min constraint in the pubspec.yaml to 1.20.0. + // https://github.com/flutter/flutter/issues/59787 + // Only build a web project if explicitly asked. final bool web = context['web'] as bool; if (relativeDestinationPath.contains('web') && !web) { @@ -128,6 +138,7 @@ if (relativeDestinationPath.startsWith('windows.tmpl') && !windows) { return null; } + final String projectName = context['projectName'] as String; final String androidIdentifier = context['androidIdentifier'] as String; final String pluginClass = context['pluginClass'] as String; @@ -139,7 +150,7 @@ .replaceAll(imageTemplateExtension, '') .replaceAll(templateExtension, ''); - if (androidIdentifier != null) { + if (android != null && android && androidIdentifier != null) { finalDestinationPath = finalDestinationPath .replaceAll('androidIdentifier', androidIdentifier.replaceAll('.', pathSeparator)); }
diff --git a/packages/flutter_tools/templates/plugin/README.md.tmpl b/packages/flutter_tools/templates/plugin/README.md.tmpl index d9c79a1..fc3fa8f 100644 --- a/packages/flutter_tools/templates/plugin/README.md.tmpl +++ b/packages/flutter_tools/templates/plugin/README.md.tmpl
@@ -9,6 +9,12 @@ a specialized package that includes platform-specific implementation code for Android and/or iOS. -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +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, 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. +{{/no_platforms}}
diff --git a/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl b/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl index f7b9f56..5ccb0c4 100644 --- a/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl +++ b/packages/flutter_tools/templates/plugin/lib/projectName.dart.tmpl
@@ -1,3 +1,10 @@ +{{#no_platforms}} +// You have generated a new plugin project without +// specifying the `--platforms` flag. A plugin project supports no platforms is generated. +// To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same +// directory. You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. +{{/no_platforms}} + import 'dart:async'; import 'package:flutter/services.dart';
diff --git a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl index 1cc1ca49..fb93073 100644 --- a/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/plugin/pubspec.yaml.tmpl
@@ -31,11 +31,25 @@ # adding or updating assets for this project. plugin: platforms: +{{#no_platforms}} + # This plugin project was generated without specifying any + # platforms with the `--platform` argument. If you see the `fake_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: + pluginClass: somePluginClass + # ------------------- +{{/no_platforms}} +{{#android}} android: package: {{androidIdentifier}} pluginClass: {{pluginClass}} +{{/android}} +{{#ios}} ios: pluginClass: {{pluginClass}} +{{/ios}} {{#linux}} linux: pluginClass: {{pluginClass}} @@ -44,6 +58,10 @@ macos: pluginClass: {{pluginClass}} {{/macos}} +{{#windows}} + windows: + pluginClass: {{pluginClass}} +{{/windows}} {{#web}} web: pluginClass: {{pluginDartClass}}Web
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index b856d69..014e70a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -26,6 +26,7 @@ import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/pubspec_schema.dart'; import '../../src/testbed.dart'; const String frameworkRevision = '12345678'; @@ -242,23 +243,18 @@ testUsingContext('detects and recreates a plugin project correctly', () async { await projectDir.create(recursive: true); await projectDir.absolute.childFile('.metadata').writeAsString('project_type: plugin\n'); - return _createAndAnalyzeProject( + await _createAndAnalyzeProject( projectDir, + <String>[], <String>[ - '-i', 'objc', '-a', 'java', - ], - <String>[ - 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java', - 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java', - 'example/ios/Runner/AppDelegate.h', - 'example/ios/Runner/AppDelegate.m', - 'example/ios/Runner/main.m', 'example/lib/main.dart', 'flutter_project.iml', - 'ios/Classes/FlutterProjectPlugin.h', - 'ios/Classes/FlutterProjectPlugin.m', 'lib/flutter_project.dart', ], + unexpectedPaths: <String>[ + 'android/app/src/main/java/com/example/flutter_project/MainActivity.java', + 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java', + 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java',] ); }, overrides: <Type, Generator>{ Pub: () => Pub( @@ -380,19 +376,16 @@ projectDir, <String>['--template=plugin', '-i', 'objc', '-a', 'java'], <String>[ - 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java', - 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java', - 'example/ios/Runner/AppDelegate.h', - 'example/ios/Runner/AppDelegate.m', - 'example/ios/Runner/main.m', 'example/lib/main.dart', 'flutter_project.iml', - 'ios/Classes/FlutterProjectPlugin.h', - 'ios/Classes/FlutterProjectPlugin.m', 'lib/flutter_project.dart', ], unexpectedPaths: <String>[ + 'android/src/main/java/com/example/flutter_project/FlutterProjectPlugin.java', + 'example/android/app/src/main/java/com/example/flutter_project_example/MainActivity.java', 'lib/flutter_project_web.dart', + // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release + // https://github.com/flutter/flutter/issues/59787 ], ); return _runFlutterTest(projectDir.childDirectory('example')); @@ -410,7 +403,7 @@ testUsingContext('plugin project supports web', () async { await _createAndAnalyzeProject( projectDir, - <String>['--template=plugin'], + <String>['--template=plugin', '--platforms=web'], <String>[ 'lib/flutter_project.dart', 'lib/flutter_project_web.dart', @@ -464,7 +457,7 @@ testUsingContext('kotlin/swift plugin project', () async { return _createProject( projectDir, - <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift'], + <String>['--no-pub', '--template=plugin', '-a', 'kotlin', '--ios-language', 'swift', '--platforms', 'ios,android'], <String>[ 'android/src/main/kotlin/com/example/flutter_project/FlutterProjectPlugin.kt', 'example/android/app/src/main/kotlin/com/example/flutter_project_example/MainActivity.kt', @@ -495,6 +488,7 @@ '--org', 'com.bar.foo', '-i', 'objc', '-a', 'java', + '--platforms', 'android', ], <String>[ 'android/src/main/java/com/bar/foo/flutter_project/FlutterProjectPlugin.java', 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java', @@ -515,6 +509,7 @@ '--project-name', 'xyz', '-i', 'objc', '-a', 'java', + '--platforms', 'android,ios', ], <String>[ 'android/src/main/java/com/example/xyz/XyzPlugin.java', 'example/android/app/src/main/java/com/example/xyz_example/MainActivity.java', @@ -529,7 +524,7 @@ testUsingContext('plugin project with invalid custom project name', () async { expect( () => _createProject(projectDir, - <String>['--no-pub', '--template=plugin', '--project-name', 'xyz.xyz'], + <String>['--no-pub', '--template=plugin', '--project-name', 'xyz.xyz', '--platforms', 'android,ios',], <String>[], ), throwsToolExit(message: '"xyz.xyz" is not a valid Dart package name.'), @@ -642,7 +637,7 @@ final CreateCommand command = CreateCommand(); final CommandRunner<void> runner = createTestCommandRunner(command); - await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms', 'android', projectDir.path]); void expectExists(String relPath) { expect(globals.fs.isFileSync('${projectDir.path}/$relPath'), true); @@ -693,9 +688,14 @@ final CreateCommand command = CreateCommand(); final CommandRunner<void> runner = createTestCommandRunner(command); - await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]); expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'linux', + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform']); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), }); @@ -711,6 +711,7 @@ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), }); @@ -738,9 +739,14 @@ final CreateCommand command = CreateCommand(); final CommandRunner<void> runner = createTestCommandRunner(command); - await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]); expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'macos', + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform']); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), }); @@ -756,6 +762,7 @@ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); expect(projectDir.childDirectory('macos').childFile('flutter_project.podspec').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false), }); @@ -798,9 +805,14 @@ final CreateCommand command = CreateCommand(); final CommandRunner<void> runner = createTestCommandRunner(command); - await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]); expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'windows' + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform']); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); @@ -816,6 +828,7 @@ await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false); }, overrides: <Type, Generator>{ FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false), }); @@ -1269,6 +1282,7 @@ '--org', 'com.bar.foo', '-i', 'objc', '-a', 'java', + '--platforms', 'ios,android' ], <String>[], ); @@ -1276,7 +1290,7 @@ projectDir.childDirectory('ios').deleteSync(recursive: true); await _createProject( projectDir, - <String>['--no-pub', '--template=plugin', '-i', 'objc', '-a', 'java'], + <String>['--no-pub', '--template=plugin', '-i', 'objc', '-a', 'java', '--platforms', 'ios,android'], <String>[ 'example/android/app/src/main/java/com/bar/foo/flutter_project_example/MainActivity.java', 'ios/Classes/FlutterProjectPlugin.h', @@ -1506,6 +1520,306 @@ HttpClientFactory: () => () => MockHttpClient(404, result: 'not found'), }); + + testUsingContext('plugin does not support any platform by default', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + + // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release + // https://github.com/flutter/flutter/issues/59787 + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('android').existsSync(), false); + expect(projectDir.childDirectory('web').existsSync(), false); + expect(projectDir.childDirectory('linux').existsSync(), false); + expect(projectDir.childDirectory('windows').existsSync(), false); + expect(projectDir.childDirectory('macos').existsSync(), false); + + // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release + // https://github.com/flutter/flutter/issues/59787 + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('web').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false); + expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), false); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[ + 'some_platform' + ], pluginClass: 'somePluginClass', + unexpectedPlatforms: <String>[ 'ios', 'android', 'web', 'linux', 'windows', 'macos']); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), + }); + + + // TODO(cyanglaz): no-op iOS folder should be removed after 1.20.0 release + // https://github.com/flutter/flutter/issues/59787 + testUsingContext('create an empty plugin contains a no-op ios folder, but no pubspec entry.', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'some_platform' + ], pluginClass: 'somePluginClass', + unexpectedPlatforms: <String>['ios']); + }); + + testUsingContext('plugin supports ios if requested', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]); + + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: <String>[ + 'ios', + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform']); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), + }); + + testUsingContext('plugin supports android if requested', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]); + + expect(projectDir.childDirectory('android').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'android' + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform'], + androidIdentifier: 'com.example.flutter_project'); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), + }); + + testUsingContext('plugin supports web if requested', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]); + expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'web' + ], pluginClass: 'FlutterProjectWeb', + unexpectedPlatforms: <String>['some_platform'], + androidIdentifier: 'com.example.flutter_project', + webFileName: 'flutter_project_web.dart'); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), + }); + + testUsingContext('plugin doe not support web if feature is not enabled', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]); + expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), false); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'some_platform' + ], pluginClass: 'somePluginClass', + unexpectedPlatforms: <String>['web']); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isWebEnabled: false), + }); + + testUsingContext('create an empty plugin, then add ios', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]); + + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), + }); + + testUsingContext('create an empty plugin, then add android', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=android', projectDir.path]); + + expect(projectDir.childDirectory('android').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false), + }); + + testUsingContext('create an empty plugin, then add linux', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=linux', projectDir.path]); + + expect(projectDir.childDirectory('linux').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('linux').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), + }); + + testUsingContext('create an empty plugin, then add macos', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]); + + expect(projectDir.childDirectory('macos').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + }); + + testUsingContext('create an empty plugin, then add windows', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]); + + expect(projectDir.childDirectory('windows').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); + + testUsingContext('create an empty plugin, then add web', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=web', projectDir.path]); + + expect(projectDir.childDirectory('lib').childFile('flutter_project_web.dart').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), + }); + + testUsingContext('create a plugin with ios, then add macos', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios', projectDir.path]); + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'ios', + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform']); + + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=macos', projectDir.path]); + expect(projectDir.childDirectory('macos').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('macos').existsSync(), true); + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + }, overrides: <Type, Generator>{ + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + }); + + testUsingContext('create a plugin with ios and android', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=ios,android', projectDir.path]); + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + + expect(projectDir.childDirectory('android').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('android').existsSync(), true); + expect(projectDir.childDirectory('ios').existsSync(), true); + expect(projectDir.childDirectory('example').childDirectory('ios').existsSync(), true); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ + 'ios', 'android' + ], pluginClass: 'FlutterProjectPlugin', + unexpectedPlatforms: <String>['some_platform'], + androidIdentifier: 'com.example.flutter_project'); + }); + + testUsingContext('create a module with --platforms throws error.', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await expectLater( + runner.run(<String>['create', '--no-pub', '--template=module', '--platforms=ios', projectDir.path]) + , throwsToolExit(message: 'The "--platforms" argument is not supported', exitCode:2)); + }); + + testUsingContext('create a package with --platforms throws error.', () async { + Cache.flutterRoot = '../..'; + when(mockFlutterVersion.frameworkRevision).thenReturn(frameworkRevision); + when(mockFlutterVersion.channel).thenReturn(frameworkChannel); + + final CreateCommand command = CreateCommand(); + final CommandRunner<void> runner = createTestCommandRunner(command); + await expectLater( + runner.run(<String>['create', '--no-pub', '--template=package', '--platforms=ios', projectDir.path]) + , throwsToolExit(message: 'The "--platforms" argument is not supported', exitCode: 2)); + }); + } Future<void> _createProject(
diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart index edd3eb9..fd4b612 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
@@ -375,7 +375,7 @@ testUsingContext('get fetches packages and injects plugin in plugin project', () async { final String projectPath = await createProject( tempDir, - arguments: <String>['--template=plugin', '--no-pub'], + arguments: <String>['--template=plugin', '--no-pub', '--platforms=ios,android'], ); final String exampleProjectPath = globals.fs.path.join(projectPath, 'example'); removeGeneratedFiles(projectPath);
diff --git a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart index 4d11c5e..f3207d5 100644 --- a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart
@@ -900,6 +900,49 @@ expect(flutterManifest.isEmpty, false); }); + + testWithoutContext('FlutterManifest getSupportedPlatforms return null if runs on legacy format', () { + const String manifest = ''' +name: test +flutter: + plugin: + androidPackage: com.example +'''; + final BufferLogger logger = BufferLogger.test(); + final FlutterManifest flutterManifest = FlutterManifest.createFromString( + manifest, + logger: logger, + ); + + expect(flutterManifest.isPlugin, true); + expect(flutterManifest.supportedPlatforms, null); + }); + + testWithoutContext('FlutterManifest getSupportedPlatforms returns valid platforms.', () { + const String manifest = ''' +name: test +flutter: + plugin: + platforms: + android: + package: com.example + pluginClass: SomeClass + ios: + pluginClass: SomeClass +'''; + final BufferLogger logger = BufferLogger.test(); + final FlutterManifest flutterManifest = FlutterManifest.createFromString( + manifest, + logger: logger, + ); + + expect(flutterManifest.isPlugin, true); + expect(flutterManifest.supportedPlatforms['ios'], + <String, dynamic>{'pluginClass': 'SomeClass'}); + expect(flutterManifest.supportedPlatforms['android'], + <String, dynamic>{'pluginClass': 'SomeClass', + 'package': 'com.example'}); + }); } Matcher matchesManifest({
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index cddb3cf..f3f4506 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -10,15 +10,18 @@ import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/dart/package_map.dart'; import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/plugins.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; +import 'package:yaml/yaml.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/pubspec_schema.dart'; void main() { group('plugins', () { @@ -109,16 +112,14 @@ ..writeAsStringSync('apackage:file://${dummyPackageDirectory.path}\n'); }); - // Makes the dummy package pointed to by packagesFile look like a plugin. - void configureDummyPackageAsPlugin() { - dummyPackageDirectory.parent.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync(''' + const String _pluginYaml = ''' flutter: plugin: platforms: ios: - pluginClass: FLESomePlugin + pluginClass: SomePlugin macos: - pluginClass: FLESomePlugin + pluginClass: SomePlugin windows: pluginClass: SomePlugin linux: @@ -129,7 +130,10 @@ android: pluginClass: SomePlugin package: AndroidPackage - '''); + '''; + // Makes the dummy package pointed to by packagesFile look like a plugin. + void configureDummyPackageAsPlugin() { + dummyPackageDirectory.parent.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync(_pluginYaml); } void createNewJavaPlugin1() { @@ -1298,6 +1302,51 @@ FeatureFlags: () => featureFlags, }); }); + + group('pubspec', () { + + Directory projectDir; + Directory tempDir; + setUp(() { + tempDir = globals.fs.systemTempDirectory.createTempSync('plugin_test.'); + projectDir = tempDir.childDirectory('flutter_project'); + }); + + tearDown(() { + tryToDelete(tempDir); + }); + + void _createPubspecFile(String yamlString) { + projectDir.childFile('pubspec.yaml')..createSync(recursive: true)..writeAsStringSync(yamlString); + } + + test('validatePubspecForPlugin works', () async { + _createPubspecFile(_pluginYaml); + validatePubspecForPlugin(projectDir: projectDir.absolute.path, pluginClass: 'SomePlugin', expectedPlatforms: <String>[ + 'ios', 'macos', 'windows', 'linux', 'android', 'web' + ], androidIdentifier: 'AndroidPackage', webFileName: 'lib/SomeFile.dart'); + }); + + test('createPlatformsYamlMap should create the correct map', () async { + final YamlMap map = Plugin.createPlatformsYamlMap(<String>['ios', 'android', 'linux'], 'PluginClass', 'some.android.package'); + expect(map['ios'], <String, String> { + 'pluginClass' : 'PluginClass' + }); + expect(map['android'], <String, String> { + 'pluginClass' : 'PluginClass', + 'package': 'some.android.package', + }); + expect(map['linux'], <String, String> { + 'pluginClass' : 'PluginClass' + }); + }); + + test('createPlatformsYamlMap should create empty map', () async { + final YamlMap map = Plugin.createPlatformsYamlMap(<String>[], null, null); + expect(map.isEmpty, true); + }); + + }); }); }
diff --git a/packages/flutter_tools/test/src/pubspec_schema.dart b/packages/flutter_tools/test/src/pubspec_schema.dart index 730bd74..b9dd1ef 100644 --- a/packages/flutter_tools/test/src/pubspec_schema.dart +++ b/packages/flutter_tools/test/src/pubspec_schema.dart
@@ -5,6 +5,10 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/flutter_manifest.dart'; +import 'package:meta/meta.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:yaml/yaml.dart'; +import 'common.dart'; /// Writes a schemaData used for validating pubspec.yaml files when parsing /// asset information. @@ -22,3 +26,22 @@ void writeEmptySchemaFile(FileSystem filesystem) { writeSchemaFile(filesystem, '{}'); } + +/// Check if the pubspec.yaml file under the `projectDir` is valid for a plugin project. +void validatePubspecForPlugin({@required String projectDir, @required String pluginClass, @required List<String> expectedPlatforms, List<String> unexpectedPlatforms = const <String>[], String androidIdentifier, String webFileName}) { + final FlutterManifest manifest = FlutterManifest.createFromPath(projectDir+'/pubspec.yaml', fileSystem: globals.fs, logger: globals.logger); + final YamlMap platformsMap = YamlMap.wrap(manifest.supportedPlatforms); + for (final String platform in expectedPlatforms) { + expect(platformsMap[platform], isNotNull); + expect(platformsMap[platform]['pluginClass'], pluginClass); + if (platform == 'android') { + expect(platformsMap[platform]['package'], androidIdentifier); + } + if (platform == 'web') { + expect(platformsMap[platform]['fileName'], webFileName); + } + } + for (final String platform in unexpectedPlatforms) { + expect(platformsMap[platform], isNull); + } +}