blob: 90cb42d40626e73e0e5c4f7e5bab9b2d22e7f99c [file] [log] [blame]
// 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:convert';
import 'dart:io';
import 'package:analysis_server_client/protocol.dart';
import 'package:analysis_server_client/src/server_base.dart';
import 'package:path/path.dart';
export 'package:analysis_server_client/src/server_base.dart'
show NotificationProcessor;
/// Instances of the class [Server] manage a server process,
/// and facilitate communication to and from the server.
///
/// Clients may not extend, implement or mix-in this class.
class Server extends ServerBase {
/// Server process object, or `null` if server hasn't been started yet
/// or if the server has already been stopped.
Process? _process;
/// The stderr subscription or `null` if either
/// [listenToOutput] has not been called or [stop] has been called.
StreamSubscription<String>? _stderrSubscription;
/// The stdout subscription or `null` if either
/// [listenToOutput] has not been called or [stop] has been called.
StreamSubscription<String>? _stdoutSubscription;
Server({super.listener, Process? process, super.stdioPassthrough})
: _process = process;
/// Force kill the server. Returns exit code future.
@override
Future<int> kill({String reason = 'none'}) {
listener?.killingServerProcess(reason);
final process = _process!;
_process = null;
process.kill();
return process.exitCode;
}
/// Start listening to output from the server,
/// and deliver notifications to [notificationProcessor].
@override
void listenToOutput({NotificationProcessor? notificationProcessor}) {
_stdoutSubscription = _process!.stdout
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((line) => outputProcessor(line, notificationProcessor));
_stderrSubscription = _process!.stderr
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((line) => errorProcessor(line, notificationProcessor));
}
/// 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.
@override
Future<Map<String, Object?>?> send(
String method, Map<String, Object?>? params) =>
sendCommandWith(method, params, _process!.stdin.add);
/// Start the server.
///
/// If [profileServer] is `true`, the server will be started
/// with "--observe" and "--pause-isolates-on-exit", allowing the observatory
/// to be used.
///
/// If [serverPath] is specified, then that analysis server will be launched,
/// otherwise the analysis server snapshot in the SDK will be launched.
///
/// If [enableAsserts] is specified, then asserts will be enabled in the new
/// dart process for that server. This is typically just useful to enable
/// locally for debugging.
@override
Future start({
String? clientId,
String? clientVersion,
int? diagnosticPort,
String? instrumentationLogFile,
bool profileServer = false,
String? sdkPath,
String? serverPath,
int? servicePort,
bool suppressAnalytics = true,
bool useAnalysisHighlight2 = false,
bool enableAsserts = false,
String? dartBinary,
}) async {
if (_process != null) {
throw Exception('Process already started');
}
dartBinary ??= Platform.executable;
// The integration tests run 3x faster when run from snapshots
// (you need to run test.py with --use-sdk).
if (serverPath == null) {
// Look for snapshots/analysis_server.dart.snapshot.
serverPath = normalize(join(dirname(Platform.resolvedExecutable),
'snapshots', 'analysis_server.dart.snapshot'));
if (!FileSystemEntity.isFileSync(serverPath)) {
// Look for dart-sdk/bin/snapshots/analysis_server.dart.snapshot.
serverPath = normalize(join(dirname(Platform.resolvedExecutable),
'dart-sdk', 'bin', 'snapshots', 'analysis_server.dart.snapshot'));
}
}
var arguments = <String>[];
//
// Add VM arguments.
//
if (profileServer) {
if (servicePort == null) {
arguments.add('--observe');
} else {
arguments.add('--observe=$servicePort');
}
arguments.add('--pause-isolates-on-exit');
} else if (servicePort != null) {
arguments.add('--enable-vm-service=$servicePort');
}
if (Platform.packageConfig != null) {
arguments.add('--packages=${Platform.packageConfig}');
}
if (enableAsserts) {
arguments.add('--enable-asserts');
}
//
// Add the server executable.
//
arguments.add(serverPath);
arguments.addAll(getServerArguments(
clientId: clientId,
clientVersion: clientVersion,
suppressAnalytics: suppressAnalytics,
diagnosticPort: diagnosticPort,
instrumentationLogFile: instrumentationLogFile,
sdkPath: sdkPath,
useAnalysisHighlight2: useAnalysisHighlight2));
listener?.startingServer(dartBinary, arguments);
final process = await Process.start(dartBinary, arguments);
_process = process;
// ignore: unawaited_futures
process.exitCode.then((int code) {
if (code != 0 && _process != null) {
// Report an error if server abruptly terminated
listener?.unexpectedStop(code);
}
});
}
/// Attempt to gracefully shutdown the server.
/// If that fails, then kill the process.
@override
Future<int> stop({Duration? timeLimit}) async {
timeLimit ??= const Duration(seconds: 5);
final process = _process;
if (process == null) {
// Process already exited
return -1;
}
final future = send(SERVER_REQUEST_SHUTDOWN, null);
_process = null;
await future
// fall through to wait for exit
.timeout(timeLimit, onTimeout: () {
return {};
}).whenComplete(() async {
await _stderrSubscription?.cancel();
_stderrSubscription = null;
await _stdoutSubscription?.cancel();
_stdoutSubscription = null;
});
return process.exitCode.timeout(
timeLimit,
onTimeout: () {
listener?.killingServerProcess('server failed to exit');
process.kill();
return process.exitCode;
},
);
}
}