diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 89503641437..8e7962e7a86 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -1106,6 +1106,11 @@ Future refreshPluginsList( /// /// In the Web platform, `destination` can point to a real filesystem (`flutter build`) /// or an in-memory filesystem (`flutter run`). +/// +/// This method is also used by [WebProject.ensureReadyForPlatformSpecificTooling] +/// to inject a copy of the plugin registrant for web into .dart_tool/dartpad so +/// dartpad can get the plugin registrant without needing to build the complete +/// project. See: https://github.com/dart-lang/dart-services/pull/874 Future injectBuildTimePluginFiles( FlutterProject project, { required Directory destination, diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 9f68a664475..813a4698c6b 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -734,7 +734,20 @@ class WebProject extends FlutterProjectPlatform { .childDirectory('web') .childFile('index.html'); - Future ensureReadyForPlatformSpecificTooling() async {} + /// The .dart_tool/dartpad directory + Directory get dartpadToolDirectory => parent.directory + .childDirectory('.dart_tool') + .childDirectory('dartpad'); + + Future ensureReadyForPlatformSpecificTooling() async { + /// Create .dart_tool/dartpad/web_plugin_registrant.dart. + /// See: https://github.com/dart-lang/dart-services/pull/874 + await injectBuildTimePluginFiles( + parent, + destination: dartpadToolDirectory, + webPlatform: true, + ); + } } /// The Fuchsia sub project. diff --git a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart index 562871b7d6c..314c69baf14 100644 --- a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart +++ b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart @@ -43,11 +43,10 @@ void main() { testUsingContext('generated plugin registrant passes analysis', () async { await _createProject(projectDir, []); - // We need to add a dependency with web support to trigger - // the generated_plugin_registrant generation. + // We need a dependency so the plugin registrant is not completely empty. await _addDependency(projectDir, 'shared_preferences', version: '^2.0.0'); - // The plugin registrant is only created after a build... + // The plugin registrant is created on build... await _buildWebProject(projectDir); // Find the web_plugin_registrant, now that it lives outside "lib": @@ -56,11 +55,77 @@ void main() { .listSync() .firstWhere((FileSystemEntity entity) => entity is Directory) as Directory; - expect( - buildDir.childFile('web_plugin_registrant.dart'), - exists, - ); - await _analyzeEntity(buildDir.childFile('web_plugin_registrant.dart')); + // Ensure the file exists, and passes analysis. + final File registrant = buildDir.childFile('web_plugin_registrant.dart'); + expect(registrant, exists); + await _analyzeEntity(registrant); + + // Ensure the contents match what we expect for a non-empty plugin registrant. + final String contents = registrant.readAsStringSync(); + expect(contents, contains("import 'package:shared_preferences_web/shared_preferences_web.dart';")); + expect(contents, contains('void registerPlugins([final Registrar? pluginRegistrar]) {')); + expect(contents, contains('SharedPreferencesPlugin.registerWith(registrar);')); + expect(contents, contains('registrar.registerMessageHandler();')); + }, overrides: { + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), + }); + + testUsingContext('(no-op) generated plugin registrant passes analysis', () async { + await _createProject(projectDir, []); + // No dependencies on web plugins this time! + await _buildWebProject(projectDir); + + // Find the web_plugin_registrant, now that it lives outside "lib": + final Directory buildDir = projectDir + .childDirectory('.dart_tool/flutter_build') + .listSync() + .firstWhere((FileSystemEntity entity) => entity is Directory) as Directory; + + // Ensure the file exists, and passes analysis. + final File registrant = buildDir.childFile('web_plugin_registrant.dart'); + expect(registrant, exists); + await _analyzeEntity(registrant); + + // Ensure the contents match what we expect for an empty (noop) plugin registrant. + final String contents = registrant.readAsStringSync(); + expect(contents, contains('void registerPlugins() {}')); + }, overrides: { + Pub: () => Pub( + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + usage: globals.flutterUsage, + botDetector: globals.botDetector, + platform: globals.platform, + ), + }); + + // See: https://github.com/dart-lang/dart-services/pull/874 + testUsingContext('generated plugin registrant for dartpad is created on pub get', () async { + await _createProject(projectDir, []); + await _addDependency(projectDir, 'shared_preferences', + version: '^2.0.0'); + // The plugin registrant for dartpad is created on flutter pub get. + await _doFlutterPubGet(projectDir); + + final File registrant = projectDir + .childDirectory('.dart_tool/dartpad') + .childFile('web_plugin_registrant.dart'); + + // Ensure the file exists, and passes analysis. + expect(registrant, exists); + await _analyzeEntity(registrant); + + // Assert the full build hasn't happened! + final Directory buildDir = projectDir.childDirectory('.dart_tool/flutter_build'); + expect(buildDir, isNot(exists)); }, overrides: { Pub: () => Pub( fileSystem: globals.fs, @@ -258,6 +323,18 @@ Future _analyzeEntity(FileSystemEntity target) async { } Future _buildWebProject(Directory workingDir) async { + return _runFlutterSnapshot(['build', 'web'], workingDir); +} + +Future _doFlutterPubGet(Directory workingDir) async { + return _runFlutterSnapshot(['pub', 'get'], workingDir); +} + +// Runs a flutter command from a snapshot build. +// `flutterCommandArgs` are the arguments passed to flutter, like: ['build', 'web'] +// to run `flutter build web`. +// `workingDir` is the directory on which the flutter command will be run. +Future _runFlutterSnapshot(List flutterCommandArgs, Directory workingDir) async { final String flutterToolsSnapshotPath = globals.fs.path.absolute( globals.fs.path.join( '..', @@ -270,8 +347,7 @@ Future _buildWebProject(Directory workingDir) async { final List args = [ flutterToolsSnapshotPath, - 'build', - 'web', + ...flutterCommandArgs ]; final ProcessResult exec = await Process.run( @@ -279,7 +355,7 @@ Future _buildWebProject(Directory workingDir) async { args, workingDirectory: workingDir.path, ); - printOnFailure('Output of flutter build web:'); + printOnFailure('Output of flutter ${flutterCommandArgs.join(" ")}:'); printOnFailure(exec.stdout.toString()); printOnFailure(exec.stderr.toString()); expect(exec.exitCode, 0);