| // 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; |
| }, |
| ); |
| } |
| } |