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);