From 7ddc4ce8ba9efd74fbef529a29e35cdf00fda41a Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Wed, 18 Jul 2018 21:32:09 +0000 Subject: [PATCH] Train the analysis server snapshot on pkg/analyzer_cli. Change-Id: I07dcaec344d681a4abf8f09558d83a68efdd70e8 Reviewed-on: https://dart-review.googlesource.com/65575 Commit-Queue: Devon Carew Reviewed-by: Konstantin Shcheglov Reviewed-by: Brian Wilkerson --- .../lib/src/server/dev_server.dart | 189 ++++++++++++++++++ .../lib/src/server/driver.dart | 52 ++++- utils/analysis_server/BUILD.gn | 4 +- 3 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 pkg/analysis_server/lib/src/server/dev_server.dart diff --git a/pkg/analysis_server/lib/src/server/dev_server.dart b/pkg/analysis_server/lib/src/server/dev_server.dart new file mode 100644 index 00000000000..7484889f96a --- /dev/null +++ b/pkg/analysis_server/lib/src/server/dev_server.dart @@ -0,0 +1,189 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. 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:io'; + +import 'package:analysis_server/protocol/protocol.dart'; +import 'package:analysis_server/src/channel/channel.dart'; +import 'package:analysis_server/src/socket_server.dart'; +import 'package:path/path.dart' as path; + +/** + * Instances of the class [DevAnalysisServer] implement a simple analysis + * server implementation, used to analyze one or more packages and then + * terminate the server. + */ +class DevAnalysisServer { + static bool get _terminalSupportsAnsi { + return stdout.supportsAnsiEscapes && + stdioType(stdout) == StdioType.terminal; + } + + /** + * An object that can handle either a WebSocket connection or a connection + * to the client over stdio. + */ + final SocketServer socketServer; + + /** + * Initialize a newly created stdio server. + */ + DevAnalysisServer(this.socketServer); + + /** + * Analyze the given directories and display any results to stdout. + * + * Return a future that will be completed with an exit code when analysis + * finishes. + */ + Future processDirectories(List directories) async { + final String bold = _terminalSupportsAnsi ? '\u001b[1m' : ''; + final String none = _terminalSupportsAnsi ? '\u001b[0m' : ''; + + print('Analyzing ${directories.join(', ')}...'); + + Stopwatch timer = new Stopwatch()..start(); + + Completer whenComplete = new Completer(); + + DevChannel channel = new DevChannel(); + + int exitCode = 0; + + void handleStatusNotification(Notification notification) { + Map params = notification.params; + if (params.containsKey('analysis')) { + bool isAnalyzing = params['analysis']['isAnalyzing']; + if (!isAnalyzing) { + timer.stop(); + double seconds = timer.elapsedMilliseconds / 1000.0; + print('Completed in ${seconds.toStringAsFixed(1)}s.'); + whenComplete.complete(exitCode); + } + } + } + + void handleErrorsNotification(Notification notification) { + String filePath = notification.params['file']; + List errors = notification.params['errors']; + + if (errors.isEmpty) { + return; + } + + filePath = path.relative(filePath); + + for (Map error in errors) { + if (error['type'] == 'TODO') { + continue; + } + + String severity = error['severity'].toLowerCase(); + if (severity == 'warning' && exitCode < 1) { + exitCode = 1; + } else if (severity == 'error' && exitCode < 2) { + exitCode = 2; + } + + String message = error['message']; + if (message.endsWith('.')) { + message = message.substring(0, message.length - 1); + } + String code = error['code']; + int line = error['location']['startLine']; + int column = error['location']['startColumn']; + + print(' $severity • $bold$message$none at $filePath:$line:$column • ' + '($code)'); + } + } + + void handleServerError(Notification notification) { + Map params = notification.params; + String message = params['message']; + String stackTrace = params['stackTrace']; + + print(message); + if (stackTrace != null) { + print(stackTrace); + } + + exitCode = 3; + } + + channel.onNotification.listen((Notification notification) { + if (notification.event == 'server.status') { + handleStatusNotification(notification); + } else if (notification.event == 'analysis.errors') { + handleErrorsNotification(notification); + } else if (notification.event == 'server.error') { + handleServerError(notification); + } + }); + + socketServer.createAnalysisServer(channel); + + int id = 0; + channel.sendRequest(new Request('${id++}', 'server.setSubscriptions', { + 'subscriptions': ['STATUS'], + })); + + directories = + directories.map((dir) => path.normalize(path.absolute(dir))).toList(); + + channel.sendRequest(new Request('${id++}', 'analysis.setAnalysisRoots', { + 'included': directories, + 'excluded': [], + })); + + return whenComplete.future; + } +} + +class DevChannel implements ServerCommunicationChannel { + StreamController _requestController = new StreamController(); + StreamController _notificationController = + new StreamController(); + + Map> _responseCompleters = {}; + + @override + void close() { + _notificationController.close(); + } + + @override + void listen( + void Function(Request request) onRequest, { + Function onError, + void Function() onDone, + }) { + _requestController.stream.listen( + onRequest, + onError: onError, + onDone: onDone, + ); + } + + @override + void sendNotification(Notification notification) { + _notificationController.add(notification); + } + + @override + void sendResponse(Response response) { + Completer completer = _responseCompleters.remove(response.id); + completer?.complete(response); + } + + Future sendRequest(Request request) { + Completer completer = new Completer(); + _responseCompleters[request.id] = completer; + _requestController.add(request); + return completer.future; + } + + Stream get onNotification => _notificationController.stream; +} diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart index 1d11ede9e4b..2245158733f 100644 --- a/pkg/analysis_server/lib/src/server/driver.dart +++ b/pkg/analysis_server/lib/src/server/driver.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'dart:math'; import 'package:analysis_server/src/analysis_server.dart'; +import 'package:analysis_server/src/server/dev_server.dart'; import 'package:analysis_server/src/server/diagnostic_server.dart'; import 'package:analysis_server/src/server/http_server.dart'; import 'package:analysis_server/src/server/stdio_server.dart'; @@ -31,6 +32,8 @@ import 'package:telemetry/telemetry.dart' as telemetry; /// TODO(pquitslund): replaces with a simple [ArgParser] instance /// when the args package supports ignoring unrecognized /// options/flags (https://github.com/dart-lang/args/issues/9). +/// TODO(devoncarew): Consider removing the ability to support unrecognized +/// flags for the analysis server. class CommandLineParser { final List _knownFlags; final bool _alwaysIgnoreUnrecognized; @@ -267,6 +270,11 @@ class Driver implements ServerStarter { */ static const String USE_FASTA_PARSER = "use-fasta-parser"; + /** + * A directory to analyze in order to train an analysis server snapshot. + */ + static const String TRAIN_USING = "train-using"; + /** * The instrumentation server that is to be used by the analysis server. */ @@ -288,8 +296,6 @@ class Driver implements ServerStarter { HttpAnalysisServer httpServer; - StdioAnalysisServer stdioServer; - Driver(); /** @@ -354,6 +360,15 @@ class Driver implements ServerStarter { return null; } + String trainDirectory = results[TRAIN_USING]; + if (trainDirectory != null) { + if (!FileSystemEntity.isDirectorySync(trainDirectory)) { + print("Training directory '$trainDirectory' not found.\n"); + exitCode = 1; + return null; + } + } + int port; bool serve_http = false; if (results[PORT_OPTION] != null) { @@ -430,26 +445,38 @@ class Driver implements ServerStarter { fileResolverProvider, packageResolverProvider); httpServer = new HttpAnalysisServer(socketServer); - stdioServer = new StdioAnalysisServer(socketServer); diagnosticServer.httpServer = httpServer; if (serve_http) { diagnosticServer.startOnPort(port); } - _captureExceptions(instrumentationService, () { - stdioServer.serveStdio().then((_) async { - // TODO(brianwilkerson) Determine whether this await is necessary. - await null; + if (trainDirectory != null) { + DevAnalysisServer devServer = new DevAnalysisServer(socketServer); + devServer.processDirectories([trainDirectory]).then((int exitCode) async { if (serve_http) { httpServer.close(); } await instrumentationService.shutdown(); - exit(0); + exit(exitCode); }); - }, - print: - results[INTERNAL_PRINT_TO_CONSOLE] ? null : httpServer.recordPrint); + } else { + _captureExceptions(instrumentationService, () { + StdioAnalysisServer stdioServer = new StdioAnalysisServer(socketServer); + stdioServer.serveStdio().then((_) async { + // TODO(brianwilkerson) Determine whether this await is necessary. + await null; + if (serve_http) { + httpServer.close(); + } + await instrumentationService.shutdown(); + exit(0); + }); + }, + print: results[INTERNAL_PRINT_TO_CONSOLE] + ? null + : httpServer.recordPrint); + } return socketServer.analysisServer; } @@ -546,6 +573,9 @@ class Driver implements ServerStarter { help: "Enable the Dart 2.0 Common Front End implementation"); parser.addFlag(USE_FASTA_PARSER, help: "Whether to enable parsing via the Fasta parser"); + parser.addOption(TRAIN_USING, + help: "Pass in a directory to analyze for purposes of training an " + "analysis server snapshot."); return parser; } diff --git a/utils/analysis_server/BUILD.gn b/utils/analysis_server/BUILD.gn index 625dd25fe51..dbe434c38a6 100644 --- a/utils/analysis_server/BUILD.gn +++ b/utils/analysis_server/BUILD.gn @@ -7,5 +7,7 @@ import("../application_snapshot.gni") application_snapshot("analysis_server") { dart_version = 2 main_dart = "../../pkg/analysis_server/bin/server.dart" - training_args = [ "--help" ] + training_args = [ + "--train-using=" + rebase_path("../../pkg/analyzer_cli"), + ] }