11d820890e
The change in [0] has propagated now everywhere, so we can use `utf8.encode()` instead of the longer `const Utf8Encoder.convert()`. As the checked-in SDK has been rolled to include [0] we can now rely on the better return type. [0] https://github.com/dart-lang/sdk/issues/52801 TEST=ci CoreLibraryReviewExempt: Minor cleanup. Change-Id: I2c0144023e03b2c265582d83a7fb9469b02f1570 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313563 Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
222 lines
7.3 KiB
Dart
222 lines
7.3 KiB
Dart
// Copyright (c) 2020, 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:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:analysis_server_client/listener/server_listener.dart';
|
|
import 'package:analysis_server_client/protocol.dart';
|
|
|
|
///
|
|
/// Add server arguments.
|
|
///
|
|
/// TODO(danrubel): Consider moving all cmdline argument consts
|
|
/// out of analysis_server and into analysis_server_client.
|
|
List<String> getServerArguments({
|
|
String? clientId,
|
|
String? clientVersion,
|
|
int? diagnosticPort,
|
|
String? instrumentationLogFile,
|
|
String? sdkPath,
|
|
bool suppressAnalytics = true,
|
|
bool useAnalysisHighlight2 = false,
|
|
}) {
|
|
var arguments = <String>[];
|
|
|
|
if (clientId != null) {
|
|
arguments.add('--client-id');
|
|
arguments.add(clientId);
|
|
}
|
|
if (clientVersion != null) {
|
|
arguments.add('--client-version');
|
|
arguments.add(clientVersion);
|
|
}
|
|
if (suppressAnalytics) {
|
|
arguments.add('--suppress-analytics');
|
|
}
|
|
if (diagnosticPort != null) {
|
|
arguments.add('--port');
|
|
arguments.add(diagnosticPort.toString());
|
|
}
|
|
if (instrumentationLogFile != null) {
|
|
arguments.add('--instrumentation-log-file=$instrumentationLogFile');
|
|
}
|
|
if (sdkPath != null) {
|
|
arguments.add('--sdk=$sdkPath');
|
|
}
|
|
if (useAnalysisHighlight2) {
|
|
arguments.add('--useAnalysisHighlight2');
|
|
}
|
|
return arguments;
|
|
}
|
|
|
|
/// A function via which data can be sent to a started server.
|
|
typedef CommandSender = void Function(List<int> utf8bytes);
|
|
|
|
/// Type of callbacks used to process notifications.
|
|
typedef NotificationProcessor = void Function(Notification notification);
|
|
|
|
/// Implementations of the class [ServerBase] manage an analysis server,
|
|
/// and facilitate communication to and from the server.
|
|
///
|
|
/// Clients outside of this package may not extend or implement this class.
|
|
abstract class ServerBase {
|
|
/// Replicate all data from the server process to stdout/stderr, when true.
|
|
final bool _stdioPassthrough;
|
|
|
|
/// Number which should be used to compute the 'id'
|
|
/// to send in the next command sent to the server.
|
|
int _nextId = 0;
|
|
|
|
/// If not `null`, [_listener] will be sent information
|
|
/// about interactions with the server.
|
|
final ServerListener? _listener;
|
|
|
|
/// Commands that have been sent to the server but not yet acknowledged,
|
|
/// and the [Completer] objects which should be completed
|
|
/// when acknowledgement is received.
|
|
final _pendingCommands = <String, Completer<Map<String, Object?>?>>{};
|
|
|
|
ServerBase({ServerListener? listener, bool stdioPassthrough = false})
|
|
: _listener = listener,
|
|
_stdioPassthrough = stdioPassthrough;
|
|
|
|
ServerListener? get listener => _listener;
|
|
|
|
/// If the implementation of [ServerBase] captures an error stream,
|
|
/// it can use this to forward the errors to [listener] and [stderr] if
|
|
/// appropriate.
|
|
void errorProcessor(
|
|
String line, NotificationProcessor? notificationProcessor) {
|
|
if (_stdioPassthrough) stderr.writeln(line);
|
|
var trimmedLine = line.trim();
|
|
listener?.errorMessage(trimmedLine);
|
|
}
|
|
|
|
/// Force kill the server. Returns a future that completes when the server
|
|
/// stops.
|
|
Future kill({String reason = 'none'});
|
|
|
|
/// Start listening to output from the server,
|
|
/// and deliver notifications to [notificationProcessor].
|
|
void listenToOutput({NotificationProcessor notificationProcessor});
|
|
|
|
/// Handle a (possibly) json encoded object, completing the [Completer] in
|
|
/// [_pendingCommands] corresponding to the response. Reports problems in
|
|
/// decoding or message synchronization using [listener], and replicates
|
|
/// raw data to [stdout] as appropriate.
|
|
void outputProcessor(
|
|
String line, NotificationProcessor? notificationProcessor) {
|
|
if (_stdioPassthrough) stdout.writeln(line);
|
|
var trimmedLine = line.trim();
|
|
|
|
// Guard against lines like:
|
|
// {"event":"server.connected","params":{...}}The Dart VM service is listening on ...
|
|
const dartVMServiceMessage = 'The Dart VM service is listening on ';
|
|
if (trimmedLine.contains(dartVMServiceMessage)) {
|
|
trimmedLine = trimmedLine
|
|
.substring(0, trimmedLine.indexOf(dartVMServiceMessage))
|
|
.trim();
|
|
}
|
|
if (trimmedLine.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
listener?.messageReceived(trimmedLine);
|
|
Map<String, Object?> message;
|
|
try {
|
|
message = json.decoder.convert(trimmedLine);
|
|
} catch (exception) {
|
|
listener?.badMessage(trimmedLine, exception);
|
|
return;
|
|
}
|
|
|
|
// Handle response.
|
|
final id = message[Response.ID];
|
|
if (id != null) {
|
|
final completer = _pendingCommands.remove(id);
|
|
if (completer == null) {
|
|
listener?.unexpectedResponse(message, id);
|
|
return;
|
|
}
|
|
|
|
final errorJson = message[Response.ERROR];
|
|
if (errorJson != null) {
|
|
completer.completeError(
|
|
RequestError.fromJson(ResponseDecoder(null), '.error', errorJson));
|
|
return;
|
|
}
|
|
|
|
final resultJson = message[Response.RESULT];
|
|
if (resultJson is Map<String, Object?>?) {
|
|
completer.complete(resultJson);
|
|
return;
|
|
}
|
|
|
|
listener?.unexpectedResponseFormat(message);
|
|
return;
|
|
}
|
|
|
|
// Handle notification.
|
|
final event = message[Notification.EVENT];
|
|
if (event is String) {
|
|
final paramsJson = message[Notification.PARAMS];
|
|
if (paramsJson is Map<String, Object?>) {
|
|
if (notificationProcessor != null) {
|
|
notificationProcessor(Notification(event, paramsJson));
|
|
}
|
|
} else {
|
|
listener?.unexpectedNotificationFormat(message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
listener?.unexpectedMessage(message);
|
|
}
|
|
|
|
/// Send a command to the server. An 'id' will be automatically assigned.
|
|
/// The returned [Future] will be completed when the server acknowledges
|
|
/// the command with a response.
|
|
/// If the server acknowledges the command with a normal (non-error) response,
|
|
/// the future will be completed with the 'result' field from the response.
|
|
/// If the server acknowledges the command with an error response,
|
|
/// the future will be completed with an error.
|
|
Future<Map<String, Object?>?> send(
|
|
String method, Map<String, Object?>? params);
|
|
|
|
/// Encodes a request for transmission and sends it as a utf8 encoded byte
|
|
/// string with [sendWith].
|
|
Future<Map<String, Object?>?> sendCommandWith(
|
|
String method, Map<String, Object?>? params, CommandSender sendWith) {
|
|
var id = '${_nextId++}';
|
|
var command = <String, dynamic>{Request.ID: id, Request.METHOD: method};
|
|
if (params != null) {
|
|
command[Request.PARAMS] = params;
|
|
}
|
|
final completer = Completer<Map<String, Object?>?>();
|
|
_pendingCommands[id] = completer;
|
|
var line = json.encode(command);
|
|
listener?.requestSent(line);
|
|
sendWith(utf8.encode('$line\n'));
|
|
return completer.future;
|
|
}
|
|
|
|
/// Start the server. The returned future completes when the server
|
|
/// is started and it is valid to call [listenToOutput].
|
|
Future start({
|
|
String clientId,
|
|
String clientVersion,
|
|
int diagnosticPort,
|
|
String instrumentationLogFile,
|
|
String sdkPath,
|
|
bool suppressAnalytics,
|
|
bool useAnalysisHighlight2,
|
|
});
|
|
|
|
/// Attempt to gracefully shutdown the server.
|
|
/// If that fails, then force it to shut down.
|
|
Future stop({Duration timeLimit});
|
|
}
|