Unverified Commit 912c3ab1 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Revert "Revert "[flutter_tools] Serve DevTools at app start (#73366)" (#73896)" (#73903)

This reverts commit 388dcd24.
parent ffbbf93c
...@@ -156,6 +156,8 @@ Future<void> main(List<String> args) async { ...@@ -156,6 +156,8 @@ Future<void> main(List<String> args) async {
processManager: globals.processManager, processManager: globals.processManager,
pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable), pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable),
logger: globals.logger, logger: globals.logger,
platform: globals.platform,
persistentToolState: globals.persistentToolState,
), ),
Logger: () { Logger: () {
final LoggerFactory loggerFactory = LoggerFactory( final LoggerFactory loggerFactory = LoggerFactory(
......
...@@ -101,6 +101,7 @@ class AttachCommand extends FlutterCommand { ...@@ -101,6 +101,7 @@ class AttachCommand extends FlutterCommand {
); );
usesTrackWidgetCreation(verboseHelp: verboseHelp); usesTrackWidgetCreation(verboseHelp: verboseHelp);
addDdsOptions(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions();
usesDeviceTimeoutOption(); usesDeviceTimeoutOption();
hotRunnerFactory ??= HotRunnerFactory(); hotRunnerFactory ??= HotRunnerFactory();
} }
...@@ -405,7 +406,11 @@ known, it can be explicitly provided to attach via the command-line, e.g. ...@@ -405,7 +406,11 @@ known, it can be explicitly provided to attach via the command-line, e.g.
); );
flutterDevice.observatoryUris = observatoryUris; flutterDevice.observatoryUris = observatoryUris;
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice]; final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(buildInfo, disableDds: boolArg('disable-dds')); final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
buildInfo,
disableDds: boolArg('disable-dds'),
devToolsServerAddress: devToolsServerAddress,
);
return buildInfo.isDebug return buildInfo.isDebug
? hotRunnerFactory.build( ? hotRunnerFactory.build(
......
...@@ -871,8 +871,7 @@ class DevToolsDomain extends Domain { ...@@ -871,8 +871,7 @@ class DevToolsDomain extends Domain {
Future<Map<String, dynamic>> serve([ Map<String, dynamic> args ]) async { Future<Map<String, dynamic>> serve([ Map<String, dynamic> args ]) async {
_devtoolsLauncher ??= DevtoolsLauncher.instance; _devtoolsLauncher ??= DevtoolsLauncher.instance;
final bool openInBrowser = args != null && (args['openInBrowser'] == 'true'); final DevToolsServerAddress server = await _devtoolsLauncher.serve();
final DevToolsServerAddress server = await _devtoolsLauncher.serve(openInBrowser: openInBrowser);
return<String, dynamic>{ return<String, dynamic>{
'host': server?.host, 'host': server?.host,
'port': server?.port, 'port': server?.port,
......
...@@ -134,6 +134,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -134,6 +134,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
usesDeviceUserOption(); usesDeviceUserOption();
usesDeviceTimeoutOption(); usesDeviceTimeoutOption();
addDdsOptions(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions();
addAndroidSpecificBuildOptions(hide: !verboseHelp); addAndroidSpecificBuildOptions(hide: !verboseHelp);
} }
...@@ -195,6 +196,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment ...@@ -195,6 +196,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
hostVmServicePort: hostVmservicePort, hostVmServicePort: hostVmservicePort,
disablePortPublication: disablePortPublication, disablePortPublication: disablePortPublication,
ddsPort: ddsPort, ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
verboseSystemLogs: boolArg('verbose-system-logs'), verboseSystemLogs: boolArg('verbose-system-logs'),
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '', hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '', port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
......
...@@ -184,6 +184,8 @@ Future<T> runInContext<T>( ...@@ -184,6 +184,8 @@ Future<T> runInContext<T>(
processManager: globals.processManager, processManager: globals.processManager,
pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable), pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable),
logger: globals.logger, logger: globals.logger,
platform: globals.platform,
persistentToolState: globals.persistentToolState,
), ),
Doctor: () => Doctor(logger: globals.logger), Doctor: () => Doctor(logger: globals.logger),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
......
...@@ -852,6 +852,7 @@ class DebuggingOptions { ...@@ -852,6 +852,7 @@ class DebuggingOptions {
this.disablePortPublication = false, this.disablePortPublication = false,
this.deviceVmServicePort, this.deviceVmServicePort,
this.ddsPort, this.ddsPort,
this.devToolsServerAddress,
this.hostname, this.hostname,
this.port, this.port,
this.webEnableExposeUrl, this.webEnableExposeUrl,
...@@ -895,6 +896,7 @@ class DebuggingOptions { ...@@ -895,6 +896,7 @@ class DebuggingOptions {
disablePortPublication = false, disablePortPublication = false,
deviceVmServicePort = null, deviceVmServicePort = null,
ddsPort = null, ddsPort = null,
devToolsServerAddress = null,
vmserviceOutFile = null, vmserviceOutFile = null,
fastStart = false, fastStart = false,
webEnableExpressionEvaluation = false, webEnableExpressionEvaluation = false,
...@@ -924,6 +926,7 @@ class DebuggingOptions { ...@@ -924,6 +926,7 @@ class DebuggingOptions {
final int deviceVmServicePort; final int deviceVmServicePort;
final bool disablePortPublication; final bool disablePortPublication;
final int ddsPort; final int ddsPort;
final Uri devToolsServerAddress;
final String port; final String port;
final String hostname; final String hostname;
final bool webEnableExposeUrl; final bool webEnableExposeUrl;
......
...@@ -4,13 +4,15 @@ ...@@ -4,13 +4,15 @@
import 'dart:async'; import 'dart:async';
import 'package:browser_launcher/browser_launcher.dart'; import 'package:http/http.dart' as http;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'base/io.dart' as io; import 'base/io.dart' as io;
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/platform.dart';
import 'convert.dart'; import 'convert.dart';
import 'persistent_tool_state.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
/// An implementation of the devtools launcher that uses the server package. /// An implementation of the devtools launcher that uses the server package.
...@@ -19,59 +21,68 @@ import 'resident_runner.dart'; ...@@ -19,59 +21,68 @@ import 'resident_runner.dart';
/// a devtools dep in google3. /// a devtools dep in google3.
class DevtoolsServerLauncher extends DevtoolsLauncher { class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({ DevtoolsServerLauncher({
@required Platform platform,
@required ProcessManager processManager, @required ProcessManager processManager,
@required String pubExecutable, @required String pubExecutable,
@required Logger logger, @required Logger logger,
@required PersistentToolState persistentToolState,
}) : _processManager = processManager, }) : _processManager = processManager,
_pubExecutable = pubExecutable, _pubExecutable = pubExecutable,
_logger = logger; _logger = logger,
_platform = platform,
_persistentToolState = persistentToolState;
final ProcessManager _processManager; final ProcessManager _processManager;
final String _pubExecutable; final String _pubExecutable;
final Logger _logger; final Logger _logger;
final Platform _platform;
final PersistentToolState _persistentToolState;
io.Process _devToolsProcess; io.Process _devToolsProcess;
Uri _devToolsUri;
static final RegExp _serveDevToolsPattern = static final RegExp _serveDevToolsPattern =
RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)'); RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
@override @override
Future<void> launch(Uri vmServiceUri, {bool openInBrowser = false}) async { Future<void> launch(Uri vmServiceUri) async {
if (_devToolsProcess != null && _devToolsUri != null) { // Place this entire method in a try/catch that swallows exceptions because
// DevTools is already running. // we do not want to block Flutter run/attach operations on a DevTools
if (openInBrowser) { // failure.
await Chrome.start(<String>[_devToolsUri.toString()]); try {
bool offline = false;
try {
const String pubHostedUrlKey = 'PUB_HOSTED_URL';
if (_platform.environment.containsKey(pubHostedUrlKey)) {
await http.head(_platform.environment[pubHostedUrlKey]);
} else {
await http.head('https://pub.dev');
} }
return; } on Exception {
offline = true;
} }
final Status status = _logger.startProgress( if (offline) {
'Activating Dart DevTools...', // TODO(kenz): we should launch an already activated version of DevTools
); // here, if available, once DevTools has offline support. DevTools does
try { // not work without internet currently due to the failed request of a
// TODO(kenz): https://github.com/dart-lang/pub/issues/2791 - calling `pub // couple scripts. See https://github.com/flutter/devtools/issues/2420.
// global activate` adds ~ 4.5 seconds of latency. return;
final io.ProcessResult _devToolsActivateProcess = await _processManager.run(<String>[ } else {
_pubExecutable, final bool didActivateDevTools = await _activateDevTools();
'global', final bool devToolsActive = await _checkForActiveDevTools();
'activate', if (!didActivateDevTools && !devToolsActive) {
'devtools' // At this point, we failed to activate the DevTools package and the
]); // package is not already active.
if (_devToolsActivateProcess.exitCode != 0) {
status.cancel();
_logger.printError('Error running `pub global activate '
'devtools`:\n${_devToolsActivateProcess.stderr}');
return; return;
} }
status.stop(); }
_devToolsProcess = await _processManager.start(<String>[ _devToolsProcess = await _processManager.start(<String>[
_pubExecutable, _pubExecutable,
'global', 'global',
'run', 'run',
'devtools', 'devtools',
if (!openInBrowser) '--no-launch-browser', '--no-launch-browser',
if (vmServiceUri != null) '--vm-uri=$vmServiceUri', if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
]); ]);
final Completer<Uri> completer = Completer<Uri>(); final Completer<Uri> completer = Completer<Uri>();
...@@ -91,30 +102,83 @@ class DevtoolsServerLauncher extends DevtoolsLauncher { ...@@ -91,30 +102,83 @@ class DevtoolsServerLauncher extends DevtoolsLauncher {
} }
completer.complete(Uri.parse(uri)); completer.complete(Uri.parse(uri));
} }
_logger.printStatus(line);
}); });
_devToolsProcess.stderr _devToolsProcess.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen(_logger.printError); .listen(_logger.printError);
_devToolsUri = await completer.future; devToolsUri = await completer.future
.timeout(const Duration(seconds: 10));
} on Exception catch (e, st) { } on Exception catch (e, st) {
status.cancel();
_logger.printError('Failed to launch DevTools: $e', stackTrace: st); _logger.printError('Failed to launch DevTools: $e', stackTrace: st);
} }
} }
@override Future<bool> _checkForActiveDevTools() async {
Future<DevToolsServerAddress> serve({bool openInBrowser = false}) async { // We are offline, and cannot activate DevTools, so check if the DevTools
await launch(null, openInBrowser: openInBrowser); // package is already active.
if (_devToolsUri == null) { final io.ProcessResult _pubGlobalListProcess = await _processManager.run(<String>[
return null; _pubExecutable,
'global',
'list',
]);
if (_pubGlobalListProcess.stdout.toString().contains('devtools ')) {
return true;
} }
return DevToolsServerAddress(_devToolsUri.host, _devToolsUri.port); return false;
}
/// Helper method to activate the DevTools pub package.
///
/// Returns a bool indicating whether or not the package was successfully
/// activated from pub.
Future<bool> _activateDevTools() async {
final DateTime now = DateTime.now();
// Only attempt to activate DevTools twice a day.
final bool shouldActivate =
_persistentToolState.lastDevToolsActivationTime == null ||
now.difference(_persistentToolState.lastDevToolsActivationTime).inHours >= 12;
if (!shouldActivate) {
return false;
}
final Status status = _logger.startProgress(
'Activating Dart DevTools...',
);
try {
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 false;
}
status.stop();
_persistentToolState.lastDevToolsActivationTime = DateTime.now();
return true;
} on Exception catch (e, _) {
status.stop();
_logger.printError('Error running `pub global activate devtools`: $e');
return false;
}
}
@override
Future<DevToolsServerAddress> serve() async {
await launch(null);
return activeDevToolsServer;
} }
@override @override
Future<void> close() async { Future<void> close() async {
devToolsUri = null;
if (_devToolsProcess != null) { if (_devToolsProcess != null) {
_devToolsProcess.kill(); _devToolsProcess.kill();
await _devToolsProcess.exitCode; await _devToolsProcess.exitCode;
......
...@@ -52,6 +52,9 @@ abstract class PersistentToolState { ...@@ -52,6 +52,9 @@ abstract class PersistentToolState {
/// Whether this client was already determined to be or not be a bot. /// Whether this client was already determined to be or not be a bot.
bool isRunningOnBot; bool isRunningOnBot;
/// The last time the the DevTools package was activated from pub.
DateTime lastDevToolsActivationTime;
} }
class _DefaultPersistentToolState implements PersistentToolState { class _DefaultPersistentToolState implements PersistentToolState {
...@@ -85,6 +88,7 @@ class _DefaultPersistentToolState implements PersistentToolState { ...@@ -85,6 +88,7 @@ class _DefaultPersistentToolState implements PersistentToolState {
Channel.stable: 'last-active-stable-version' Channel.stable: 'last-active-stable-version'
}; };
static const String _kBotKey = 'is-bot'; static const String _kBotKey = 'is-bot';
static const String _kLastDevToolsActivationTimeKey = 'last-devtools-activation-time';
static const String _kLicenseHash = 'license-hash'; static const String _kLicenseHash = 'license-hash';
final Config _config; final Config _config;
...@@ -131,4 +135,14 @@ class _DefaultPersistentToolState implements PersistentToolState { ...@@ -131,4 +135,14 @@ class _DefaultPersistentToolState implements PersistentToolState {
@override @override
set isRunningOnBot(bool value) => _config.setValue(_kBotKey, value); set isRunningOnBot(bool value) => _config.setValue(_kBotKey, value);
@override
DateTime get lastDevToolsActivationTime {
final String value = _config.getValue(_kLastDevToolsActivationTimeKey) as String;
return value != null ? DateTime.parse(value) : null;
}
@override
set lastDevToolsActivationTime(DateTime time) =>
_config.setValue(_kLastDevToolsActivationTimeKey, time.toString());
} }
...@@ -774,7 +774,7 @@ abstract class ResidentRunner { ...@@ -774,7 +774,7 @@ abstract class ResidentRunner {
final CommandHelp commandHelp; final CommandHelp commandHelp;
final bool machine; final bool machine;
DevtoolsLauncher _devtoolsLauncher; DevtoolsLauncher _devToolsLauncher;
bool _exited = false; bool _exited = false;
Completer<int> _finished = Completer<int>(); Completer<int> _finished = Completer<int>();
...@@ -926,7 +926,7 @@ abstract class ResidentRunner { ...@@ -926,7 +926,7 @@ abstract class ResidentRunner {
} }
@protected @protected
void writeVmserviceFile() { void writeVmServiceFile() {
if (debuggingOptions.vmserviceOutFile != null) { if (debuggingOptions.vmserviceOutFile != null) {
try { try {
final String address = flutterDevices.first.vmService.wsAddress.toString(); final String address = flutterDevices.first.vmService.wsAddress.toString();
...@@ -941,7 +941,7 @@ abstract class ResidentRunner { ...@@ -941,7 +941,7 @@ abstract class ResidentRunner {
Future<void> exit() async { Future<void> exit() async {
_exited = true; _exited = true;
await shutdownDevtools(); await shutdownDevTools();
await stopEchoingDeviceLog(); await stopEchoingDeviceLog();
await preExit(); await preExit();
await exitApp(); await exitApp();
...@@ -949,7 +949,7 @@ abstract class ResidentRunner { ...@@ -949,7 +949,7 @@ abstract class ResidentRunner {
} }
Future<void> detach() async { Future<void> detach() async {
await shutdownDevtools(); await shutdownDevTools();
await stopEchoingDeviceLog(); await stopEchoingDeviceLog();
await preExit(); await preExit();
await shutdownDartDevelopmentService(); await shutdownDartDevelopmentService();
...@@ -1250,22 +1250,29 @@ abstract class ResidentRunner { ...@@ -1250,22 +1250,29 @@ abstract class ResidentRunner {
} }
} }
Future<bool> launchDevTools({bool openInBrowser = false}) async { DevToolsServerAddress activeDevToolsServer() {
_devToolsLauncher ??= DevtoolsLauncher.instance;
return _devToolsLauncher.activeDevToolsServer;
}
Future<void> serveDevToolsGracefully({
Uri devToolsServerAddress
}) async {
if (!supportsServiceProtocol) { if (!supportsServiceProtocol) {
return false; return;
}
_devToolsLauncher ??= DevtoolsLauncher.instance;
if (devToolsServerAddress != null) {
_devToolsLauncher.devToolsUri = devToolsServerAddress;
} else {
await _devToolsLauncher.serve();
} }
assert(supportsServiceProtocol);
_devtoolsLauncher ??= DevtoolsLauncher.instance;
unawaited(_devtoolsLauncher.launch(
flutterDevices.first.vmService.httpAddress,
openInBrowser: openInBrowser,
));
return true;
} }
Future<void> shutdownDevtools() async { Future<void> shutdownDevTools() async {
await _devtoolsLauncher?.close(); await _devToolsLauncher?.close();
_devtoolsLauncher = null; _devToolsLauncher = null;
} }
Future<void> _serviceProtocolDone(dynamic object) async { Future<void> _serviceProtocolDone(dynamic object) async {
...@@ -1551,8 +1558,6 @@ class TerminalHandler { ...@@ -1551,8 +1558,6 @@ class TerminalHandler {
return residentRunner.debugDumpRenderTree(); return residentRunner.debugDumpRenderTree();
case 'U': case 'U':
return residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); return residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
case 'v':
return residentRunner.launchDevTools(openInBrowser: true);
case 'w': case 'w':
case 'W': case 'W':
return residentRunner.debugDumpApp(); return residentRunner.debugDumpApp();
...@@ -1645,16 +1650,25 @@ String nextPlatform(String currentPlatform, FeatureFlags featureFlags) { ...@@ -1645,16 +1650,25 @@ String nextPlatform(String currentPlatform, FeatureFlags featureFlags) {
/// A launcher for the devtools debugger and analysis tool. /// A launcher for the devtools debugger and analysis tool.
abstract class DevtoolsLauncher { abstract class DevtoolsLauncher {
Uri devToolsUri;
/// Launch a Dart DevTools process, optionally targeting a specific VM Service /// Launch a Dart DevTools process, optionally targeting a specific VM Service
/// URI if [vmServiceUri] is non-null. /// URI if [vmServiceUri] is non-null.
Future<void> launch(Uri vmServiceUri, {bool openInBrowser = false}); Future<void> launch(Uri vmServiceUri);
/// Serve Dart DevTools and return the host and port they are available on. /// Serve Dart DevTools and return the host and port they are available on.
Future<DevToolsServerAddress> serve({bool openInBrowser = false}); Future<DevToolsServerAddress> serve();
Future<void> close(); Future<void> close();
static DevtoolsLauncher get instance => context.get<DevtoolsLauncher>(); static DevtoolsLauncher get instance => context.get<DevtoolsLauncher>();
DevToolsServerAddress get activeDevToolsServer {
if (devToolsUri == null) {
return null;
}
return DevToolsServerAddress(devToolsUri.host, devToolsUri.port);
}
} }
class DevToolsServerAddress { class DevToolsServerAddress {
...@@ -1662,4 +1676,11 @@ class DevToolsServerAddress { ...@@ -1662,4 +1676,11 @@ class DevToolsServerAddress {
final String host; final String host;
final int port; final int port;
Uri get uri {
if (host == null || port == null) {
return null;
}
return Uri(scheme: 'http', host: host, port: port);
}
} }
...@@ -72,7 +72,12 @@ class ColdRunner extends ResidentRunner { ...@@ -72,7 +72,12 @@ class ColdRunner extends ResidentRunner {
// Connect to observatory. // Connect to observatory.
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
try { try {
await connectToServiceProtocol(); await Future.wait(<Future<void>>[
connectToServiceProtocol(),
serveDevToolsGracefully(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
),
]);
} on String catch (message) { } on String catch (message) {
globals.printError(message); globals.printError(message);
appFailedToStart(); appFailedToStart();
...@@ -115,7 +120,7 @@ class ColdRunner extends ResidentRunner { ...@@ -115,7 +120,7 @@ class ColdRunner extends ResidentRunner {
appStartedCompleter?.complete(); appStartedCompleter?.complete();
writeVmserviceFile(); writeVmServiceFile();
if (stayResident && !traceStartup) { if (stayResident && !traceStartup) {
return waitForAppToFinish(); return waitForAppToFinish();
...@@ -132,10 +137,15 @@ class ColdRunner extends ResidentRunner { ...@@ -132,10 +137,15 @@ class ColdRunner extends ResidentRunner {
}) async { }) async {
_didAttach = true; _didAttach = true;
try { try {
await connectToServiceProtocol( await Future.wait(<Future<void>>[
connectToServiceProtocol(
getSkSLMethod: writeSkSL, getSkSLMethod: writeSkSL,
allowExistingDdsInstance: allowExistingDdsInstance, allowExistingDdsInstance: allowExistingDdsInstance,
); ),
serveDevToolsGracefully(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
),
]);
} on Exception catch (error) { } on Exception catch (error) {
globals.printError('Error connecting to the service protocol: $error'); globals.printError('Error connecting to the service protocol: $error');
return 2; return 2;
...@@ -195,6 +205,19 @@ class ColdRunner extends ResidentRunner { ...@@ -195,6 +205,19 @@ class ColdRunner extends ResidentRunner {
'An Observatory debugger and profiler on $dname is available at: ' 'An Observatory debugger and profiler on $dname is available at: '
'${device.vmService.httpAddress}', '${device.vmService.httpAddress}',
); );
final DevToolsServerAddress devToolsServerAddress = activeDevToolsServer();
if (devToolsServerAddress != null) {
final Uri uri = devToolsServerAddress.uri?.replace(
queryParameters: <String, dynamic>{'uri': '${device.vmService.httpAddress}'},
);
if (uri != null) {
globals.printStatus(
'\nFlutter DevTools, a Flutter debugger and profiler, on '
'${device.device.name} is available at: $uri',
);
}
}
} }
} }
} }
......
...@@ -174,13 +174,18 @@ class HotRunner extends ResidentRunner { ...@@ -174,13 +174,18 @@ class HotRunner extends ResidentRunner {
}) async { }) async {
_didAttach = true; _didAttach = true;
try { try {
await connectToServiceProtocol( await Future.wait(<Future<void>>[
connectToServiceProtocol(
reloadSources: _reloadSourcesService, reloadSources: _reloadSourcesService,
restart: _restartService, restart: _restartService,
compileExpression: _compileExpressionService, compileExpression: _compileExpressionService,
getSkSLMethod: writeSkSL, getSkSLMethod: writeSkSL,
allowExistingDdsInstance: allowExistingDdsInstance, allowExistingDdsInstance: allowExistingDdsInstance,
); ),
serveDevToolsGracefully(
devToolsServerAddress: debuggingOptions.devToolsServerAddress,
),
]);
// Catches all exceptions, non-Exception objects are rethrown. // Catches all exceptions, non-Exception objects are rethrown.
} catch (error) { // ignore: avoid_catches_without_on_clauses } catch (error) { // ignore: avoid_catches_without_on_clauses
if (error is! Exception && error is! String) { if (error is! Exception && error is! String) {
...@@ -280,7 +285,7 @@ class HotRunner extends ResidentRunner { ...@@ -280,7 +285,7 @@ class HotRunner extends ResidentRunner {
benchmarkOutput.writeAsStringSync(toPrettyJson(benchmarkData)); benchmarkOutput.writeAsStringSync(toPrettyJson(benchmarkData));
return 0; return 0;
} }
writeVmserviceFile(); writeVmServiceFile();
int result = 0; int result = 0;
if (stayResident) { if (stayResident) {
...@@ -1087,6 +1092,19 @@ class HotRunner extends ResidentRunner { ...@@ -1087,6 +1092,19 @@ class HotRunner extends ResidentRunner {
'An Observatory debugger and profiler on ${device.device.name} is available at: ' 'An Observatory debugger and profiler on ${device.device.name} is available at: '
'${device.vmService.httpAddress}', '${device.vmService.httpAddress}',
); );
final DevToolsServerAddress devToolsServerAddress = activeDevToolsServer();
if (devToolsServerAddress != null) {
final Uri uri = devToolsServerAddress.uri?.replace(
queryParameters: <String, dynamic>{'uri': '${device.vmService.httpAddress}'},
);
if (uri != null) {
globals.printStatus(
'\nFlutter DevTools, a Flutter debugger and profiler, on '
'${device.device.name} is available at: $uri',
);
}
}
} }
globals.printStatus(''); globals.printStatus('');
if (debuggingOptions.buildInfo.nullSafetyMode == NullSafetyMode.sound) { if (debuggingOptions.buildInfo.nullSafetyMode == NullSafetyMode.sound) {
......
...@@ -128,6 +128,9 @@ abstract class FlutterCommand extends Command<void> { ...@@ -128,6 +128,9 @@ abstract class FlutterCommand extends Command<void> {
/// The option name for a custom observatory port. /// The option name for a custom observatory port.
static const String observatoryPortOption = 'observatory-port'; static const String observatoryPortOption = 'observatory-port';
/// The option name for a custom DevTools server address.
static const String kDevToolsServerAddress = 'devtools-server-address';
/// The flag name for whether or not to use ipv6. /// The flag name for whether or not to use ipv6.
static const String ipv6Flag = 'ipv6'; static const String ipv6Flag = 'ipv6';
...@@ -322,6 +325,13 @@ abstract class FlutterCommand extends Command<void> { ...@@ -322,6 +325,13 @@ abstract class FlutterCommand extends Command<void> {
_usesPortOption = true; _usesPortOption = true;
} }
void addDevToolsOptions() {
argParser.addOption(kDevToolsServerAddress,
help: 'When this value is provided, the Flutter tool will not spin up a '
'new DevTools server instance, but instead will use the one provided '
'at this address.');
}
void addDdsOptions({@required bool verboseHelp}) { void addDdsOptions({@required bool verboseHelp}) {
argParser.addOption('dds-port', argParser.addOption('dds-port',
help: 'When this value is provided, the Dart Development Service (DDS) will be ' help: 'When this value is provided, the Dart Development Service (DDS) will be '
...@@ -365,6 +375,16 @@ abstract class FlutterCommand extends Command<void> { ...@@ -365,6 +375,16 @@ abstract class FlutterCommand extends Command<void> {
return 0; return 0;
} }
Uri get devToolsServerAddress {
if (argResults.wasParsed(kDevToolsServerAddress)) {
final Uri uri = Uri.tryParse(stringArg(kDevToolsServerAddress));
if (uri != null && uri.host.isNotEmpty && uri.port != 0) {
return uri;
}
}
return null;
}
/// Gets the vmservice port provided to in the 'observatory-port' or /// Gets the vmservice port provided to in the 'observatory-port' or
/// 'host-vmservice-port option. /// 'host-vmservice-port option.
/// ///
......
...@@ -4,20 +4,41 @@ ...@@ -4,20 +4,41 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/devtools_launcher.dart'; import 'package:flutter_tools/src/devtools_launcher.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/persistent_tool_state.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
void main() { void main() {
BufferLogger logger;
FakePlatform platform;
PersistentToolState persistentToolState;
setUp(() {
logger = BufferLogger.test();
platform = FakePlatform(environment: <String, String>{});
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('devtools_launcher_test');
persistentToolState = PersistentToolState.test(
directory: tempDir,
logger: logger,
);
});
testWithoutContext('DevtoolsLauncher launches DevTools through pub and saves the URI', () async { testWithoutContext('DevtoolsLauncher launches DevTools through pub and saves the URI', () async {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
pubExecutable: 'pub', pubExecutable: 'pub',
logger: BufferLogger.test(), logger: logger,
platform: platform,
persistentToolState: persistentToolState,
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
...@@ -28,6 +49,14 @@ void main() { ...@@ -28,6 +49,14 @@ void main() {
], ],
stdout: 'Activated DevTools 0.9.5', stdout: 'Activated DevTools 0.9.5',
), ),
const FakeCommand(
command: <String>[
'pub',
'global',
'list',
],
stdout: 'devtools 0.9.6',
),
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'pub', 'pub',
...@@ -51,7 +80,9 @@ void main() { ...@@ -51,7 +80,9 @@ void main() {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
pubExecutable: 'pub', pubExecutable: 'pub',
logger: BufferLogger.test(), logger: logger,
platform: platform,
persistentToolState: persistentToolState,
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
...@@ -62,12 +93,21 @@ void main() { ...@@ -62,12 +93,21 @@ void main() {
], ],
stdout: 'Activated DevTools 0.9.5', stdout: 'Activated DevTools 0.9.5',
), ),
const FakeCommand(
command: <String>[
'pub',
'global',
'list',
],
stdout: 'devtools 0.9.6',
),
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'pub', 'pub',
'global', 'global',
'run', 'run',
'devtools', 'devtools',
'--no-launch-browser',
], ],
stdout: 'Serving DevTools at http://127.0.0.1:9100\n', stdout: 'Serving DevTools at http://127.0.0.1:9100\n',
completer: completer, completer: completer,
...@@ -75,16 +115,49 @@ void main() { ...@@ -75,16 +115,49 @@ void main() {
]), ]),
); );
final DevToolsServerAddress address = await launcher.serve(openInBrowser: true); final DevToolsServerAddress address = await launcher.serve();
expect(address.host, '127.0.0.1'); expect(address.host, '127.0.0.1');
expect(address.port, 9100); expect(address.port, 9100);
}); });
testWithoutContext('DevtoolsLauncher does not activate DevTools if it was recently activated', () async {
persistentToolState.lastDevToolsActivationTime = DateTime.now();
final DevtoolsLauncher launcher = DevtoolsServerLauncher(
pubExecutable: 'pub',
logger: logger,
platform: platform,
persistentToolState: persistentToolState,
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'pub',
'global',
'list',
],
stdout: 'devtools 0.9.6',
),
const FakeCommand(
command: <String>[
'pub',
'global',
'run',
'devtools',
'--no-launch-browser',
],
stdout: 'Serving DevTools at http://127.0.0.1:9100\n',
),
]),
);
await launcher.serve();
});
testWithoutContext('DevtoolsLauncher prints error if exception is thrown during activate', () async { testWithoutContext('DevtoolsLauncher prints error if exception is thrown during activate', () async {
final BufferLogger logger = BufferLogger.test();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
pubExecutable: 'pub', pubExecutable: 'pub',
logger: logger, logger: logger,
platform: platform,
persistentToolState: persistentToolState,
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
...@@ -96,6 +169,14 @@ void main() { ...@@ -96,6 +169,14 @@ void main() {
stderr: 'Error - could not activate devtools', stderr: 'Error - could not activate devtools',
exitCode: 1, exitCode: 1,
), ),
const FakeCommand(
command: <String>[
'pub',
'global',
'list',
],
stdout: 'devtools 0.9.6',
),
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'pub', 'pub',
...@@ -118,10 +199,11 @@ void main() { ...@@ -118,10 +199,11 @@ void main() {
}); });
testWithoutContext('DevtoolsLauncher prints error if exception is thrown during launch', () async { testWithoutContext('DevtoolsLauncher prints error if exception is thrown during launch', () async {
final BufferLogger logger = BufferLogger.test();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
pubExecutable: 'pub', pubExecutable: 'pub',
logger: logger, logger: logger,
platform: platform,
persistentToolState: persistentToolState,
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
...@@ -132,6 +214,14 @@ void main() { ...@@ -132,6 +214,14 @@ void main() {
], ],
stdout: 'Activated DevTools 0.9.5', stdout: 'Activated DevTools 0.9.5',
), ),
const FakeCommand(
command: <String>[
'pub',
'global',
'list',
],
stdout: 'devtools 0.9.6',
),
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'pub', 'pub',
......
...@@ -57,4 +57,23 @@ void main() { ...@@ -57,4 +57,23 @@ void main() {
expect(state2.lastActiveVersion(Channel.beta), 'ghi'); expect(state2.lastActiveVersion(Channel.beta), 'ghi');
expect(state2.lastActiveVersion(Channel.stable), 'jkl'); expect(state2.lastActiveVersion(Channel.stable), 'jkl');
}); });
testWithoutContext('lastDevToolsActivationTime can be cached and stored', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final Directory directory = fileSystem.directory('state_dir')..createSync();
final PersistentToolState state1 = PersistentToolState.test(
directory: directory,
logger: BufferLogger.test(),
);
final DateTime time = DateTime.now();
state1.lastDevToolsActivationTime = time;
final PersistentToolState state2 = PersistentToolState.test(
directory: directory,
logger: BufferLogger.test(),
);
expect(state2.lastDevToolsActivationTime, equals(time));
});
} }
...@@ -290,6 +290,48 @@ void main() { ...@@ -290,6 +290,48 @@ void main() {
expect(FlutterCommandResult.warning().exitStatus, ExitStatus.warning); expect(FlutterCommandResult.warning().exitStatus, ExitStatus.warning);
}); });
testUsingContext('devToolsServerAddress returns parsed uri', () async {
final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions();
await createTestCommandRunner(command).run(<String>[
'dummy',
'--${FlutterCommand.kDevToolsServerAddress}',
'http://127.0.0.1:9105',
]);
expect(command.devToolsServerAddress.toString(), equals('http://127.0.0.1:9105'));
});
testUsingContext('devToolsServerAddress returns null for bad input', () async {
final DummyFlutterCommand command = DummyFlutterCommand()..addDevToolsOptions();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'dummy',
'--${FlutterCommand.kDevToolsServerAddress}',
'hello-world',
]);
expect(command.devToolsServerAddress, isNull);
await runner.run(<String>[
'dummy',
'--${FlutterCommand.kDevToolsServerAddress}',
'',
]);
expect(command.devToolsServerAddress, isNull);
await runner.run(<String>[
'dummy',
'--${FlutterCommand.kDevToolsServerAddress}',
'9101',
]);
expect(command.devToolsServerAddress, isNull);
await runner.run(<String>[
'dummy',
'--${FlutterCommand.kDevToolsServerAddress}',
'127.0.0.1:9101',
]);
expect(command.devToolsServerAddress, isNull);
});
group('signals tests', () { group('signals tests', () {
MockIoProcessSignal mockSignal; MockIoProcessSignal mockSignal;
ProcessSignal signalUnderTest; ProcessSignal signalUnderTest;
......
...@@ -285,13 +285,6 @@ void main() { ...@@ -285,13 +285,6 @@ void main() {
verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1); verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
}); });
testWithoutContext('v - launchDevTools', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
await terminalHandler.processTerminalInput('v');
verify(mockResidentRunner.launchDevTools(openInBrowser: true)).called(1);
});
testWithoutContext('w,W - debugDumpApp with service protocol', () async { testWithoutContext('w,W - debugDumpApp with service protocol', () async {
await terminalHandler.processTerminalInput('w'); await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W'); await terminalHandler.processTerminalInput('W');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment