Skip to content

Commit

Permalink
[flutter_tools] Serve DevTools at app start (flutter#73366)
Browse files Browse the repository at this point in the history
* [flutter_tools] Serve DevTools at app start
  • Loading branch information
kenzieschmoll authored Jan 13, 2021
1 parent 7c75c01 commit 1cb0a24
Show file tree
Hide file tree
Showing 17 changed files with 557 additions and 116 deletions.
2 changes: 2 additions & 0 deletions packages/flutter_tools/lib/executable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ Future<void> main(List<String> args) async {
processManager: globals.processManager,
pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable),
logger: globals.logger,
platform: globals.platform,
persistentToolState: globals.persistentToolState,
),
Logger: () {
final LoggerFactory loggerFactory = LoggerFactory(
Expand Down
7 changes: 6 additions & 1 deletion packages/flutter_tools/lib/src/commands/attach.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class AttachCommand extends FlutterCommand {
);
usesTrackWidgetCreation(verboseHelp: verboseHelp);
addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions();
usesDeviceTimeoutOption();
hotRunnerFactory ??= HotRunnerFactory();
}
Expand Down Expand Up @@ -405,7 +406,11 @@ known, it can be explicitly provided to attach via the command-line, e.g.
);
flutterDevice.observatoryUris = observatoryUris;
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
? hotRunnerFactory.build(
Expand Down
3 changes: 1 addition & 2 deletions packages/flutter_tools/lib/src/commands/daemon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -871,8 +871,7 @@ class DevToolsDomain extends Domain {

Future<Map<String, dynamic>> serve([ Map<String, dynamic> args ]) async {
_devtoolsLauncher ??= DevtoolsLauncher.instance;
final bool openInBrowser = args != null && (args['openInBrowser'] == 'true');
final DevToolsServerAddress server = await _devtoolsLauncher.serve(openInBrowser: openInBrowser);
final DevToolsServerAddress server = await _devtoolsLauncher.serve();
return<String, dynamic>{
'host': server?.host,
'port': server?.port,
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter_tools/lib/src/commands/run.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
usesDeviceUserOption();
usesDeviceTimeoutOption();
addDdsOptions(verboseHelp: verboseHelp);
addDevToolsOptions();
addAndroidSpecificBuildOptions(hide: !verboseHelp);
}

Expand Down Expand Up @@ -195,6 +196,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
hostVmServicePort: hostVmservicePort,
disablePortPublication: disablePortPublication,
ddsPort: ddsPort,
devToolsServerAddress: devToolsServerAddress,
verboseSystemLogs: boolArg('verbose-system-logs'),
hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter_tools/lib/src/context_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ Future<T> runInContext<T>(
processManager: globals.processManager,
pubExecutable: globals.artifacts.getArtifactPath(Artifact.pubExecutable),
logger: globals.logger,
platform: globals.platform,
persistentToolState: globals.persistentToolState,
),
Doctor: () => Doctor(logger: globals.logger),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
Expand Down
3 changes: 3 additions & 0 deletions packages/flutter_tools/lib/src/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ class DebuggingOptions {
this.disablePortPublication = false,
this.deviceVmServicePort,
this.ddsPort,
this.devToolsServerAddress,
this.hostname,
this.port,
this.webEnableExposeUrl,
Expand Down Expand Up @@ -895,6 +896,7 @@ class DebuggingOptions {
disablePortPublication = false,
deviceVmServicePort = null,
ddsPort = null,
devToolsServerAddress = null,
vmserviceOutFile = null,
fastStart = false,
webEnableExpressionEvaluation = false,
Expand Down Expand Up @@ -924,6 +926,7 @@ class DebuggingOptions {
final int deviceVmServicePort;
final bool disablePortPublication;
final int ddsPort;
final Uri devToolsServerAddress;
final String port;
final String hostname;
final bool webEnableExposeUrl;
Expand Down
138 changes: 101 additions & 37 deletions packages/flutter_tools/lib/src/devtools_launcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import 'dart:async';

import 'package:browser_launcher/browser_launcher.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:process/process.dart';

import 'base/io.dart' as io;
import 'base/logger.dart';
import 'base/platform.dart';
import 'convert.dart';
import 'persistent_tool_state.dart';
import 'resident_runner.dart';

/// An implementation of the devtools launcher that uses the server package.
Expand All @@ -19,59 +21,68 @@ import 'resident_runner.dart';
/// a devtools dep in google3.
class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({
@required Platform platform,
@required ProcessManager processManager,
@required String pubExecutable,
@required Logger logger,
@required PersistentToolState persistentToolState,
}) : _processManager = processManager,
_pubExecutable = pubExecutable,
_logger = logger;
_logger = logger,
_platform = platform,
_persistentToolState = persistentToolState;

final ProcessManager _processManager;
final String _pubExecutable;
final Logger _logger;
final Platform _platform;
final PersistentToolState _persistentToolState;

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()]);
Future<void> launch(Uri vmServiceUri) async {
// Place this entire method in a try/catch that swallows exceptions because
// we do not want to block Flutter run/attach operations on a DevTools
// failure.
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');
}
} on Exception {
offline = true;
}
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}');
if (offline) {
// TODO(kenz): we should launch an already activated version of DevTools
// here, if available, once DevTools has offline support. DevTools does
// not work without internet currently due to the failed request of a
// couple scripts. See https://github.com/flutter/devtools/issues/2420.
return;
} else {
final bool didActivateDevTools = await _activateDevTools();
final bool devToolsActive = await _checkForActiveDevTools();
if (!didActivateDevTools && !devToolsActive) {
// At this point, we failed to activate the DevTools package and the
// package is not already active.
return;
}
}
status.stop();

_devToolsProcess = await _processManager.start(<String>[
_pubExecutable,
'global',
'run',
'devtools',
if (!openInBrowser) '--no-launch-browser',
'--no-launch-browser',
if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
]);
final Completer<Uri> completer = Completer<Uri>();
Expand All @@ -91,30 +102,83 @@ class DevtoolsServerLauncher extends DevtoolsLauncher {
}
completer.complete(Uri.parse(uri));
}
_logger.printStatus(line);
});
_devToolsProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_logger.printError);
_devToolsUri = await completer.future;
devToolsUri = await completer.future
.timeout(const Duration(seconds: 10));
} 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;
Future<bool> _checkForActiveDevTools() async {
// We are offline, and cannot activate DevTools, so check if the DevTools
// package is already active.
final io.ProcessResult _pubGlobalListProcess = await _processManager.run(<String>[
_pubExecutable,
'global',
'list',
]);

if (_pubGlobalListProcess.stdout.toString().contains('devtools ')) {
return true;
}
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;
}
return DevToolsServerAddress(_devToolsUri.host, _devToolsUri.port);

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
Future<void> close() async {
devToolsUri = null;
if (_devToolsProcess != null) {
_devToolsProcess.kill();
await _devToolsProcess.exitCode;
Expand Down
14 changes: 14 additions & 0 deletions packages/flutter_tools/lib/src/persistent_tool_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ abstract class PersistentToolState {

/// Whether this client was already determined to be or not be a bot.
bool isRunningOnBot;

/// The last time the the DevTools package was activated from pub.
DateTime lastDevToolsActivationTime;
}

class _DefaultPersistentToolState implements PersistentToolState {
Expand Down Expand Up @@ -85,6 +88,7 @@ class _DefaultPersistentToolState implements PersistentToolState {
Channel.stable: 'last-active-stable-version'
};
static const String _kBotKey = 'is-bot';
static const String _kLastDevToolsActivationTimeKey = 'last-devtools-activation-time';
static const String _kLicenseHash = 'license-hash';

final Config _config;
Expand Down Expand Up @@ -131,4 +135,14 @@ class _DefaultPersistentToolState implements PersistentToolState {

@override
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());
}
Loading

0 comments on commit 1cb0a24

Please sign in to comment.