From c98ae486d66205160cd26ba4887173d430744bd9 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Fri, 11 Oct 2024 15:06:39 +0200 Subject: [PATCH] Add --watch to _macro_tool for analyzer. --- pkgs/_macro_tool/bin/main.dart | 7 ++- pkgs/_macro_tool/lib/analyzer_macro_tool.dart | 61 ++++++++++++------- pkgs/_macro_tool/lib/cfe_macro_tool.dart | 5 +- pkgs/_macro_tool/lib/macro_tool.dart | 13 ++-- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/pkgs/_macro_tool/bin/main.dart b/pkgs/_macro_tool/bin/main.dart index c9fdff55..a8bb4b26 100644 --- a/pkgs/_macro_tool/bin/main.dart +++ b/pkgs/_macro_tool/bin/main.dart @@ -14,7 +14,9 @@ final argParser = ArgParser() ..addOption('workspace', help: 'Path to workspace.') ..addOption('packageConfig', help: 'Path to package config.') ..addOption('script', help: 'Path to script.') - ..addFlag('skip-cleanup'); + ..addFlag('skip-cleanup', + help: 'Whether to skip delete of augmentations and revert of script.') + ..addFlag('watch', help: 'Whether to watch for changes.'); Future main(List arguments) async { final args = argParser.parse(arguments); @@ -40,6 +42,7 @@ ${argParser.usage}'''); workspacePath: p.canonicalize(workspace), packageConfigPath: p.canonicalize(packageConfig), scriptPath: p.canonicalize(script), - skipCleanup: args['skip-cleanup'] as bool); + skipCleanup: args['skip-cleanup'] as bool, + watch: args['watch'] as bool); await tool.run(); } diff --git a/pkgs/_macro_tool/lib/analyzer_macro_tool.dart b/pkgs/_macro_tool/lib/analyzer_macro_tool.dart index 61bfb718..c5695fd4 100644 --- a/pkgs/_macro_tool/lib/analyzer_macro_tool.dart +++ b/pkgs/_macro_tool/lib/analyzer_macro_tool.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:_analyzer_macros/macro_implementation.dart'; import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected_analyzer; import 'package:macro_service/macro_service.dart'; @@ -18,7 +19,8 @@ class AnalyzerMacroTool extends MacroTool { {required super.workspacePath, required super.packageConfigPath, required super.scriptPath, - required super.skipCleanup}) + required super.skipCleanup, + required super.watch}) : super.internal(); /// Runs macros in [scriptFile] on the analyzer. @@ -38,30 +40,47 @@ class AnalyzerMacroTool extends MacroTool { version: ProtocolVersion.macros1), packageConfig: Uri.file(packageConfigPath)); - final resolvedLibrary = (await analysisContext.currentSession - .getResolvedLibrary(scriptPath)) as ResolvedLibraryResult; + ResolvedLibraryResult resolvedLibrary; + // `asBroadcastStream` so repeated use of `first` below waits for the next + // change. + var events = File(scriptPath).watch().asBroadcastStream(); + var stopwatch = Stopwatch()..start(); + while (true) { + resolvedLibrary = (await analysisContext.currentSession + .getResolvedLibrary(scriptPath)) as ResolvedLibraryResult; + print('Resolved in ${stopwatch.elapsedMilliseconds}ms.'); - final errors = (await analysisContext.currentSession.getErrors(scriptPath)) - as ErrorsResult; - if (errors.errors.isNotEmpty) { - print('Errors: ${errors.errors}'); - } + final errors = (await analysisContext.currentSession + .getErrors(scriptPath)) as ErrorsResult; + final actualErrors = + errors.errors.where((e) => e.severity == Severity.error).toList(); + if (actualErrors.isNotEmpty) { + print('Errors: $actualErrors'); + } - final augmentationUnits = - resolvedLibrary.units.where((u) => u.isMacroPart).toList(); - if (augmentationUnits.isEmpty) { - return false; - } + final augmentationUnits = + resolvedLibrary.units.where((u) => u.isMacroPart).toList(); + if (augmentationUnits.isEmpty) { + return false; + } - print('Macro output (patched to use augment library): ' - '$augmentationFilePath'); - File(augmentationFilePath).writeAsStringSync(augmentationUnits - .single.content - // The analyzer produces augmentations in parts, but the CFE still - // wants them in augmentation libraries. Adjust the output accordingly. - .replaceAll('part of', 'augment library')); + print('Macro output (patched to use augment library): ' + '$augmentationFilePath'); + File(augmentationFilePath).writeAsStringSync(augmentationUnits + .single.content + // The analyzer produces augmentations in parts, but the CFE still + // wants them in augmentation libraries. Adjust the output accordingly. + .replaceAll('part of', 'augment library')); - return true; + if (!watch) return true; + + print('Running with --watch, waiting for next change to script.'); + await events.first; + print('Script changed, rerunning macro.'); + stopwatch.reset(); + analysisContext.changeFile(scriptPath); + await analysisContext.applyPendingFileChanges(); + } } @override diff --git a/pkgs/_macro_tool/lib/cfe_macro_tool.dart b/pkgs/_macro_tool/lib/cfe_macro_tool.dart index f6729f19..bb69cb61 100644 --- a/pkgs/_macro_tool/lib/cfe_macro_tool.dart +++ b/pkgs/_macro_tool/lib/cfe_macro_tool.dart @@ -17,7 +17,8 @@ class CfeMacroTool extends MacroTool { {required super.workspacePath, required super.packageConfigPath, required super.scriptPath, - required super.skipCleanup}) + required super.skipCleanup, + required super.watch}) : super.internal(); /// Runs macros in [scriptFile] using the CFE. @@ -27,6 +28,8 @@ class CfeMacroTool extends MacroTool { /// Returns whether an augmentation file was written. @override Future augment() async { + if (watch) throw UnimplementedError('--watch not implemented for CFE.'); + // TODO(davidmorgan): this dill comes from the Dart SDK running the test, // but `package:frontend_server` and `package:front_end` are used as a // library, so we will see version skew breakage. Find a better way. diff --git a/pkgs/_macro_tool/lib/macro_tool.dart b/pkgs/_macro_tool/lib/macro_tool.dart index 4da22dae..5ac75056 100644 --- a/pkgs/_macro_tool/lib/macro_tool.dart +++ b/pkgs/_macro_tool/lib/macro_tool.dart @@ -15,30 +15,35 @@ abstract class MacroTool { String packageConfigPath; String scriptPath; bool skipCleanup; + bool watch; MacroTool.internal( {required this.workspacePath, required this.packageConfigPath, required this.scriptPath, - required this.skipCleanup}); + required this.skipCleanup, + required this.watch}); factory MacroTool( {required HostOption host, required String workspacePath, required String packageConfigPath, required String scriptPath, - required bool skipCleanup}) => + required bool skipCleanup, + required bool watch}) => host == HostOption.analyzer ? AnalyzerMacroTool( workspacePath: workspacePath, packageConfigPath: packageConfigPath, scriptPath: scriptPath, - skipCleanup: skipCleanup) + skipCleanup: skipCleanup, + watch: watch) : CfeMacroTool( workspacePath: workspacePath, packageConfigPath: packageConfigPath, scriptPath: scriptPath, - skipCleanup: skipCleanup); + skipCleanup: skipCleanup, + watch: watch); Future run() async { print('Running ${p.basename(scriptPath)} with macros on $this.');