| // Copyright 2014 The Flutter Authors. 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 'package:meta/meta.dart'; | |
| import 'package:process/process.dart'; | |
| import 'artifacts.dart'; | |
| import 'base/bot_detector.dart'; | |
| import 'base/common.dart'; | |
| import 'base/io.dart' as io; | |
| import 'base/logger.dart'; | |
| import 'base/utils.dart'; | |
| import 'resident_runner.dart'; | |
| /// An implementation of the devtools launcher that uses `dart devtools` to | |
| /// start a DevTools server instance. | |
| class DevtoolsServerLauncher extends DevtoolsLauncher { | |
| DevtoolsServerLauncher({ | |
| required this._processManager, | |
| required this._logger, | |
| required this._botDetector, | |
| required this._artifacts, | |
| }); | |
| final ProcessManager _processManager; | |
| final Artifacts _artifacts; | |
| late final String _dartExecutable = _artifacts.getArtifactPath(Artifact.engineDartBinary); | |
| final Logger _logger; | |
| final BotDetector _botDetector; | |
| final _processStartCompleter = Completer<void>(); | |
| io.Process? _devToolsProcess; | |
| var _devToolsProcessKilled = false; | |
| @visibleForTesting | |
| Future<void>? devToolsProcessExit; | |
| static final _serveDevToolsPattern = RegExp( | |
| r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$', | |
| ); | |
| static final _serveDtdPattern = RegExp( | |
| r'Serving the Dart Tooling Daemon at (ws:\/\/[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$', | |
| ); | |
| @override | |
| Future<void> get processStart => _processStartCompleter.future; | |
| @override | |
| Future<void> launch(Uri? vmServiceUri, {List<String>? additionalArguments}) async { | |
| // Place this entire method in a try/catch that swallows exceptions because | |
| // this method is guaranteed not to return a Future that throws. | |
| try { | |
| _devToolsProcess = await _processManager.start(<String>[ | |
| _dartExecutable, | |
| 'devtools', | |
| '--no-launch-browser', | |
| if (printDtdUri) '--print-dtd', | |
| if (vmServiceUri != null) '--vm-uri=$vmServiceUri', | |
| ...?additionalArguments, | |
| ]); | |
| _processStartCompleter.complete(); | |
| final devToolsCompleter = Completer<Uri>(); | |
| _devToolsProcess!.stdout.transform(utf8LineDecoder).listen((String line) { | |
| final Match? dtdMatch = _serveDtdPattern.firstMatch(line); | |
| if (dtdMatch != null) { | |
| final String uri = dtdMatch[1]!; | |
| dtdUri = Uri.parse(uri); | |
| } | |
| final Match? devToolsMatch = _serveDevToolsPattern.firstMatch(line); | |
| if (devToolsMatch != null) { | |
| final String url = devToolsMatch[1]!; | |
| devToolsCompleter.complete(Uri.parse(url)); | |
| } | |
| }); | |
| _devToolsProcess!.stderr.transform(utf8LineDecoder).listen(_logger.printError); | |
| final bool runningOnBot = await _botDetector.isRunningOnBot; | |
| devToolsProcessExit = _devToolsProcess!.exitCode.then((int exitCode) { | |
| if (!_devToolsProcessKilled && runningOnBot) { | |
| throwToolExit('DevTools process failed: exitCode=$exitCode'); | |
| } | |
| }); | |
| // We do not need to wait for a [Completer] holding the DTD URI because | |
| // the DTD URI will be output to stdout before the DevTools URI. Awaiting | |
| // a [Completer] for the DevTools URI ensures both values will be | |
| // populated before returning. | |
| devToolsUrl = await devToolsCompleter.future; | |
| } on Exception catch (e, st) { | |
| _logger.printError('Failed to launch DevTools: $e', stackTrace: st); | |
| } | |
| } | |
| @override | |
| Future<DevToolsServerAddress?> serve() async { | |
| if (activeDevToolsServer == null) { | |
| await launch(null); | |
| } | |
| return activeDevToolsServer; | |
| } | |
| @override | |
| Future<void> close() async { | |
| if (devToolsUrl != null) { | |
| devToolsUrl = null; | |
| } | |
| if (_devToolsProcess != null) { | |
| _devToolsProcessKilled = true; | |
| _devToolsProcess!.kill(); | |
| } | |
| } | |
| } |