blob: 25811c73a76cf11a10b4d7a9cfc90b7bf9fa93a0 [file] [log] [blame]
// 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 'base/bot_detector.dart';
import 'base/common.dart';
import 'base/io.dart' as io;
import 'base/logger.dart';
import 'convert.dart';
import 'resident_runner.dart';
/// An implementation of the devtools launcher that uses `pub global activate` to
/// start a server instance.
class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({
required ProcessManager processManager,
required String dartExecutable,
required Logger logger,
required BotDetector botDetector,
}) : _processManager = processManager,
_dartExecutable = dartExecutable,
_logger = logger,
_botDetector = botDetector;
final ProcessManager _processManager;
final String _dartExecutable;
final Logger _logger;
final BotDetector _botDetector;
final Completer<void> _processStartCompleter = Completer<void>();
io.Process? _devToolsProcess;
bool _devToolsProcessKilled = false;
@visibleForTesting
Future<void>? devToolsProcessExit;
static final RegExp _serveDevToolsPattern =
RegExp(r'Serving DevTools at ((http|//)[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 (vmServiceUri != null) '--vm-uri=$vmServiceUri',
...?additionalArguments,
]);
_processStartCompleter.complete();
final Completer<Uri> completer = Completer<Uri>();
_devToolsProcess!.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
final Match? match = _serveDevToolsPattern.firstMatch(line);
if (match != null) {
final String url = match[1]!;
completer.complete(Uri.parse(url));
}
});
_devToolsProcess!.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_logger.printError);
final bool runningOnBot = await _botDetector.isRunningOnBot;
devToolsProcessExit = _devToolsProcess!.exitCode.then(
(int exitCode) {
if (!_devToolsProcessKilled && runningOnBot) {
throwToolExit('DevTools process failed: exitCode=$exitCode');
}
}
);
devToolsUrl = await completer.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();
}
}
}