diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index faf0b41b263..ee96e12fc43 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -88,6 +88,7 @@ Future main(List args) async { final bool muteCommandLogging = (help || doctor) && !veryVerbose; final bool verboseHelp = help && verbose; final bool daemon = args.contains('daemon'); + final bool widgetPreviews = args.contains(WidgetPreviewCommand.kWidgetPreview); final bool runMachine = args.contains('--machine'); // Cache.flutterRoot must be set early because other features use it (e.g. @@ -133,6 +134,7 @@ Future main(List args) async { verbose: verbose && !muteCommandLogging, prefixedErrors: prefixedErrors, windows: globals.platform.isWindows, + widgetPreviews: widgetPreviews, ); }, AnsiTerminal: () { @@ -294,6 +296,7 @@ class LoggerFactory { required bool machine, required bool daemon, required bool windows, + required bool widgetPreviews, }) { Logger logger; if (windows) { @@ -317,6 +320,9 @@ class LoggerFactory { if (prefixedErrors) { logger = PrefixedErrorLogger(logger); } + if (widgetPreviews) { + return WidgetPreviewMachineAwareLogger(logger, machine: machine, verbose: verbose); + } if (daemon) { return NotifyingLogger(verbose: verbose, parent: logger); } diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 62a731702fd..bfd3e18f9df 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -13,6 +13,7 @@ import 'package:process/process.dart'; import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; +import '../base/io.dart'; import '../base/logger.dart'; import '../base/os.dart'; import '../base/platform.dart'; @@ -28,7 +29,6 @@ import '../isolated/resident_web_runner.dart'; import '../project.dart'; import '../resident_runner.dart'; import '../runner/flutter_command.dart'; -import '../runner/flutter_command_runner.dart'; import '../web/web_device.dart'; import '../widget_preview/analytics.dart'; import '../widget_preview/dependency_graph.dart'; @@ -77,7 +77,8 @@ class WidgetPreviewCommand extends FlutterCommand { String get description => 'Manage the widget preview environment.'; @override - String get name => 'widget-preview'; + String get name => kWidgetPreview; + static const kWidgetPreview = 'widget-preview'; @override String get category => FlutterCommandCategory.tools; @@ -130,7 +131,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C required this.processManager, required this.artifacts, @visibleForTesting WidgetPreviewDtdServices? dtdServicesOverride, - }) : logger = WidgetPreviewMachineAwareLogger(logger) { + }) : _logger = logger { if (dtdServicesOverride != null) { _dtdService = dtdServicesOverride; } @@ -200,7 +201,8 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C final FileSystem fs; @override - final WidgetPreviewMachineAwareLogger logger; + WidgetPreviewMachineAwareLogger get logger => _logger as WidgetPreviewMachineAwareLogger; + final Logger _logger; @override final FlutterProjectFactory projectFactory; @@ -259,17 +261,17 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C @override Future runCommand() async { + assert(_logger is WidgetPreviewMachineAwareLogger); + // Start the timer tracking how long it takes to launch the preview environment. previewAnalytics.initializeLaunchStopwatch(); + logger.sendInitializingEvent(); final String? customPreviewScaffoldOutput = stringArg(kWidgetPreviewScaffoldOutputDir); final Directory widgetPreviewScaffold = customPreviewScaffoldOutput != null ? fs.directory(customPreviewScaffoldOutput) : rootProject.widgetPreviewScaffold; - final bool machine = boolArg(FlutterGlobalOptions.kMachineFlag); - logger.machine = machine; - // Check to see if a preview scaffold has already been generated. If not, // generate one. final bool generateScaffoldProject = @@ -479,7 +481,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C ); unawaited(_widgetPreviewApp!.run(appStartedCompleter: appStarted)); await appStarted.future; - logger.sendEvent('started', {'url': flutterDevice.devFS!.baseUri.toString()}); + logger.sendStartedEvent(applicationUrl: flutterDevice.devFS!.baseUri!); } } on Exception catch (error) { throwToolExit(error.toString()); @@ -531,9 +533,10 @@ final class WidgetPreviewCleanCommand extends WidgetPreviewSubCommandBase { /// A custom logger for the widget-preview commands that disables non-event output to stdio when /// machine mode is enabled. final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { - WidgetPreviewMachineAwareLogger(super.delegate); + WidgetPreviewMachineAwareLogger(super.delegate, {required this.machine, required this.verbose}); - var machine = false; + final bool machine; + final bool verbose; @override void printError( @@ -546,6 +549,11 @@ final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { bool? wrap, }) { if (machine) { + sendEvent('logMessage', { + 'level': 'error', + 'message': message, + 'stackTrace': ?stackTrace?.toString(), + }); return; } super.printError( @@ -570,6 +578,7 @@ final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { bool fatal = true, }) { if (machine) { + sendEvent('logMessage', {'level': 'warning', 'message': message}); return; } super.printWarning( @@ -594,6 +603,7 @@ final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { bool? wrap, }) { if (machine) { + sendEvent('logMessage', {'level': 'status', 'message': message}); return; } super.printStatus( @@ -617,12 +627,27 @@ final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { @override void printTrace(String message) { + if (!verbose) { + return; + } if (machine) { + sendEvent('logMessage', {'level': 'trace', 'message': message}); return; } super.printTrace(message); } + /// Notifies tooling that the widget previewer is initializing. + void sendInitializingEvent() { + sendEvent('initializing', {'pid': pid}); + } + + /// Notifies tooling that the widget previewer has started and is being + /// served at [applicationUrl]. + void sendStartedEvent({required Uri applicationUrl}) { + sendEvent('started', {'url': applicationUrl.toString()}); + } + @override void sendEvent(String name, [Map? args]) { if (!machine) { @@ -642,6 +667,7 @@ final class WidgetPreviewMachineAwareLogger extends DelegatingLogger { int progressIndicatorPadding = kDefaultStatusPadding, }) { if (machine) { + printStatus(message); return SilentStatus(stopwatch: Stopwatch()); } return super.startProgress( diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart index c32a29c8e4e..0a1d7d71681 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart @@ -109,7 +109,7 @@ void main() { await ensureFlutterToolsSnapshot(); loggingProcessManager = LoggingProcessManager(); shutdownHooks = ShutdownHooks(); - logger = BufferLogger.test(); + logger = WidgetPreviewMachineAwareLogger(BufferLogger.test(), machine: false, verbose: false); fs = LocalFileSystem.test(signals: Signals.test()); botDetector = const FakeBotDetector(false); tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_create_test.'); @@ -190,7 +190,7 @@ void main() { void expectSinglePreviewLaunchTimingEvent() => expectNPreviewLaunchTimingEvents(1); void expectDeviceSelected(Device device) { - final bufferLogger = logger as BufferLogger; + final BufferLogger bufferLogger = asLogger(logger); expect( bufferLogger.statusText, contains('Launching the Widget Preview Scaffold on ${device.displayName}...'), diff --git a/packages/flutter_tools/test/general.shard/base/logger_test.dart b/packages/flutter_tools/test/general.shard/base/logger_test.dart index 9b333aea536..16b58f99ab6 100644 --- a/packages/flutter_tools/test/general.shard/base/logger_test.dart +++ b/packages/flutter_tools/test/general.shard/base/logger_test.dart @@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/commands/daemon.dart'; +import 'package:flutter_tools/src/commands/widget_preview.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; @@ -37,6 +38,7 @@ void main() { prefixedErrors: false, machine: false, daemon: false, + widgetPreviews: false, windows: false, ), isA(), @@ -47,6 +49,7 @@ void main() { prefixedErrors: false, machine: false, daemon: false, + widgetPreviews: false, windows: true, ), isA(), @@ -57,6 +60,7 @@ void main() { prefixedErrors: false, machine: false, daemon: false, + widgetPreviews: false, windows: true, ), isA(), @@ -67,6 +71,7 @@ void main() { prefixedErrors: false, machine: false, daemon: false, + widgetPreviews: false, windows: false, ), isA(), @@ -77,6 +82,7 @@ void main() { prefixedErrors: true, machine: false, daemon: false, + widgetPreviews: false, windows: false, ), isA(), @@ -87,6 +93,7 @@ void main() { prefixedErrors: false, machine: false, daemon: true, + widgetPreviews: false, windows: false, ), isA(), @@ -97,10 +104,32 @@ void main() { prefixedErrors: false, machine: true, daemon: false, + widgetPreviews: false, windows: false, ), isA(), ); + + for (final verbose in [false, true]) { + for (final machine in [false, true]) { + for (final windows in [false, true]) { + expect( + loggerFactory.createLogger( + verbose: verbose, + prefixedErrors: false, + machine: machine, + daemon: false, + widgetPreviews: true, + windows: windows, + ), + isA(), + reason: + 'Did not produce WidgetPreviewMachineAwareLogger for ' + 'verbose: $verbose machine: $machine windows: $windows.', + ); + } + } + } }); testWithoutContext( diff --git a/packages/flutter_tools/test/general.shard/runner/runner_test.dart b/packages/flutter_tools/test/general.shard/runner/runner_test.dart index ebf10974379..1d6be5a665e 100644 --- a/packages/flutter_tools/test/general.shard/runner/runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/runner_test.dart @@ -604,6 +604,7 @@ void main() { machine: true, verbose: false, prefixedErrors: false, + widgetPreviews: false, windows: globals.platform.isWindows, ); }, diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_machine_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_machine_test.dart index 744981c6365..0d4691ea3a1 100644 --- a/packages/flutter_tools/test/integration.shard/widget_preview_machine_test.dart +++ b/packages/flutter_tools/test/integration.shard/widget_preview_machine_test.dart @@ -16,20 +16,28 @@ import '../src/common.dart'; import 'test_data/basic_project.dart'; import 'test_utils.dart'; -typedef ExpectedEvent = ({String event, FutureOr Function(Map)? validator}); +typedef ExpectedEvent = ({String event, FutureOr Function(Map)? validator}); final launchEvents = [ + ( + event: 'widget_preview.initializing', + validator: (Map params) { + return params.containsKey('pid'); + }, + ), ( event: 'widget_preview.started', validator: (Map params) async { - if (params case {'uri': final String uri}) { + if (params case {'url': final String uri}) { try { final Response response = await get(Uri.parse(uri)); - expect(response.statusCode, HttpStatus.ok, reason: 'Failed to retrieve widget previewer'); + return response.statusCode == HttpStatus.ok; + // ignore: avoid_catches_without_on_clauses } catch (e) { - fail('Failed to access widget previewer: $e'); + printOnFailure('Failed to access widget previewer: $e'); } } + return false; }, ), ]; @@ -82,7 +90,11 @@ void main() { if (event case [final Map eventObject]) { final ExpectedEvent expectation = expectedEvents[nextExpectationIndex]; if (expectation.event == eventObject['event']) { - await expectation.validator?.call(eventObject); + expect( + await expectation.validator?.call(eventObject['params']! as Map), + true, + reason: 'Validator[$nextExpectationIndex] failed.', + ); ++nextExpectationIndex; } }