Reland the Dart plugin registry (#79669)
diff --git a/dev/devicelab/bin/tasks/dart_plugin_registry_test.dart b/dev/devicelab/bin/tasks/dart_plugin_registry_test.dart new file mode 100644 index 0000000..50c50ae --- /dev/null +++ b/dev/devicelab/bin/tasks/dart_plugin_registry_test.dart
@@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_devicelab/tasks/dart_plugin_registry_tests.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future<void> main() async { + await task(dartPluginRegistryTest()); +}
diff --git a/dev/devicelab/lib/tasks/dart_plugin_registry_tests.dart b/dev/devicelab/lib/tasks/dart_plugin_registry_tests.dart new file mode 100644 index 0000000..444ddee --- /dev/null +++ b/dev/devicelab/lib/tasks/dart_plugin_registry_tests.dart
@@ -0,0 +1,179 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/task_result.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; + +TaskFunction dartPluginRegistryTest({ + String deviceIdOverride, + Map<String, String> environment, +}) { + final Directory tempDir = Directory.systemTemp + .createTempSync('flutter_devicelab_dart_plugin_test.'); + return () async { + try { + section('Create implementation plugin'); + await inDirectory(tempDir, () async { + await flutter( + 'create', + options: <String>[ + '--template=plugin', + '--org', + 'io.flutter.devicelab', + '--platforms', + 'macos', + 'plugin_platform_implementation', + ], + environment: environment, + ); + }); + + final File pluginMain = File(path.join( + tempDir.absolute.path, + 'plugin_platform_implementation', + 'lib', + 'plugin_platform_implementation.dart', + )); + if (!pluginMain.existsSync()) { + return TaskResult.failure('${pluginMain.path} does not exist'); + } + + // Patch plugin main dart file. + await pluginMain.writeAsString(''' +class PluginPlatformInterfaceMacOS { + static void registerWith() { + print('PluginPlatformInterfaceMacOS.registerWith() was called'); + } +} +''', flush: true); + + // Patch plugin main pubspec file. + final File pluginImplPubspec = File(path.join( + tempDir.absolute.path, + 'plugin_platform_implementation', + 'pubspec.yaml', + )); + String pluginImplPubspecContent = await pluginImplPubspec.readAsString(); + pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst( + ' pluginClass: PluginPlatformImplementationPlugin', + ' pluginClass: PluginPlatformImplementationPlugin\n' + ' dartPluginClass: PluginPlatformInterfaceMacOS\n', + ); + pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst( + ' platforms:\n', + ' implements: plugin_platform_interface\n' + ' platforms:\n'); + await pluginImplPubspec.writeAsString(pluginImplPubspecContent, + flush: true); + + section('Create interface plugin'); + await inDirectory(tempDir, () async { + await flutter( + 'create', + options: <String>[ + '--template=plugin', + '--org', + 'io.flutter.devicelab', + '--platforms', + 'macos', + 'plugin_platform_interface', + ], + environment: environment, + ); + }); + final File pluginInterfacePubspec = File(path.join( + tempDir.absolute.path, + 'plugin_platform_interface', + 'pubspec.yaml', + )); + String pluginInterfacePubspecContent = + await pluginInterfacePubspec.readAsString(); + pluginInterfacePubspecContent = + pluginInterfacePubspecContent.replaceFirst( + ' pluginClass: PluginPlatformInterfacePlugin', + ' default_package: plugin_platform_implementation\n'); + pluginInterfacePubspecContent = + pluginInterfacePubspecContent.replaceFirst( + 'dependencies:', + 'dependencies:\n' + ' plugin_platform_implementation:\n' + ' path: ../plugin_platform_implementation\n'); + await pluginInterfacePubspec.writeAsString(pluginInterfacePubspecContent, + flush: true); + + section('Create app'); + + await inDirectory(tempDir, () async { + await flutter( + 'create', + options: <String>[ + '--template=app', + '--org', + 'io.flutter.devicelab', + '--platforms', + 'macos', + 'app', + ], + environment: environment, + ); + }); + + final File appPubspec = File(path.join( + tempDir.absolute.path, + 'app', + 'pubspec.yaml', + )); + String appPubspecContent = await appPubspec.readAsString(); + appPubspecContent = appPubspecContent.replaceFirst( + 'dependencies:', + 'dependencies:\n' + ' plugin_platform_interface:\n' + ' path: ../plugin_platform_interface\n'); + await appPubspec.writeAsString(appPubspecContent, flush: true); + + section('Flutter run for macos'); + + await inDirectory(path.join(tempDir.path, 'app'), () async { + final Process run = await startProcess( + path.join(flutterDirectory.path, 'bin', 'flutter'), + flutterCommandArgs('run', <String>['-d', 'macos', '-v']), + environment: null, + ); + Completer<void> registryExecutedCompleter = Completer<void>(); + final StreamSubscription<void> subscription = run.stdout + .transform<String>(utf8.decoder) + .transform<String>(const LineSplitter()) + .listen((String line) { + if (line.contains( + 'PluginPlatformInterfaceMacOS.registerWith() was called')) { + registryExecutedCompleter.complete(); + } + print('stdout: $line'); + }); + + section('Wait for registry execution'); + await registryExecutedCompleter.future; + + // Hot restart. + run.stdin.write('R'); + registryExecutedCompleter = Completer<void>(); + + section('Wait for registry execution after hot restart'); + await registryExecutedCompleter.future; + + subscription.cancel(); + run.kill(); + }); + return TaskResult.success(null); + } finally { + rmTree(tempDir); + } + }; +}
diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart index de7c1c0..542f8b8 100644 --- a/packages/flutter_tools/lib/src/build_system/build_system.dart +++ b/packages/flutter_tools/lib/src/build_system/build_system.dart
@@ -332,6 +332,7 @@ @required ProcessManager processManager, @required Platform platform, @required String engineVersion, + @required bool generateDartPluginRegistry, Directory buildDir, Map<String, String> defines = const <String, String>{}, Map<String, String> inputs = const <String, String>{}, @@ -372,6 +373,7 @@ platform: platform, engineVersion: engineVersion, inputs: inputs, + generateDartPluginRegistry: generateDartPluginRegistry, ); } @@ -389,6 +391,7 @@ Map<String, String> inputs = const <String, String>{}, String engineVersion, Platform platform, + bool generateDartPluginRegistry = false, @required FileSystem fileSystem, @required Logger logger, @required Artifacts artifacts, @@ -408,6 +411,7 @@ processManager: processManager, platform: platform ?? FakePlatform(), engineVersion: engineVersion, + generateDartPluginRegistry: generateDartPluginRegistry, ); } @@ -426,6 +430,7 @@ @required this.artifacts, @required this.engineVersion, @required this.inputs, + @required this.generateDartPluginRegistry, }); /// The [Source] value which is substituted with the path to [projectDir]. @@ -505,6 +510,11 @@ /// The version of the current engine, or `null` if built with a local engine. final String engineVersion; + + /// Whether to generate the Dart plugin registry. + /// When [true], the main entrypoint is wrapped and the wrapper becomes + /// the new entrypoint. + final bool generateDartPluginRegistry; } /// The result information from the build system.
diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index 2a7ce7b..ba55b4e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart
@@ -17,6 +17,7 @@ import '../depfile.dart'; import '../exceptions.dart'; import 'assets.dart'; +import 'dart_plugin_registrant.dart'; import 'icon_tree_shaker.dart'; import 'localizations.dart'; @@ -209,6 +210,7 @@ @override List<Target> get dependencies => const <Target>[ GenerateLocalizationsTarget(), + DartPluginRegistrantTarget(), ]; @override @@ -286,6 +288,8 @@ fileSystemScheme: fileSystemScheme, dartDefines: decodeDartDefines(environment.defines, kDartDefines), packageConfig: packageConfig, + buildDir: environment.buildDir, + checkDartPluginRegistry: environment.generateDartPluginRegistry, ); if (output == null || output.errorCount != 0) { throw Exception();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart new file mode 100644 index 0000000..553cefc --- /dev/null +++ b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart
@@ -0,0 +1,88 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:meta/meta.dart'; +import 'package:package_config/package_config.dart'; + +import '../../base/file_system.dart'; +import '../../dart/package_map.dart'; +import '../../flutter_plugins.dart'; +import '../../project.dart'; +import '../build_system.dart'; +import 'common.dart'; + +/// Generates a new `./dart_tool/flutter_build/generated_main.dart` +/// based on the current dependency map in `pubspec.lock`. +class DartPluginRegistrantTarget extends Target { + /// Construct a [DartPluginRegistrantTarget]. + const DartPluginRegistrantTarget() : _project = null; + + /// Construct a [DartPluginRegistrantTarget]. + /// + /// If `project` is unset, a [FlutterProject] based on environment is used. + @visibleForTesting + factory DartPluginRegistrantTarget.test(FlutterProject project) { + return DartPluginRegistrantTarget._(project); + } + + DartPluginRegistrantTarget._(this._project); + + final FlutterProject _project; + + @override + Future<void> build(Environment environment) async { + assert(environment.generateDartPluginRegistry); + final File packagesFile = environment.projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + packagesFile, + logger: environment.logger, + ); + final String targetFile = environment.defines[kTargetFile] ?? + environment.fileSystem.path.join('lib', 'main.dart'); + final File mainFile = environment.fileSystem.file(targetFile); + final Uri mainFileUri = mainFile.uri; + assert(packagesFile.path != null); + final String mainUri = packageConfig.toPackageUri(mainFileUri)?.toString(); + final File newMainDart = environment.projectDir + .childDirectory('.dart_tool') + .childDirectory('flutter_build') + .childFile('generated_main.dart'); + await generateMainDartWithPluginRegistrant( + _project ?? FlutterProject.fromDirectory(environment.projectDir), + packageConfig, + mainUri, + newMainDart, + mainFile, + throwOnPluginPubspecError: false, + ); + } + + @override + bool canSkip(Environment environment) { + return !environment.generateDartPluginRegistry; + } + + @override + List<Target> get dependencies => <Target>[]; + + @override + List<Source> get inputs => <Source>[ + const Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'), + ]; + + @override + String get name => 'gen_dart_plugin_registrant'; + + @override + List<Source> get outputs => <Source>[ + const Source.pattern( + '{PROJECT_DIR}/.dart_tool/flutter_build/generated_main.dart', + optional: true, + ), + ]; +}
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart index 2423ca9..ab464f6 100644 --- a/packages/flutter_tools/lib/src/bundle.dart +++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -137,6 +137,7 @@ logger: globals.logger, processManager: globals.processManager, platform: globals.platform, + generateDartPluginRegistry: true, ); final Target target = buildInfo.mode == BuildMode.debug ? const CopyFlutterBundle()
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index c591ed3..4e2ecb1 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -246,7 +246,8 @@ platform: globals.platform, engineVersion: globals.artifacts.isLocalEngine ? null - : globals.flutterVersion.engineRevision + : globals.flutterVersion.engineRevision, + generateDartPluginRegistry: true, ); return result; }
diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index 1dfce6a..1269886 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart
@@ -385,6 +385,7 @@ engineVersion: globals.artifacts.isLocalEngine ? null : globals.flutterVersion.engineRevision, + generateDartPluginRegistry: true, ); Target target; // Always build debug for simulator.
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index d09caea..02a7ccc 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -120,6 +120,7 @@ processManager: globals.processManager, platform: globals.platform, projectDir: flutterProject.directory, + generateDartPluginRegistry: true, ); await generateLocalizationsSyntheticPackage( @@ -325,6 +326,7 @@ processManager: globals.processManager, platform: globals.platform, projectDir: flutterProject.directory, + generateDartPluginRegistry: true, ); await generateLocalizationsSyntheticPackage(
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 1a32d5e..76ba679 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart
@@ -229,6 +229,8 @@ String fileSystemScheme, String initializeFromDill, String platformDill, + Directory buildDir, + bool checkDartPluginRegistry = false, @required String packagesPath, @required BuildMode buildMode, @required bool trackWidgetCreation, @@ -247,7 +249,8 @@ throwToolExit('Unable to find Dart binary at $engineDartPath'); } String mainUri; - final Uri mainFileUri = _fileSystem.file(mainPath).uri; + final File mainFile = _fileSystem.file(mainPath); + final Uri mainFileUri = mainFile.uri; if (packagesPath != null) { mainUri = packageConfig.toPackageUri(mainFileUri)?.toString(); } @@ -255,6 +258,15 @@ if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) { _fileSystem.file(outputFilePath).createSync(recursive: true); } + if (buildDir != null && checkDartPluginRegistry) { + // Check if there's a Dart plugin registrant. + // This is contained in the file `generated_main.dart` under `.dart_tools/flutter_build/`. + final File newMainDart = buildDir.parent.childFile('generated_main.dart'); + if (newMainDart.existsSync()) { + mainUri = newMainDart.path; + } + } + final List<String> command = <String>[ engineDartPath, '--disable-dart-dev', @@ -457,6 +469,8 @@ List<Uri> invalidatedFiles, { @required String outputPath, @required PackageConfig packageConfig, + @required String projectRootPath, + @required FileSystem fs, bool suppressErrors = false, }); @@ -596,12 +610,27 @@ @required String outputPath, @required PackageConfig packageConfig, bool suppressErrors = false, + String projectRootPath, + FileSystem fs, }) async { assert(outputPath != null); if (!_controller.hasListener) { _controller.stream.listen(_handleCompilationRequest); } - + // `generated_main.dart` contains the Dart plugin registry. + if (projectRootPath != null && fs != null) { + final File generatedMainDart = fs.file( + fs.path.join( + projectRootPath, + '.dart_tool', + 'flutter_build', + 'generated_main.dart', + ), + ); + if (generatedMainDart != null && generatedMainDart.existsSync()) { + mainUri = generatedMainDart.uri; + } + } final Completer<CompilerOutput> completer = Completer<CompilerOutput>(); _controller.add( _RecompileRequest(completer, mainUri, invalidatedFiles, outputPath, packageConfig, suppressErrors)
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index b2e6652..62b1176 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -524,6 +524,8 @@ mainUri, invalidatedFiles, outputPath: dillOutputPath, + fs: _fileSystem, + projectRootPath: projectRootPath, packageConfig: packageConfig, ); if (bundle != null) {
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index 67c38c6..7c905f4 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -75,6 +75,13 @@ /// The string value of the top-level `name` property in the `pubspec.yaml` file. String get appName => _descriptor['name'] as String? ?? ''; + /// Contains the name of the dependencies. + /// These are the keys specified in the `dependency` map. + Set<String> get dependencies { + final YamlMap? dependencies = _descriptor['dependencies'] as YamlMap?; + return dependencies != null ? <String>{...dependencies.keys.cast<String>()} : <String>{}; + } + // Flag to avoid printing multiple invalid version messages. bool _hasShowInvalidVersionMsg = false;
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index a641ab9..9935e6e 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -17,7 +17,9 @@ import 'base/platform.dart'; import 'base/template.dart'; import 'base/version.dart'; +import 'cache.dart'; import 'convert.dart'; +import 'dart/language_version.dart'; import 'dart/package_map.dart'; import 'features.dart'; import 'globals.dart' as globals; @@ -32,15 +34,16 @@ file.writeAsStringSync(renderedTemplate); } -Plugin _pluginFromPackage(String name, Uri packageRoot) { - final String pubspecPath = globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml')); - if (!globals.fs.isFileSync(pubspecPath)) { +Plugin _pluginFromPackage(String name, Uri packageRoot, Set<String> appDependencies, {FileSystem fileSystem}) { + final FileSystem fs = fileSystem ?? globals.fs; + final String pubspecPath = fs.path.fromUri(packageRoot.resolve('pubspec.yaml')); + if (!fs.isFileSync(pubspecPath)) { return null; } dynamic pubspec; try { - pubspec = loadYaml(globals.fs.file(pubspecPath).readAsStringSync()); + pubspec = loadYaml(fs.file(pubspecPath).readAsStringSync()); } on YamlException catch (err) { globals.printTrace('Failed to parse plugin manifest for $name: $err'); // Do nothing, potentially not a plugin. @@ -52,7 +55,7 @@ if (flutterConfig == null || !(flutterConfig.containsKey('plugin') as bool)) { return null; } - final String packageRootPath = globals.fs.path.fromUri(packageRoot); + final String packageRootPath = fs.path.fromUri(packageRoot); final YamlMap dependencies = pubspec['dependencies'] as YamlMap; globals.printTrace('Found plugin $name at $packageRootPath'); return Plugin.fromYaml( @@ -60,24 +63,31 @@ packageRootPath, flutterConfig['plugin'] as YamlMap, dependencies == null ? <String>[] : <String>[...dependencies.keys.cast<String>()], - fileSystem: globals.fs, + fileSystem: fs, + appDependencies: appDependencies, ); } Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = true}) async { final List<Plugin> plugins = <Plugin>[]; - final String packagesFile = globals.fs.path.join( + final FileSystem fs = project.directory.fileSystem; + final String packagesFile = fs.path.join( project.directory.path, '.packages', ); final PackageConfig packageConfig = await loadPackageConfigWithLogging( - globals.fs.file(packagesFile), + fs.file(packagesFile), logger: globals.logger, throwOnError: throwOnError, ); for (final Package package in packageConfig.packages) { final Uri packageRoot = package.packageUriRoot.resolve('..'); - final Plugin plugin = _pluginFromPackage(package.name, packageRoot); + final Plugin plugin = _pluginFromPackage( + package.name, + packageRoot, + project.manifest.dependencies, + fileSystem: fs + ); if (plugin != null) { plugins.add(plugin); } @@ -619,6 +629,74 @@ endforeach(plugin) '''; +const String _dartPluginRegisterWith = r''' + try { + {{dartClass}}.registerWith(); + } catch (err) { + print( + '`{{pluginName}}` threw an error: $err. ' + 'The app may not function as expected until you remove this plugin from pubspec.yaml' + ); + rethrow; + } +'''; + +// TODO(egarciad): Evaluate merging the web and desktop plugin registry templates. +// https://github.com/flutter/flutter/issues/80406 +const String _dartPluginRegistryForDesktopTemplate = ''' +// +// Generated file. Do not edit. +// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`. +// + +// @dart = {{dartLanguageVersion}} + +import '{{mainEntrypoint}}' as entrypoint; +import 'dart:io'; // flutter_ignore: dart_io_import. +{{#linux}} +import 'package:{{pluginName}}/{{pluginName}}.dart'; +{{/linux}} +{{#macos}} +import 'package:{{pluginName}}/{{pluginName}}.dart'; +{{/macos}} +{{#windows}} +import 'package:{{pluginName}}/{{pluginName}}.dart'; +{{/windows}} + +@pragma('vm:entry-point') +class _PluginRegistrant { + + @pragma('vm:entry-point') + static void register() { + if (Platform.isLinux) { + {{#linux}} +$_dartPluginRegisterWith + {{/linux}} + } else if (Platform.isMacOS) { + {{#macos}} +$_dartPluginRegisterWith + {{/macos}} + } else if (Platform.isWindows) { + {{#windows}} +$_dartPluginRegisterWith + {{/windows}} + } + } + +} + +typedef _UnaryFunction = dynamic Function(List<String> args); +typedef _NullaryFunction = dynamic Function(); + +void main(List<String> args) { + if (entrypoint.main is _UnaryFunction) { + (entrypoint.main as _UnaryFunction)(args); + } else { + (entrypoint.main as _NullaryFunction)(); + } +} +'''; + Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { final List<Map<String, dynamic>> iosPlugins = _extractPlatformMaps(plugins, IOSPlugin.kConfigKey); final Map<String, dynamic> context = <String, dynamic>{ @@ -979,3 +1057,180 @@ bool hasPlugins(FlutterProject project) { return _readFileContent(project.flutterPluginsFile) != null; } + +/// Resolves the platform implementation for Dart-only plugins. +/// +/// * If there are multiple direct pub dependencies on packages that implement the +/// frontend plugin for the current platform, fail. +/// * If there is a single direct dependency on a package that implements the +/// frontend plugin for the target platform, this package is the selected implementation. +/// * If there is no direct dependency on a package that implements the frontend +/// plugin for the target platform, and the frontend plugin has a default implementation +/// for the target platform the default implementation is selected. +/// * Else fail. +/// +/// For more details, https://flutter.dev/go/federated-plugins. +List<PluginInterfaceResolution> resolvePlatformImplementation( + List<Plugin> plugins, { + bool throwOnPluginPubspecError = true, +}) { + final List<String> platforms = <String>[ + LinuxPlugin.kConfigKey, + MacOSPlugin.kConfigKey, + WindowsPlugin.kConfigKey, + ]; + final Map<String, PluginInterfaceResolution> directDependencyResolutions + = <String, PluginInterfaceResolution>{}; + final Map<String, String> defaultImplementations = <String, String>{}; + bool didFindError = false; + + for (final Plugin plugin in plugins) { + for (final String platform in platforms) { + // The plugin doesn't implement this platform. + if (plugin.platforms[platform] == null && + plugin.defaultPackagePlatforms[platform] == null) { + continue; + } + // The plugin doesn't implement an interface, verify that it has a default implementation. + if (plugin.implementsPackage == null || plugin.implementsPackage.isEmpty) { + final String defaultImplementation = plugin.defaultPackagePlatforms[platform]; + if (defaultImplementation == null) { + if (throwOnPluginPubspecError) { + globals.printError( + 'Plugin `${plugin.name}` doesn\'t implement a plugin interface, nor sets ' + 'a default implementation in pubspec.yaml.\n\n' + 'To set a default implementation, use:\n' + 'flutter:\n' + ' plugin:\n' + ' platforms:\n' + ' $platform:\n' + ' $kDefaultPackage: <plugin-implementation>\n' + '\n' + 'To implement an interface, use:\n' + 'flutter:\n' + ' plugin:\n' + ' implements: <plugin-interface>' + '\n' + ); + } + didFindError = true; + continue; + } + defaultImplementations['$platform/${plugin.name}'] = defaultImplementation; + continue; + } + if (plugin.pluginDartClassPlatforms[platform] == null || + plugin.pluginDartClassPlatforms[platform] == 'none') { + continue; + } + final String resolutionKey = '$platform/${plugin.implementsPackage}'; + if (directDependencyResolutions.containsKey(resolutionKey)) { + final PluginInterfaceResolution currResolution = directDependencyResolutions[resolutionKey]; + if (currResolution != null && currResolution.plugin.isDirectDependency) { + if (plugin.isDirectDependency) { + if (throwOnPluginPubspecError) { + globals.printError( + 'Plugin `${plugin.name}` implements an interface for `$platform`, which was already ' + 'implemented by plugin `${currResolution.plugin.name}`.\n' + 'To fix this issue, remove either dependency from pubspec.yaml.' + '\n\n' + ); + } + didFindError = true; + } + // Use the plugin implementation added by the user as a direct dependency. + continue; + } + } + directDependencyResolutions[resolutionKey] = PluginInterfaceResolution( + plugin: plugin, + platform: platform, + ); + } + } + if (didFindError && throwOnPluginPubspecError) { + throwToolExit('Please resolve the errors'); + } + final List<PluginInterfaceResolution> finalResolution = <PluginInterfaceResolution>[]; + for (final MapEntry<String, PluginInterfaceResolution> resolution in directDependencyResolutions.entries) { + if (resolution.value.plugin.isDirectDependency) { + finalResolution.add(resolution.value); + } else if (defaultImplementations.containsKey(resolution.key)) { + // Pick the default implementation. + if (defaultImplementations[resolution.key] == resolution.value.plugin.name) { + finalResolution.add(resolution.value); + } + } + } + return finalResolution; +} + +/// Generates the Dart plugin registrant, which allows to bind a platform +/// implementation of a Dart only plugin to its interface. +/// The new entrypoint wraps [currentMainUri], adds the [_PluginRegistrant] class, +/// and writes the file to [newMainDart]. +/// +/// [mainFile] is the main entrypoint file. e.g. /<app>/lib/main.dart. +/// +/// A successful run will create a new generate_main.dart file or update the existing file. +/// Throws [ToolExit] if unable to generate the file. +/// +/// This method also validates each plugin's pubspec.yaml, but errors are only +/// reported if [throwOnPluginPubspecError] is [true]. +/// +/// For more details, see https://flutter.dev/go/federated-plugins. +Future<void> generateMainDartWithPluginRegistrant( + FlutterProject rootProject, + PackageConfig packageConfig, + String currentMainUri, + File newMainDart, + File mainFile, { + bool throwOnPluginPubspecError, +}) async { + final List<Plugin> plugins = await findPlugins(rootProject); + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation( + plugins, + throwOnPluginPubspecError: throwOnPluginPubspecError ?? false, + ); + final LanguageVersion entrypointVersion = determineLanguageVersion( + mainFile, + packageConfig.packageOf(mainFile.absolute.uri), + Cache.flutterRoot, + ); + final Map<String, dynamic> templateContext = <String, dynamic>{ + 'mainEntrypoint': currentMainUri, + 'dartLanguageVersion': entrypointVersion.toString(), + LinuxPlugin.kConfigKey: <dynamic>[], + MacOSPlugin.kConfigKey: <dynamic>[], + WindowsPlugin.kConfigKey: <dynamic>[], + }; + if (resolutions.isEmpty) { + try { + if (newMainDart.existsSync()) { + newMainDart.deleteSync(); + } + } on FileSystemException catch (error) { + globals.printError( + 'Unable to remove ${newMainDart.path}, received error: $error.\n' + 'You might need to run flutter clean.' + ); + rethrow; + } + return; + } + for (final PluginInterfaceResolution resolution in resolutions) { + assert(templateContext.containsKey(resolution.platform)); + (templateContext[resolution.platform] as List<dynamic>).add(resolution.toMap()); + } + try { + _renderTemplateToFile( + _dartPluginRegistryForDesktopTemplate, + templateContext, + newMainDart, + globals.templateRenderer, + ); + } on FileSystemException catch (error) { + globals.printError('Unable to write ${newMainDart.path}, received error: $error'); + rethrow; + } +}
diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index ae5ac2d..04efb25 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart
@@ -835,6 +835,8 @@ invalidatedFiles, outputPath: dillOutputPath, packageConfig: packageConfig, + projectRootPath: projectRootPath, + fs: globals.fs, ); if (compilerOutput == null || compilerOutput.errorCount > 0) { return UpdateFSReport(success: false);
diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index 34e1619..dff6242 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart
@@ -13,6 +13,9 @@ /// Constant for 'pluginClass' key in plugin maps. const String kDartPluginClass = 'dartPluginClass'; +// Constant for 'defaultPackage' key in plugin maps. +const String kDefaultPackage = 'default_package'; + /// Marker interface for all platform specific plugin config implementations. abstract class PluginPlatform { const PluginPlatform(); @@ -204,6 +207,7 @@ required this.name, this.pluginClass, this.dartPluginClass, + this.defaultPackage, }); factory MacOSPlugin.fromYaml(String name, YamlMap yaml) { @@ -217,6 +221,7 @@ name: name, pluginClass: pluginClass, dartPluginClass: yaml[kDartPluginClass] as String?, + defaultPackage: yaml[kDefaultPackage] as String?, ); } @@ -224,7 +229,9 @@ if (yaml == null) { return false; } - return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String; + return yaml[kPluginClass] is String || + yaml[kDartPluginClass] is String || + yaml[kDefaultPackage] is String; } static const String kConfigKey = 'macos'; @@ -232,6 +239,7 @@ final String name; final String? pluginClass; final String? dartPluginClass; + final String? defaultPackage; @override bool isNative() => pluginClass != null; @@ -241,7 +249,8 @@ return <String, dynamic>{ 'name': name, if (pluginClass != null) 'class': pluginClass, - if (dartPluginClass != null) 'dartPluginClass': dartPluginClass, + if (dartPluginClass != null) kDartPluginClass : dartPluginClass, + if (defaultPackage != null) kDefaultPackage : defaultPackage, }; } } @@ -255,7 +264,8 @@ required this.name, this.pluginClass, this.dartPluginClass, - }) : assert(pluginClass != null || dartPluginClass != null); + this.defaultPackage, + }) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null); factory WindowsPlugin.fromYaml(String name, YamlMap yaml) { assert(validate(yaml)); @@ -268,6 +278,7 @@ name: name, pluginClass: pluginClass, dartPluginClass: yaml[kDartPluginClass] as String?, + defaultPackage: yaml[kDefaultPackage] as String?, ); } @@ -275,7 +286,9 @@ if (yaml == null) { return false; } - return yaml[kDartPluginClass] is String || yaml[kPluginClass] is String; + return yaml[kPluginClass] is String || + yaml[kDartPluginClass] is String || + yaml[kDefaultPackage] is String; } static const String kConfigKey = 'windows'; @@ -283,6 +296,7 @@ final String name; final String? pluginClass; final String? dartPluginClass; + final String? defaultPackage; @override bool isNative() => pluginClass != null; @@ -291,9 +305,10 @@ Map<String, dynamic> toMap() { return <String, dynamic>{ 'name': name, - if (pluginClass != null) 'class': pluginClass, + if (pluginClass != null) 'class': pluginClass!, if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass!), - if (dartPluginClass != null) 'dartPluginClass': dartPluginClass, + if (dartPluginClass != null) kDartPluginClass: dartPluginClass!, + if (defaultPackage != null) kDefaultPackage: defaultPackage!, }; } } @@ -307,7 +322,8 @@ required this.name, this.pluginClass, this.dartPluginClass, - }) : assert(pluginClass != null || dartPluginClass != null); + this.defaultPackage, + }) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null); factory LinuxPlugin.fromYaml(String name, YamlMap yaml) { assert(validate(yaml)); @@ -320,6 +336,7 @@ name: name, pluginClass: pluginClass, dartPluginClass: yaml[kDartPluginClass] as String?, + defaultPackage: yaml[kDefaultPackage] as String?, ); } @@ -327,7 +344,9 @@ if (yaml == null) { return false; } - return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String; + return yaml[kPluginClass] is String || + yaml[kDartPluginClass] is String || + yaml[kDefaultPackage] is String; } static const String kConfigKey = 'linux'; @@ -335,6 +354,7 @@ final String name; final String? pluginClass; final String? dartPluginClass; + final String? defaultPackage; @override bool isNative() => pluginClass != null; @@ -343,9 +363,10 @@ Map<String, dynamic> toMap() { return <String, dynamic>{ 'name': name, - if (pluginClass != null) 'class': pluginClass, + if (pluginClass != null) 'class': pluginClass!, if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass!), - if (dartPluginClass != null) 'dartPluginClass': dartPluginClass, + if (dartPluginClass != null) kDartPluginClass: dartPluginClass!, + if (defaultPackage != null) kDefaultPackage: defaultPackage!, }; } }
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index 1f8a0e5..2cba9ee 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -13,11 +13,18 @@ required this.name, required this.path, required this.platforms, + required this.defaultPackagePlatforms, + required this.pluginDartClassPlatforms, required this.dependencies, + required this.isDirectDependency, + this.implementsPackage, }) : assert(name != null), assert(path != null), assert(platforms != null), - assert(dependencies != null); + assert(defaultPackagePlatforms != null), + assert(pluginDartClassPlatforms != null), + assert(dependencies != null), + assert(isDirectDependency != null); /// Parses [Plugin] specification from the provided pluginYaml. /// @@ -53,15 +60,30 @@ YamlMap? pluginYaml, List<String> dependencies, { required FileSystem fileSystem, + Set<String>? appDependencies, }) { final List<String> errors = validatePluginYaml(pluginYaml); if (errors.isNotEmpty) { throwToolExit('Invalid plugin specification $name.\n${errors.join('\n')}'); } if (pluginYaml != null && pluginYaml['platforms'] != null) { - return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies, fileSystem); + return Plugin._fromMultiPlatformYaml( + name, + path, + pluginYaml, + dependencies, + fileSystem, + appDependencies != null && appDependencies.contains(name), + ); } - return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies, fileSystem); + return Plugin._fromLegacyYaml( + name, + path, + pluginYaml, + dependencies, + fileSystem, + appDependencies != null && appDependencies.contains(name), + ); } factory Plugin._fromMultiPlatformYaml( @@ -70,6 +92,7 @@ dynamic pluginYaml, List<String> dependencies, FileSystem fileSystem, + bool isDirectDependency, ) { assert (pluginYaml != null && pluginYaml['platforms'] != null, 'Invalid multi-platform plugin specification $name.'); @@ -114,11 +137,47 @@ WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey] as YamlMap); } + final String? defaultPackageForLinux = + _getDefaultPackageForPlatform(platformsYaml, LinuxPlugin.kConfigKey); + + final String? defaultPackageForMacOS = + _getDefaultPackageForPlatform(platformsYaml, MacOSPlugin.kConfigKey); + + final String? defaultPackageForWindows = + _getDefaultPackageForPlatform(platformsYaml, WindowsPlugin.kConfigKey); + + final String? defaultPluginDartClassForLinux = + _getPluginDartClassForPlatform(platformsYaml, LinuxPlugin.kConfigKey); + + final String? defaultPluginDartClassForMacOS = + _getPluginDartClassForPlatform(platformsYaml, MacOSPlugin.kConfigKey); + + final String? defaultPluginDartClassForWindows = + _getPluginDartClassForPlatform(platformsYaml, WindowsPlugin.kConfigKey); + return Plugin( name: name, path: path, platforms: platforms, + defaultPackagePlatforms: <String, String>{ + if (defaultPackageForLinux != null) + LinuxPlugin.kConfigKey : defaultPackageForLinux, + if (defaultPackageForMacOS != null) + MacOSPlugin.kConfigKey : defaultPackageForMacOS, + if (defaultPackageForWindows != null) + WindowsPlugin.kConfigKey : defaultPackageForWindows, + }, + pluginDartClassPlatforms: <String, String>{ + if (defaultPluginDartClassForLinux != null) + LinuxPlugin.kConfigKey : defaultPluginDartClassForLinux, + if (defaultPluginDartClassForMacOS != null) + MacOSPlugin.kConfigKey : defaultPluginDartClassForMacOS, + if (defaultPluginDartClassForWindows != null) + WindowsPlugin.kConfigKey : defaultPluginDartClassForWindows, + }, dependencies: dependencies, + isDirectDependency: isDirectDependency, + implementsPackage: pluginYaml['implements'] != null ? pluginYaml['implements'] as String : '', ); } @@ -128,6 +187,7 @@ dynamic pluginYaml, List<String> dependencies, FileSystem fileSystem, + bool isDirectDependency, ) { final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{}; final String pluginClass = pluginYaml['pluginClass'] as String; @@ -155,7 +215,10 @@ name: name, path: path, platforms: platforms, + defaultPackagePlatforms: <String, String>{}, + pluginDartClassPlatforms: <String, String>{}, dependencies: dependencies, + isDirectDependency: isDirectDependency, ); } @@ -271,11 +334,41 @@ return errors; } - static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) { + static bool _supportsPlatform(YamlMap platformsYaml, String platformKey) { if (!platformsYaml.containsKey(platformKey)) { return false; } - if ((platformsYaml[platformKey] as YamlMap).containsKey('default_package')) { + if (platformsYaml[platformKey] is YamlMap) { + return true; + } + return false; + } + + static String? _getDefaultPackageForPlatform(YamlMap platformsYaml, String platformKey) { + if (!_supportsPlatform(platformsYaml, platformKey)) { + return null; + } + if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) { + return (platformsYaml[platformKey] as YamlMap)[kDefaultPackage] as String; + } + return null; + } + + static String? _getPluginDartClassForPlatform(YamlMap platformsYaml, String platformKey) { + if (!_supportsPlatform(platformsYaml, platformKey)) { + return null; + } + if ((platformsYaml[platformKey] as YamlMap).containsKey(kDartPluginClass)) { + return (platformsYaml[platformKey] as YamlMap)[kDartPluginClass] as String; + } + return null; + } + + static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) { + if (!_supportsPlatform(platformsYaml, platformKey)) { + return false; + } + if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) { return false; } return true; @@ -284,9 +377,45 @@ final String name; final String path; + /// The name of the interface package that this plugin implements. + /// If [null], this plugin doesn't implement an interface. + final String? implementsPackage; + /// The name of the packages this plugin depends on. final List<String> dependencies; /// This is a mapping from platform config key to the plugin platform spec. final Map<String, PluginPlatform> platforms; + + /// This is a mapping from platform config key to the default package implementation. + final Map<String, String> defaultPackagePlatforms; + + /// This is a mapping from platform config key to the plugin class for the given platform. + final Map<String, String> pluginDartClassPlatforms; + + /// Whether this plugin is a direct dependency of the app. + /// If [false], the plugin is a dependency of another plugin. + final bool isDirectDependency; +} + +/// Metadata associated with the resolution of a platform interface of a plugin. +class PluginInterfaceResolution { + PluginInterfaceResolution({ + required this.plugin, + required this.platform, + }) : assert(plugin != null), + assert(platform != null); + + /// The plugin. + final Plugin plugin; + // The name of the platform that this plugin implements. + final String platform; + + Map<String, String> toMap() { + return <String, String> { + 'pluginName': plugin.name, + 'platform': platform, + 'dartClass': plugin.pluginDartClassPlatforms[platform] ?? '', + }; + } }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 5fec223..399d9de 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -26,6 +26,7 @@ import 'base/utils.dart'; import 'build_info.dart'; import 'build_system/build_system.dart'; +import 'build_system/targets/dart_plugin_registrant.dart'; import 'build_system/targets/localizations.dart'; import 'bundle.dart'; import 'cache.dart'; @@ -1211,9 +1212,16 @@ processManager: globals.processManager, platform: globals.platform, projectDir: globals.fs.currentDirectory, + generateDartPluginRegistry: true, ); - _lastBuild = await globals.buildSystem.buildIncremental( + + final CompositeTarget compositeTarget = CompositeTarget(<Target>[ const GenerateLocalizationsTarget(), + const DartPluginRegistrantTarget(), + ]); + + _lastBuild = await globals.buildSystem.buildIncremental( + compositeTarget, _environment, _lastBuild, );
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 7a34dbe..51ae4a8 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -26,6 +26,7 @@ import 'device.dart'; import 'features.dart'; import 'globals_null_migrated.dart' as globals; +import 'project.dart'; import 'reporting/reporting.dart'; import 'resident_devtools_handler.dart'; import 'resident_runner.dart'; @@ -314,6 +315,7 @@ bool enableDevTools = false, String route, }) async { + final File mainFile = globals.fs.file(mainPath); firstBuildTime = DateTime.now(); final List<Future<bool>> startupTasks = <Future<bool>>[]; @@ -326,7 +328,7 @@ if (device.generator != null) { startupTasks.add( device.generator.recompile( - globals.fs.file(mainPath).uri, + mainFile.uri, <Uri>[], // When running without a provided applicationBinary, the tool will // simultaneously run the initial frontend_server compilation and @@ -338,6 +340,8 @@ trackWidgetCreation: debuggingOptions.buildInfo.trackWidgetCreation, ), packageConfig: debuggingOptions.buildInfo.packageConfig, + projectRootPath: FlutterProject.current().directory.absolute.path, + fs: globals.fs, ).then((CompilerOutput output) => output?.errorCount == 0) ); }
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 32d341d..59deb60 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -1176,6 +1176,7 @@ processManager: globals.processManager, platform: globals.platform, projectDir: project.directory, + generateDartPluginRegistry: true, ); await generateLocalizationsSyntheticPackage(
diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart index 34e6f01..8b269bd 100644 --- a/packages/flutter_tools/lib/src/test/test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/test_compiler.dart
@@ -140,6 +140,8 @@ <Uri>[request.mainUri], outputPath: outputDill.path, packageConfig: buildInfo.packageConfig, + projectRootPath: flutterProject.directory.absolute.path, + fs: globals.fs, ); final String outputPath = compilerOutput?.outputFilename;
diff --git a/packages/flutter_tools/lib/src/test/web_test_compiler.dart b/packages/flutter_tools/lib/src/test/web_test_compiler.dart index 88047e9..8e8bd8f 100644 --- a/packages/flutter_tools/lib/src/test/web_test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/web_test_compiler.dart
@@ -138,6 +138,8 @@ <Uri>[], outputPath: outputDirectory.childFile('out').path, packageConfig: buildInfo.packageConfig, + fs: _fileSystem, + projectRootPath: projectDirectory.absolute.path, ); if (output.errorCount > 0) { throwToolExit('Failed to compile');
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 6a1d6fe..0a5ecf4 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -71,6 +71,9 @@ ? null : globals.flutterVersion.engineRevision, flutterRootDir: globals.fs.directory(Cache.flutterRoot), + // Web uses a different Dart plugin registry. + // https://github.com/flutter/flutter/issues/80406 + generateDartPluginRegistry: false, )); if (!result.success) { for (final ExceptionMeasurement measurement in result.exceptions.values) {
diff --git a/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart b/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart index 3ba668a..a240f24 100644 --- a/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart
@@ -38,6 +38,7 @@ processManager: globals.processManager, platform: FakePlatform(), engineVersion: 'invalidEngineVersion', + generateDartPluginRegistry: false, ); return result; }
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart new file mode 100644 index 0000000..3e50f9a --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart
@@ -0,0 +1,292 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/build_system/targets/common.dart'; +import 'package:flutter_tools/src/build_system/targets/dart_plugin_registrant.dart'; +import 'package:flutter_tools/src/project.dart'; + +import '../../../src/common.dart'; +import '../../../src/context.dart'; + +const String _kEmptyPubspecFile = ''' +name: app_without_plugins + +dependencies: + flutter: + sdk: flutter +'''; + +const String _kEmptyPackageJson = ''' +{ + "configVersion": 2, + "packages": [ + ] +} +'''; + +const String _kSamplePackageJson = ''' +{ + "configVersion": 2, + "packages": [ + { + "name": "path_provider_linux", + "rootUri": "/path_provider_linux", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "path_provider_example", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "2.12" + } + ] +} +'''; + +const String _kSamplePackagesFile = ''' +path_provider_linux:/path_provider_linux/lib/ +path_provider_example:lib/ +'''; + +const String _kSamplePubspecFile = ''' +name: path_provider_example +description: Demonstrates how to use the path_provider plugin. + +dependencies: + flutter: + sdk: flutter + path_provider_linux: 1.0.0 +'''; + +const String _kLinuxRegistrant = +'//\n' +'// Generated file. Do not edit.\n' +'// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.\n' +'//\n' +'\n' +'// @dart = 2.12\n' +'\n' +'import \'package:path_provider_example/main.dart\' as entrypoint;\n' +'import \'dart:io\'; // flutter_ignore: dart_io_import.\n' +'import \'package:path_provider_linux/path_provider_linux.dart\';\n' +'\n' +'@pragma(\'vm:entry-point\')\n' +'class _PluginRegistrant {\n' +'\n' +' @pragma(\'vm:entry-point\')\n' +' static void register() {\n' +' if (Platform.isLinux) {\n' +' try {\n' +' PathProviderLinux.registerWith();\n' +' } catch (err) {\n' +' print(\n' +' \'`path_provider_linux` threw an error: \$err. \'\n' +' \'The app may not function as expected until you remove this plugin from pubspec.yaml\'\n' +' );\n' +' rethrow;\n' +' }\n' +'\n' +' } else if (Platform.isMacOS) {\n' +' } else if (Platform.isWindows) {\n' +' }\n' +' }\n' +'\n' +'}\n' +'\n' +'typedef _UnaryFunction = dynamic Function(List<String> args);\n' +'typedef _NullaryFunction = dynamic Function();\n' +'\n' +'void main(List<String> args) {\n' +' if (entrypoint.main is _UnaryFunction) {\n' +' (entrypoint.main as _UnaryFunction)(args);\n' +' } else {\n' +' (entrypoint.main as _NullaryFunction)();\n' +' }\n' +'}\n' +''; + +const String _kSamplePluginPubspec = ''' +name: path_provider_linux +description: linux implementation of the path_provider plugin +// version: 2.0.1 +// homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux + +flutter: + plugin: + implements: path_provider + platforms: + linux: + dartPluginClass: PathProviderLinux + pluginClass: none + +environment: + sdk: ">=2.12.0-259.9.beta <3.0.0" + flutter: ">=1.20.0" +'''; + +void main() { + + group('Dart plugin registrant' , () { + final FileSystem fileSystem = MemoryFileSystem.test(); + + testWithoutContext('skipped based on environment.generateDartPluginRegistry', + () async { + final Environment environment = Environment.test( + fileSystem.currentDirectory, + artifacts: null, + fileSystem: fileSystem, + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + generateDartPluginRegistry: false); + + expect(const DartPluginRegistrantTarget().canSkip(environment), true); + + final Environment environment2 = Environment.test( + fileSystem.currentDirectory, + artifacts: null, + fileSystem: fileSystem, + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + generateDartPluginRegistry: true); + + expect(const DartPluginRegistrantTarget().canSkip(environment2), false); + }); + + testUsingContext("doesn't generate generated_main.dart if there aren't Dart plugins", () async { + final Environment environment = Environment.test( + fileSystem.currentDirectory, + projectDir: fileSystem.directory('project')..createSync(), + artifacts: null, + fileSystem: fileSystem, + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + generateDartPluginRegistry: true); + + final File config = environment.projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + config.createSync(recursive: true); + config.writeAsStringSync(_kSamplePackageJson); + + final File pubspec = environment.projectDir.childFile('pubspec.yaml'); + pubspec.createSync(); + + final File packages = environment.projectDir.childFile('.packages'); + packages.createSync(); + + final File generatedMain = environment.projectDir + .childDirectory('.dart_tool') + .childDirectory('flutter_build') + .childFile('generated_main.dart'); + + final FlutterProject testProject = FlutterProject.fromDirectoryTest(environment.projectDir); + await DartPluginRegistrantTarget.test(testProject).build(environment); + + expect(generatedMain.existsSync(), isFalse); + }); + + testUsingContext('regenerates generated_main.dart', () async { + final Directory projectDir = fileSystem.directory('project')..createSync(); + final Environment environment = Environment.test( + fileSystem.currentDirectory, + projectDir: projectDir, + artifacts: null, + fileSystem: fileSystem, + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + defines: <String, String>{ + kTargetFile: projectDir.childDirectory('lib').childFile('main.dart').absolute.path, + }, + generateDartPluginRegistry: true); + + final File config = projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + config.createSync(recursive: true); + config.writeAsStringSync(_kSamplePackageJson); + + final File pubspec = projectDir.childFile('pubspec.yaml'); + pubspec.createSync(); + pubspec.writeAsStringSync(_kSamplePubspecFile); + + final File packages = projectDir.childFile('.packages'); + packages.createSync(); + packages.writeAsStringSync(_kSamplePackagesFile); + + final File generatedMain = projectDir + .childDirectory('.dart_tool') + .childDirectory('flutter_build') + .childFile('generated_main.dart'); + generatedMain.createSync(recursive: true); + + final File mainEntrypoint = projectDir.childDirectory('lib').childFile('main.dart'); + mainEntrypoint.createSync(recursive: true); + + final File pluginPubspec = environment.fileSystem.currentDirectory.childDirectory('path_provider_linux').childFile('pubspec.yaml'); + pluginPubspec.createSync(recursive: true); + pluginPubspec.writeAsStringSync(_kSamplePluginPubspec); + + final FlutterProject testProject = FlutterProject.fromDirectoryTest(environment.projectDir); + await DartPluginRegistrantTarget.test(testProject).build(environment); + + final String mainContent = generatedMain.readAsStringSync(); + expect(mainContent, equals(_kLinuxRegistrant)); + }); + + testUsingContext('removes generated_main.dart if plugins are removed from pubspec.yaml', () async { + final Environment environment = Environment.test( + fileSystem.currentDirectory, + projectDir: fileSystem.directory('project')..createSync(), + artifacts: null, + fileSystem: fileSystem, + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + generateDartPluginRegistry: true); + final File config = environment.projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + config.createSync(recursive: true); + config.writeAsStringSync(_kSamplePackageJson); + + final File pubspec = environment.projectDir.childFile('pubspec.yaml'); + pubspec.createSync(); + pubspec.writeAsStringSync(_kSamplePubspecFile); + + final File packages = environment.projectDir.childFile('.packages'); + packages.createSync(); + packages.writeAsStringSync(_kSamplePackagesFile); + + final File generatedMain = environment.projectDir + .childDirectory('.dart_tool') + .childDirectory('flutter_build') + .childFile('generated_main.dart'); + + final File pluginPubspec = environment.fileSystem.currentDirectory + .childDirectory('path_provider_linux') + .childFile('pubspec.yaml'); + + pluginPubspec.createSync(recursive: true); + pluginPubspec.writeAsStringSync(_kSamplePluginPubspec); + + final FlutterProject testProject = FlutterProject.fromDirectoryTest(environment.projectDir); + await DartPluginRegistrantTarget.test(testProject).build(environment); + expect(generatedMain.existsSync(), isTrue); + + // Simulate a user removing everything from pubspec.yaml. + pubspec.writeAsStringSync(_kEmptyPubspecFile); + packages.writeAsStringSync(_kEmptyPackageJson); + config.writeAsStringSync(_kEmptyPackageJson); + + await DartPluginRegistrantTarget.test(testProject).build(environment); + expect(generatedMain.existsSync(), isFalse); + }); + }); +}
diff --git a/packages/flutter_tools/test/general.shard/compile_batch_test.dart b/packages/flutter_tools/test/general.shard/compile_batch_test.dart index 6415f58..e789407 100644 --- a/packages/flutter_tools/test/general.shard/compile_batch_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_batch_test.dart
@@ -8,6 +8,7 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/compile.dart'; @@ -367,4 +368,60 @@ expect((await output).outputFilename, ''); }); + + testWithoutContext('KernelCompiler uses generated entrypoint', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger, fileSystem: MemoryFileSystem.test()); + final Completer<void> completer = Completer<void>(); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final KernelCompiler kernelCompiler = KernelCompiler( + artifacts: Artifacts.test(), + fileSystem: fs, + fileSystemRoots: <String>[ + '/foo/bar/fizz', + ], + fileSystemScheme: 'scheme', + logger: logger, + processManager: FakeProcessManager.list(<FakeCommand>[ + FakeCommand(command: const <String>[ + 'HostArtifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + '.dart_tools/flutter_build/generated_main.dart', + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, + ); + + final Directory buildDir = fs.directory('.dart_tools') + .childDirectory('flutter_build') + .childDirectory('test'); + + buildDir.parent.childFile('generated_main.dart').createSync(recursive: true); + + final Future<CompilerOutput> output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + mainPath: '/foo/bar/fizz/main.dart', + buildMode: BuildMode.debug, + trackWidgetCreation: false, + dartDefines: const <String>[], + packageConfig: PackageConfig.empty, + packagesPath: '.packages', + buildDir: buildDir, + checkDartPluginRegistry: true, + ); + + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, <Uri>[])); + completer.complete(); + await output; + }); }
diff --git a/packages/flutter_tools/test/general.shard/compile_expression_test.dart b/packages/flutter_tools/test/general.shard/compile_expression_test.dart index 7987a39..faab7f0 100644 --- a/packages/flutter_tools/test/general.shard/compile_expression_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_expression_test.dart
@@ -95,6 +95,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + projectRootPath: '', + fs: fileSystem, ).then((CompilerOutput output) { expect(frontendServerStdIn.getAndClear(), 'compile file:///path/to/main.dart\n'); @@ -137,6 +139,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + projectRootPath: '', + fs: MemoryFileSystem(), ).then((CompilerOutput outputCompile) { expect(testLogger.errorText, equals('line1\nline2\n'));
diff --git a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart index 2d67f46..0ab702d 100644 --- a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart
@@ -92,6 +92,8 @@ null /* invalidatedFiles */, outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); expect(testLogger.errorText, equals('line1\nline2\n')); @@ -117,6 +119,8 @@ null /* invalidatedFiles */, outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile scheme:///main.dart\n'); expect(testLogger.errorText, equals('line1\nline2\n')); @@ -135,6 +139,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', )), throwsToolExit()); }); @@ -150,6 +156,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', )), throwsToolExit(message: 'the Dart compiler exited unexpectedly.')); }); @@ -167,6 +175,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + projectRootPath: '', + fs: MemoryFileSystem(), ); expect(frontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); @@ -213,6 +223,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile scheme:///main.dart\n'); @@ -274,6 +286,8 @@ null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile scheme:///main.dart\n'); @@ -323,6 +337,8 @@ <Uri>[], outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); @@ -359,6 +375,8 @@ null /* invalidatedFiles */, outputPath: '/build/', packageConfig: PackageConfig.empty, + fs: MemoryFileSystem(), + projectRootPath: '', ); expect(frontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); @@ -398,6 +416,8 @@ outputPath: '/build/', packageConfig: PackageConfig.empty, suppressErrors: suppressErrors, + fs: MemoryFileSystem(), + projectRootPath: '', ); // Put content into the output stream after generator.recompile gets
diff --git a/packages/flutter_tools/test/general.shard/dart_plugin_test.dart b/packages/flutter_tools/test/general.shard/dart_plugin_test.dart new file mode 100644 index 0000000..98a62ab --- /dev/null +++ b/packages/flutter_tools/test/general.shard/dart_plugin_test.dart
@@ -0,0 +1,954 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/dart/package_map.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; +import 'package:flutter_tools/src/flutter_plugins.dart'; +import 'package:flutter_tools/src/globals_null_migrated.dart' as globals; +import 'package:flutter_tools/src/plugins.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:mockito/mockito.dart'; +import 'package:package_config/package_config.dart'; +import 'package:yaml/yaml.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; + +void main() { + group('Dart plugin registrant', () { + FileSystem fs; + MockFlutterProject flutterProject; + MockFlutterManifest flutterManifest; + + setUp(() async { + fs = MemoryFileSystem.test(); + + flutterProject = MockFlutterProject(); + + flutterManifest = MockFlutterManifest(); + when(flutterManifest.dependencies).thenReturn(<String>{}); + + when(flutterProject.manifest).thenReturn(flutterManifest); + when(flutterProject.directory).thenReturn(fs.systemTempDirectory.childDirectory('app')); + + when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins')); + when(flutterProject.flutterPluginsDependenciesFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins-dependencies')); + + flutterProject.directory.childFile('.packages').createSync(recursive: true); + }); + + group('resolvePlatformImplementation', () { + testWithoutContext('selects implementation from direct dependency', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_linux', + 'url_launcher_macos', + }; + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_macos', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'macos': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginMacOS', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'undirect_dependency_plugin', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'windows': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginWindows', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_macos', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'macos': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginMacOS', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + + expect(resolutions.length, equals(2)); + expect(resolutions[0].toMap(), equals( + <String, String>{ + 'pluginName': 'url_launcher_linux', + 'dartClass': 'UrlLauncherPluginLinux', + 'platform': 'linux', + }) + ); + expect(resolutions[1].toMap(), equals( + <String, String>{ + 'pluginName': 'url_launcher_macos', + 'dartClass': 'UrlLauncherPluginMacOS', + 'platform': 'macos', + }) + ); + }); + + testWithoutContext('selects default implementation', () async { + final Set<String> directDependencies = <String>{}; + + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'default_package': 'url_launcher_linux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + expect(resolutions.length, equals(1)); + expect(resolutions[0].toMap(), equals( + <String, String>{ + 'pluginName': 'url_launcher_linux', + 'dartClass': 'UrlLauncherPluginLinux', + 'platform': 'linux', + }) + ); + }); + + testWithoutContext('selects default implementation if interface is direct dependency', () async { + final Set<String> directDependencies = <String>{'url_launcher'}; + + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'default_package': 'url_launcher_linux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + expect(resolutions.length, equals(1)); + expect(resolutions[0].toMap(), equals( + <String, String>{ + 'pluginName': 'url_launcher_linux', + 'dartClass': 'UrlLauncherPluginLinux', + 'platform': 'linux', + }) + ); + }); + + testWithoutContext('selects user selected implementation despites default implementation', () async { + final Set<String> directDependencies = <String>{ + 'user_selected_url_launcher_implementation', + 'url_launcher', + }; + + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'default_package': 'url_launcher_linux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'user_selected_url_launcher_implementation', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + expect(resolutions.length, equals(1)); + expect(resolutions[0].toMap(), equals( + <String, String>{ + 'pluginName': 'user_selected_url_launcher_implementation', + 'dartClass': 'UrlLauncherPluginLinux', + 'platform': 'linux', + }) + ); + }); + + testWithoutContext('selects user selected implementation despites default implementation', () async { + final Set<String> directDependencies = <String>{ + 'user_selected_url_launcher_implementation', + 'url_launcher', + }; + + final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'default_package': 'url_launcher_linux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'user_selected_url_launcher_implementation', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + expect(resolutions.length, equals(1)); + expect(resolutions[0].toMap(), equals( + <String, String>{ + 'pluginName': 'user_selected_url_launcher_implementation', + 'dartClass': 'UrlLauncherPluginLinux', + 'platform': 'linux', + }) + ); + }); + + testUsingContext('provides error when user selected multiple implementations', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_linux_1', + 'url_launcher_linux_2', + }; + expect(() { + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_linux_1', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux_2', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + + expect( + testLogger.errorText, + 'Plugin `url_launcher_linux_2` implements an interface for `linux`, which was already implemented by plugin `url_launcher_linux_1`.\n' + 'To fix this issue, remove either dependency from pubspec.yaml.' + '\n\n' + ); + }, + throwsToolExit( + message: 'Please resolve the errors', + )); + }); + + testUsingContext('provides all errors when user selected multiple implementations', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_linux_1', + 'url_launcher_linux_2', + }; + expect(() { + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_linux_1', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_linux_2', + '', + YamlMap.wrap(<String, dynamic>{ + 'implements': 'url_launcher', + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + + expect( + testLogger.errorText, + 'Plugin `url_launcher_linux_2` implements an interface for `linux`, which was already implemented by plugin `url_launcher_linux_1`.\n' + 'To fix this issue, remove either dependency from pubspec.yaml.' + '\n\n' + ); + }, + throwsToolExit( + message: 'Please resolve the errors', + )); + }); + + testUsingContext('provides error when plugin pubspec.yaml doesn\'t have "implementation" nor "default_implementation"', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_linux_1', + }; + expect(() { + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_linux_1', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + }, + throwsToolExit( + message: 'Please resolve the errors' + )); + expect( + testLogger.errorText, + 'Plugin `url_launcher_linux_1` doesn\'t implement a plugin interface, ' + 'nor sets a default implementation in pubspec.yaml.\n\n' + 'To set a default implementation, use:\n' + 'flutter:\n' + ' plugin:\n' + ' platforms:\n' + ' linux:\n' + ' default_package: <plugin-implementation>\n' + '\n' + 'To implement an interface, use:\n' + 'flutter:\n' + ' plugin:\n' + ' implements: <plugin-interface>' + '\n\n' + ); + }); + + testUsingContext('provides all errors when plugin pubspec.yaml doesn\'t have "implementation" nor "default_implementation"', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_linux', + 'url_launcher_windows', + }; + expect(() { + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_linux', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'linux': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginLinux', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + Plugin.fromYaml( + 'url_launcher_windows', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'windows': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginWindows', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ]); + }, + throwsToolExit( + message: 'Please resolve the errors' + )); + expect( + testLogger.errorText, + 'Plugin `url_launcher_linux` doesn\'t implement a plugin interface, ' + 'nor sets a default implementation in pubspec.yaml.\n\n' + 'To set a default implementation, use:\n' + 'flutter:\n' + ' plugin:\n' + ' platforms:\n' + ' linux:\n' + ' default_package: <plugin-implementation>\n' + '\n' + 'To implement an interface, use:\n' + 'flutter:\n' + ' plugin:\n' + ' implements: <plugin-interface>' + '\n\n' + 'Plugin `url_launcher_windows` doesn\'t implement a plugin interface, ' + 'nor sets a default implementation in pubspec.yaml.\n\n' + 'To set a default implementation, use:\n' + 'flutter:\n' + ' plugin:\n' + ' platforms:\n' + ' windows:\n' + ' default_package: <plugin-implementation>\n' + '\n' + 'To implement an interface, use:\n' + 'flutter:\n' + ' plugin:\n' + ' implements: <plugin-interface>' + '\n\n' + ); + }); + }); + + group('generateMainDartWithPluginRegistrant', () { + + void createFakeDartPlugins( + FlutterProject flutterProject, + FlutterManifest flutterManifest, + FileSystem fs, + Map<String, String> plugins, + ) { + final Directory fakePubCache = fs.systemTempDirectory.childDirectory('cache'); + final File packagesFile = flutterProject.directory + .childFile('.packages') + ..createSync(recursive: true); + + for (final MapEntry<String, String> entry in plugins.entries) { + final String name = fs.path.basename(entry.key); + final Directory pluginDirectory = fakePubCache.childDirectory(name); + packagesFile.writeAsStringSync( + '$name:file://${pluginDirectory.childFile('lib').uri}\n', + mode: FileMode.writeOnlyAppend); + pluginDirectory.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(entry.value); + } + when(flutterManifest.dependencies).thenReturn(<String>{...plugins.keys}); + } + + testUsingContext('Generates new entrypoint', () async { + when(flutterProject.isModule).thenReturn(false); + + createFakeDartPlugins( + flutterProject, + flutterManifest, + fs, + <String, String>{ + 'url_launcher_macos': ''' + flutter: + plugin: + implements: url_launcher + platforms: + macos: + dartPluginClass: MacOSPlugin +''', + 'url_launcher_linux': ''' + flutter: + plugin: + implements: url_launcher + platforms: + linux: + dartPluginClass: LinuxPlugin +''', + 'url_launcher_windows': ''' + flutter: + plugin: + implements: url_launcher + platforms: + windows: + dartPluginClass: WindowsPlugin +''', + 'awesome_macos': ''' + flutter: + plugin: + implements: awesome + platforms: + macos: + dartPluginClass: AwesomeMacOS +''' + }); + + final Directory libDir = flutterProject.directory.childDirectory('lib'); + libDir.createSync(recursive: true); + + final File mainFile = libDir.childFile('main.dart'); + mainFile.writeAsStringSync(''' +// @dart = 2.8 +void main() { +} +'''); + final File generatedMainFile = flutterProject.directory.childFile('generated_main.dart'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), + logger: globals.logger, + throwOnError: false, + ); + await generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ); + expect(generatedMainFile.readAsStringSync(), + '//\n' + '// Generated file. Do not edit.\n' + '// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.\n' + '//\n' + '\n' + '// @dart = 2.8\n' + '\n' + 'import \'package:app/main.dart\' as entrypoint;\n' + 'import \'dart:io\'; // flutter_ignore: dart_io_import.\n' + 'import \'package:url_launcher_linux/url_launcher_linux.dart\';\n' + 'import \'package:awesome_macos/awesome_macos.dart\';\n' + 'import \'package:url_launcher_macos/url_launcher_macos.dart\';\n' + 'import \'package:url_launcher_windows/url_launcher_windows.dart\';\n' + '\n' + '@pragma(\'vm:entry-point\')\n' + 'class _PluginRegistrant {\n' + '\n' + ' @pragma(\'vm:entry-point\')\n' + ' static void register() {\n' + ' if (Platform.isLinux) {\n' + ' try {\n' + ' LinuxPlugin.registerWith();\n' + ' } catch (err) {\n' + ' print(\n' + ' \'`url_launcher_linux` threw an error: \$err. \'\n' + ' \'The app may not function as expected until you remove this plugin from pubspec.yaml\'\n' + ' );\n' + ' rethrow;\n' + ' }\n' + '\n' + ' } else if (Platform.isMacOS) {\n' + ' try {\n' + ' AwesomeMacOS.registerWith();\n' + ' } catch (err) {\n' + ' print(\n' + ' \'`awesome_macos` threw an error: \$err. \'\n' + ' \'The app may not function as expected until you remove this plugin from pubspec.yaml\'\n' + ' );\n' + ' rethrow;\n' + ' }\n' + '\n' + ' try {\n' + ' MacOSPlugin.registerWith();\n' + ' } catch (err) {\n' + ' print(\n' + ' \'`url_launcher_macos` threw an error: \$err. \'\n' + ' \'The app may not function as expected until you remove this plugin from pubspec.yaml\'\n' + ' );\n' + ' rethrow;\n' + ' }\n' + '\n' + ' } else if (Platform.isWindows) {\n' + ' try {\n' + ' WindowsPlugin.registerWith();\n' + ' } catch (err) {\n' + ' print(\n' + ' \'`url_launcher_windows` threw an error: \$err. \'\n' + ' \'The app may not function as expected until you remove this plugin from pubspec.yaml\'\n' + ' );\n' + ' rethrow;\n' + ' }\n' + '\n' + ' }\n' + ' }\n' + '\n' + '}\n' + '\n' + 'typedef _UnaryFunction = dynamic Function(List<String> args);\n' + 'typedef _NullaryFunction = dynamic Function();\n' + '\n' + 'void main(List<String> args) {\n' + ' if (entrypoint.main is _UnaryFunction) {\n' + ' (entrypoint.main as _UnaryFunction)(args);\n' + ' } else {\n' + ' (entrypoint.main as _NullaryFunction)();\n' + ' }\n' + '}\n' + '', + ); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('Plugin without platform support throws tool exit', () async { + when(flutterProject.isModule).thenReturn(false); + + createFakeDartPlugins( + flutterProject, + flutterManifest, + fs, + <String, String>{ + 'url_launcher_macos': ''' + flutter: + plugin: + implements: url_launcher + platforms: + macos: + invalid: +''' + }); + + final Directory libDir = flutterProject.directory.childDirectory('lib'); + libDir.createSync(recursive: true); + + final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); + final File generatedMainFile = flutterProject.directory.childFile('generated_main.dart'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), + logger: globals.logger, + throwOnError: false, + ); + await expectLater( + generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ), throwsToolExit(message: + 'Invalid plugin specification url_launcher_macos.\n' + 'Invalid "macos" plugin specification.' + ), + ); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('Plugin with platform support without dart plugin class throws tool exit', () async { + when(flutterProject.isModule).thenReturn(false); + + createFakeDartPlugins( + flutterProject, + flutterManifest, + fs, + <String, String>{ + 'url_launcher_macos': ''' + flutter: + plugin: + implements: url_launcher +''' + }); + + final Directory libDir = flutterProject.directory.childDirectory('lib'); + libDir.createSync(recursive: true); + + final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); + final File generatedMainFile = flutterProject.directory.childFile('generated_main.dart'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), + logger: globals.logger, + throwOnError: false, + ); + await expectLater( + generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ), throwsToolExit(message: + 'Invalid plugin specification url_launcher_macos.\n' + 'Cannot find the `flutter.plugin.platforms` key in the `pubspec.yaml` file. ' + 'An instruction to format the `pubspec.yaml` can be found here: ' + 'https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms' + ), + ); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('Does not show error messages if throwOnPluginPubspecError is false', () async { + final Set<String> directDependencies = <String>{ + 'url_launcher_windows', + }; + resolvePlatformImplementation(<Plugin>[ + Plugin.fromYaml( + 'url_launcher_windows', + '', + YamlMap.wrap(<String, dynamic>{ + 'platforms': <String, dynamic>{ + 'windows': <String, dynamic>{ + 'dartPluginClass': 'UrlLauncherPluginWindows', + }, + }, + }), + <String>[], + fileSystem: fs, + appDependencies: directDependencies, + ), + ], + throwOnPluginPubspecError: false, + ); + expect(testLogger.errorText, ''); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('Does not create new entrypoint if there are no platform resolutions', () async { + when(flutterProject.isModule).thenReturn(false); + when(flutterManifest.dependencies).thenReturn(<String>{}); + + final Directory libDir = flutterProject.directory.childDirectory('lib'); + libDir.createSync(recursive: true); + + final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); + final File generatedMainFile = flutterProject.directory.childFile('generated_main.dart'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), + logger: globals.logger, + throwOnError: false, + ); + await generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ); + expect(generatedMainFile.existsSync(), isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('Deletes new entrypoint if there are no platform resolutions', () async { + when(flutterProject.isModule).thenReturn(false); + + createFakeDartPlugins( + flutterProject, + flutterManifest, + fs, + <String, String>{ + 'url_launcher_macos': ''' + flutter: + plugin: + implements: url_launcher + platforms: + macos: + dartPluginClass: MacOSPlugin +''' + }); + + final Directory libDir = flutterProject.directory.childDirectory('lib'); + libDir.createSync(recursive: true); + + final File mainFile = libDir.childFile('main.dart')..writeAsStringSync(''); + final File generatedMainFile = flutterProject.directory.childFile('generated_main.dart'); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'), + logger: globals.logger, + throwOnError: false, + ); + await generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ); + expect(generatedMainFile.existsSync(), isTrue); + + // No plugins. + createFakeDartPlugins( + flutterProject, + flutterManifest, + fs, + <String, String>{}); + + await generateMainDartWithPluginRegistrant( + flutterProject, + packageConfig, + 'package:app/main.dart', + generatedMainFile, + mainFile, + throwOnPluginPubspecError: true, + ); + expect(generatedMainFile.existsSync(), isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + }); + + }); +} + +class MockFlutterManifest extends Mock implements FlutterManifest {} +class MockFlutterProject extends Mock implements FlutterProject {}
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index bb79b6e..9764304 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -170,6 +170,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('lib/foo.dill') ..createSync(recursive: true) @@ -237,6 +239,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('lib/foo.dill', 2, <Uri>[]); }); @@ -281,6 +285,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('example').createSync(); return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]); @@ -327,6 +333,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('lib/foo.txt.dill').createSync(recursive: true); return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]); @@ -379,6 +387,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('example').createSync(); return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
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 709ab20..08463ab 100644 --- a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart
@@ -1444,6 +1444,19 @@ expect(deferredComponents[0].assets[1].path, 'path/to/asset2.jpg'); expect(deferredComponents[0].assets[2].path, 'path/to/asset3.jpg'); }); + + testWithoutContext('FlutterManifest can parse empty dependencies', () async { + const String manifest = ''' +name: test +'''; + final FlutterManifest? flutterManifest = FlutterManifest.createFromString( + manifest, + logger: BufferLogger.test(), + ); + + expect(flutterManifest, isNotNull); + expect(flutterManifest!.dependencies, isEmpty); + }); } Matcher matchesManifest({
diff --git a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart index da59b44..490dc9a 100644 --- a/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart +++ b/packages/flutter_tools/test/general.shard/plugin_parsing_test.dart
@@ -218,6 +218,39 @@ ); expect(plugin.platforms, <String, PluginPlatform>{}); + expect(plugin.defaultPackagePlatforms, <String, String>{ + 'linux': 'sample_package_linux', + 'macos': 'sample_package_macos', + 'windows': 'sample_package_windows', + }); + expect(plugin.pluginDartClassPlatforms, <String, String>{}); + }); + + testWithoutContext('Desktop plugin parsing allows a dartPluginClass field', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + const String pluginYamlRaw = + 'platforms:\n' + ' linux:\n' + ' dartPluginClass: LinuxClass\n' + ' macos:\n' + ' dartPluginClass: MacOSClass\n' + ' windows:\n' + ' dartPluginClass: WindowsClass\n'; + + final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap; + final Plugin plugin = Plugin.fromYaml( + _kTestPluginName, + _kTestPluginPath, + pluginYaml, + const <String>[], + fileSystem: fileSystem, + ); + + expect(plugin.pluginDartClassPlatforms, <String, String>{ + 'linux': 'LinuxClass', + 'macos': 'MacOSClass', + 'windows': 'WindowsClass', + }); }); testWithoutContext('Plugin parsing throws a fatal error on an empty plugin', () {
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 293cdb6..74d0cef 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -14,6 +14,7 @@ import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_plugins.dart'; import 'package:flutter_tools/src/globals_null_migrated.dart' as globals; import 'package:flutter_tools/src/ios/xcodeproj.dart'; @@ -33,6 +34,7 @@ group('plugins', () { FileSystem fs; MockFlutterProject flutterProject; + MockFlutterManifest flutterManifest; MockIosProject iosProject; MockMacOSProject macosProject; MockAndroidProject androidProject; @@ -49,6 +51,12 @@ // Adds basic properties to the flutterProject and its subprojects. void setUpProject(FileSystem fileSystem) { flutterProject = MockFlutterProject(); + + flutterManifest = MockFlutterManifest(); + when(flutterManifest.dependencies).thenReturn(<String>{}); + + when(flutterProject.manifest).thenReturn(flutterManifest); + when(flutterProject.directory).thenReturn(fileSystem.systemTempDirectory.childDirectory('app')); // TODO(franciscojma): Remove logic for .flutter-plugins once it's deprecated. when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins')); @@ -1412,6 +1420,7 @@ } class MockAndroidProject extends Mock implements AndroidProject {} +class MockFlutterManifest extends Mock implements FlutterManifest {} class MockFlutterProject extends Mock implements FlutterProject {} class MockIosProject extends Mock implements IosProject {} class MockMacOSProject extends Mock implements MacOSProject {}
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 5f86a0e..dfdd038 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -110,6 +110,17 @@ ); }); + _testInMemory('reads dependencies from pubspec.yaml', () async { + final Directory directory = globals.fs.directory('myproject'); + directory.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(validPubspecWithDependencies); + expect( + FlutterProject.fromDirectory(directory).manifest.dependencies, + <String>{'plugin_a', 'plugin_b'}, + ); + }); + _testInMemory('sets up location', () async { final Directory directory = globals.fs.directory('myproject'); expect( @@ -920,6 +931,16 @@ flutter: '''; +String get validPubspecWithDependencies => ''' +name: hello +flutter: + +dependencies: + plugin_a: + plugin_b: +'''; + + String get invalidPubspec => ''' name: hello flutter:
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index beb459e..d7c6cb2 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -271,6 +271,8 @@ outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), suppressErrors: true, + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('foo', 0 ,<Uri>[]); }); @@ -288,6 +290,8 @@ outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), suppressErrors: true, + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).called(1); expect(fakeVmServiceHost.hasRemainingExpectations, false); }), overrides: <Type, Generator>{ @@ -316,6 +320,8 @@ outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), suppressErrors: true, + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('foo', 1 ,<Uri>[]); }); @@ -408,6 +414,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), suppressErrors: false, )).thenAnswer((Invocation invocation) async { return const CompilerOutput('foo', 0, <Uri>[]); @@ -426,6 +434,8 @@ outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), suppressErrors: false, + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).called(1); expect(fakeVmServiceHost.hasRemainingExpectations, false); }), overrides: <Type, Generator>{ @@ -1438,6 +1448,26 @@ globals.fs.file('l10n.yaml').createSync(); globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n'); + // Create necessary files for [DartPluginRegistrantTarget] + final File packageConfig = globals.fs.directory('.dart_tool') + .childFile('package_config.json'); + packageConfig.createSync(recursive: true); + packageConfig.writeAsStringSync(''' +{ + "configVersion": 2, + "packages": [ + { + "name": "path_provider_linux", + "rootUri": "../../../path_provider_linux", + "packageUri": "lib/", + "languageVersion": "2.12" + } + ] +} +'''); + // Start from an empty generated_main.dart file. + globals.fs.directory('.dart_tool').childDirectory('flutter_build').childFile('generated_main.dart').createSync(recursive: true); + await residentRunner.runSourceGenerators(); expect(testLogger.errorText, isEmpty); @@ -2515,6 +2545,8 @@ List<Uri> invalidatedFiles, { @required String outputPath, @required PackageConfig packageConfig, + @required String projectRootPath, + @required FileSystem fs, bool suppressErrors = false, }) async { return const CompilerOutput('foo.dill', 0, <Uri>[]);
diff --git a/packages/flutter_tools/test/general.shard/test/test_compiler_test.dart b/packages/flutter_tools/test/general.shard/test/test_compiler_test.dart index 76237e2..ed151a1 100644 --- a/packages/flutter_tools/test/general.shard/test/test_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/test/test_compiler_test.dart
@@ -55,6 +55,8 @@ <Uri>[Uri.parse('test/foo.dart')], outputPath: testCompiler.outputDill.path, packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('abc.dill').createSync(); return const CompilerOutput('abc.dill', 0, <Uri>[]); @@ -80,6 +82,8 @@ <Uri>[Uri.parse('test/foo.dart')], outputPath: testCompiler.outputDill.path, packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { fileSystem.file('abc.dill').createSync(); return const CompilerOutput('abc.dill', 1, <Uri>[]);
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index d949bb9..38099c9 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -605,6 +605,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); }); @@ -723,6 +725,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); }); @@ -838,6 +842,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); }); @@ -883,6 +889,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); }); @@ -936,6 +944,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); }); @@ -1056,6 +1066,8 @@ any, outputPath: anyNamed('outputPath'), packageConfig: anyNamed('packageConfig'), + projectRootPath: anyNamed('projectRootPath'), + fs: anyNamed('fs'), )).thenAnswer((Invocation invocation) async { return const CompilerOutput('a', 0, <Uri>[]); });
diff --git a/packages/flutter_tools/test/general.shard/windows/plugins_test.dart b/packages/flutter_tools/test/general.shard/windows/plugins_test.dart index 6a00c3a..1eb14b8 100644 --- a/packages/flutter_tools/test/general.shard/windows/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/plugins_test.dart
@@ -44,8 +44,11 @@ Plugin( name: 'test', path: 'foo', + defaultPackagePlatforms: const <String, String>{}, + pluginDartClassPlatforms: const <String, String>{}, platforms: const <String, PluginPlatform>{WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: 'Foo')}, dependencies: <String>[], + isDirectDependency: true, ), ], renderer); @@ -67,8 +70,11 @@ Plugin( name: 'test', path: 'foo', + defaultPackagePlatforms: const <String, String>{}, + pluginDartClassPlatforms: const <String, String>{}, platforms: const <String, PluginPlatform>{WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: 'Foo')}, dependencies: <String>[], + isDirectDependency: true, ), ], renderer);