blob: c553a997df5d075708652362aa9fe8b6746171e7 [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:browser_launcher/browser_launcher.dart';
import 'package:meta/meta.dart';
import 'package:process/process.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 the server package.
///
/// This is implemented in isolated to prevent the flutter_tool from needing
/// a devtools dep in google3.
class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({
@required ProcessManager processManager,
@required String pubExecutable,
@required Logger logger,
}) : _processManager = processManager,
_pubExecutable = pubExecutable,
_logger = logger;
final ProcessManager _processManager;
final String _pubExecutable;
final Logger _logger;
io.Process _devToolsProcess;
Uri _devToolsUri;
static final RegExp _serveDevToolsPattern =
RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
@override
Future<void> launch(Uri vmServiceUri, {bool openInBrowser = false}) async {
if (_devToolsProcess != null && _devToolsUri != null) {
// DevTools is already running.
if (openInBrowser) {
await Chrome.start(<String>[_devToolsUri.toString()]);
}
return;
}
final Status status = _logger.startProgress(
'Activating Dart DevTools...',
);
try {
// TODO(kenz): https://github.com/dart-lang/pub/issues/2791 - calling `pub
// global activate` adds ~ 4.5 seconds of latency.
final io.ProcessResult _devToolsActivateProcess = await _processManager.run(<String>[
_pubExecutable,
'global',
'activate',
'devtools'
]);
if (_devToolsActivateProcess.exitCode != 0) {
status.cancel();
_logger.printError('Error running `pub global activate '
'devtools`:\n${_devToolsActivateProcess.stderr}');
return;
}
status.stop();
_devToolsProcess = await _processManager.start(<String>[
_pubExecutable,
'global',
'run',
'devtools',
if (!openInBrowser) '--no-launch-browser',
if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
]);
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) {
// We are trying to pull "http://127.0.0.1:9101" from "Serving
// DevTools at http://127.0.0.1:9101.". `match[1]` will return
// "http://127.0.0.1:9101.", and we need to trim the trailing period
// so that we don't throw an exception from `Uri.parse`.
String uri = match[1];
if (uri.endsWith('.')) {
uri = uri.substring(0, uri.length - 1);
}
completer.complete(Uri.parse(uri));
}
_logger.printStatus(line);
});
_devToolsProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_logger.printError);
_devToolsUri = await completer.future;
} on Exception catch (e, st) {
status.cancel();
_logger.printError('Failed to launch DevTools: $e', stackTrace: st);
}
}
@override
Future<DevToolsServerAddress> serve({bool openInBrowser = false}) async {
await launch(null, openInBrowser: openInBrowser);
if (_devToolsUri == null) {
return null;
}
return DevToolsServerAddress(_devToolsUri.host, _devToolsUri.port);
}
@override
Future<void> close() async {
if (_devToolsProcess != null) {
_devToolsProcess.kill();
await _devToolsProcess.exitCode;
}
}
}