Attention is currently required from: Ryan Macnak, Siva Annamalai.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
Ben Konyi would like Ryan Macnak and Siva Annamalai to review this change.
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Passing --devtools alongside either --enable-vm-service or --observe
will result in a DevTools instance being served by DDS. By default,
DevTools is hosted at localhost:8182, but the bind address and port can
be configured with optional parameters to --devtools, similar to those
available for --observe.
Example output on stdout when DevTools is enabled:
Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8182?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws
hello world!
vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug.
Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec
---
M .dart_tool/package_config.json
M .packages
M DEPS
M pkg/dartdev/lib/dartdev.dart
M pkg/dartdev/lib/src/commands/run.dart
M pkg/dartdev/lib/src/sdk.dart
M pkg/dartdev/test/commands/run_test.dart
M pkg/dds/CHANGELOG.md
M pkg/dds/bin/dds.dart
M pkg/dds/lib/dds.dart
M pkg/dds/lib/src/dds_impl.dart
M pkg/dds/pubspec.yaml
M runtime/bin/main_options.cc
M runtime/bin/main_options.h
M sdk/BUILD.gn
M tools/generate_package_config.dart
M utils/dartdev/BUILD.gn
17 files changed, 388 insertions(+), 32 deletions(-)
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 45febc4..a26c902 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-03-01T09:26:17.118461",
+ "generated": "2021-03-01T14:13:13.790109",
"generator": "tools/generate_package_config.dart",
"packages": [
{
@@ -139,6 +139,12 @@
"languageVersion": "2.10"
},
{
+ "name": "browser_launcher",
+ "rootUri": "../third_party/pkg/browser_launcher",
+ "packageUri": "lib/",
+ "languageVersion": "2.2"
+ },
+ {
"name": "build_integration",
"rootUri": "../pkg/build_integration",
"packageUri": "lib/",
@@ -232,7 +238,7 @@
"name": "dartdoc",
"rootUri": "../third_party/pkg/dartdoc",
"packageUri": "lib/",
- "languageVersion": "2.10"
+ "languageVersion": "2.11"
},
{
"name": "dds",
@@ -247,6 +253,18 @@
"languageVersion": "2.3"
},
{
+ "name": "devtools_server",
+ "rootUri": "../third_party/devtools/devtools_server",
+ "packageUri": "lib/",
+ "languageVersion": "2.6"
+ },
+ {
+ "name": "devtools_shared",
+ "rootUri": "../third_party/devtools/devtools_shared",
+ "packageUri": "lib/",
+ "languageVersion": "2.3"
+ },
+ {
"name": "diagnostic",
"rootUri": "../pkg/diagnostic",
"packageUri": "lib/",
@@ -788,6 +806,12 @@
"languageVersion": "2.0"
},
{
+ "name": "webkit_inspection_protocol",
+ "rootUri": "../third_party/pkg/webkit_inspection_protocol",
+ "packageUri": "lib/",
+ "languageVersion": "2.12"
+ },
+ {
"name": "yaml",
"rootUri": "../third_party/pkg/yaml",
"packageUri": "lib/",
diff --git a/.packages b/.packages
index 3587c4f..f6517f3 100644
--- a/.packages
+++ b/.packages
@@ -21,6 +21,7 @@
benchmark_harness:third_party/pkg/benchmark_harness/lib
boolean_selector:third_party/pkg/boolean_selector/lib
build_integration:pkg/build_integration/lib
+browser_launcher:third_party/pkg/browser_launcher/lib
charcode:third_party/pkg/charcode/lib
cli_util:third_party/pkg/cli_util/lib
collection:third_party/pkg/collection/lib
@@ -37,6 +38,8 @@
dartdoc:third_party/pkg/dartdoc/lib
dds:pkg/dds/lib
dev_compiler:pkg/dev_compiler/lib
+devtools_server:third_party/devtools/devtools_server/lib
+devtools_shared:third_party/devtools/devtools_shared/lib
diagnostic:pkg/diagnostic/lib
expect:pkg/expect/lib
ffi:third_party/pkg/ffi/lib
@@ -118,6 +121,7 @@
wasm:pkg/wasm/lib
watcher:third_party/pkg/watcher/lib
webdriver:third_party/pkg/webdriver/lib
+webkit_inspection_protocol: third_party/pkg/webkit_inspection_protocol.dart/lib
web_components:third_party/pkg/web_components/lib
web_socket_channel:third_party/pkg/web_socket_channel/lib
yaml:third_party/pkg/yaml/lib
diff --git a/DEPS b/DEPS
index 383b118..678a837 100644
--- a/DEPS
+++ b/DEPS
@@ -76,6 +76,7 @@
"boringssl_gen_rev": "7322fc15cc065d8d2957fccce6b62a509dc4d641",
"boringssl_rev" : "1607f54fed72c6589d560254626909a64124f091",
"browser-compat-data_tag": "v1.0.22",
+ "browser_launcher_rev": "3e1677b20fd3267a5d1ae217ece3d58bb08c8938",
"charcode_rev": "bcd8a12c315b7a83390e4865ad847ecd9344cba2",
"chrome_rev" : "19997",
"cli_util_rev" : "fd1b716e8a350a454e01ae56df540293d31ff6c8",
@@ -165,6 +166,7 @@
"vector_math_rev": "0c9f5d68c047813a6dcdeb88ba7a42daddf25025",
"watcher_rev": "3924194385fb215cef483193ed2879a618a3d69c",
"webdriver_rev": "5a8d6805d9cf8a3cbb4fcd64849b538b7491e50e",
+ "webkit_inspection_protocol_rev": "dd6fb5d8b536e19cedb384d0bbf1f5631923f1e8",
"web_components_rev": "8f57dac273412a7172c8ade6f361b407e2e4ed02",
"web_socket_channel_rev": "76931ea1b81ba71e8319330c35285d3e88566315",
"WebCore_rev": "fb11e887f77919450e497344da570d780e078bc8",
@@ -232,7 +234,7 @@
Var("dart_root") + "/third_party/devtools": {
"packages": [{
"package": "dart/third_party/flutter/devtools",
- "version": "revision:6729ec62c3548839018c32fa711756202431ccf7",
+ "version": "revision:76865807f83f728756ee1b122119bfc64db4601b",
}],
"dep_type": "cipd",
},
@@ -305,6 +307,9 @@
Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' +
"@" + Var("browser-compat-data_tag"),
+ Var("dart_root") + "/third_party/pkg/browser_launcher":
+ Var("dart_git") + "browser_launcher.git" + "@" + Var("browser_launcher_rev"),
+
Var("dart_root") + "/third_party/tcmalloc/gperftools":
Var('chromium_git') + '/external/github.com/gperftools/gperftools.git' +
"@" + Var("gperftools_revision"),
@@ -469,6 +474,9 @@
Var("dart_root") + "/third_party/pkg/webdriver":
Var("dart_git") + "external/github.com/google/webdriver.dart.git" +
"@" + Var("webdriver_rev"),
+ Var("dart_root") + "/third_party/pkg/webkit_inspection_protocol":
+ Var("dart_git") + "external/github.com/google/webkit_inspection_protocol.dart" +
+ "@" + Var("webkit_inspection_protocol_rev"),
Var("dart_root") + "/third_party/pkg/web_socket_channel":
Var("dart_git") + "web_socket_channel.git" +
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index a8f9780..ab6c921 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -40,7 +40,8 @@
args = args
.where(
(element) => !(element.contains('--observe') ||
- element.contains('--enable-vm-service')),
+ element.contains('--enable-vm-service') ||
+ element.contains('--devtools')),
)
.toList();
}
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 45bfd1e..71602e6 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -51,12 +51,19 @@
'Debugging options:',
)
..addOption(
+ 'devtools',
+ help: 'Allows for debugging with a Dart DevTools instance when '
+ 'combined with --observe or --enable-vm-service.',
+ valueHelp: '[<port>[/<bind-address>]]',
+ )
+ ..addOption(
'observe',
help: 'The observe flag is a convenience flag used to run a program '
'with a set of common options useful for debugging.',
valueHelp: '[<port>[/<bind-address>]]',
)
..addOption('launch-dds', hide: true, help: 'Launch DDS.')
+ ..addOption('launch-devtools', hide: true, help: 'Launch DevTools.')
..addSeparator(
'Options implied by --observe are currently:',
)
@@ -179,12 +186,21 @@
String launchDdsArg = argResults['launch-dds'];
String ddsHost = '';
String ddsPort = '';
+ bool launchDevTools = false;
+ String devToolsHost = '';
+ String devToolsPort = '';
bool launchDds = false;
if (launchDdsArg != null) {
launchDds = true;
- final ddsUrl = launchDdsArg.split(':');
+ final ddsUrl = launchDdsArg.split('\\:');
ddsHost = ddsUrl[0];
ddsPort = ddsUrl[1];
+ if (argResults.wasParsed('launch-devtools')) {
+ launchDevTools = true;
+ final devToolsUrl = argResults['launch-devtools'].split('\\:');
+ devToolsHost = devToolsUrl[0];
+ devToolsPort = devToolsUrl[1];
+ }
}
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
@@ -198,7 +214,13 @@
if (launchDds) {
debugSession = _DebuggingSession();
if (!await debugSession.start(
- ddsHost, ddsPort, disableServiceAuthCodes)) {
+ ddsHost,
+ ddsPort,
+ disableServiceAuthCodes,
+ launchDevTools,
+ devToolsHost,
+ devToolsPort,
+ )) {
return errorExitCode;
}
}
@@ -242,39 +264,58 @@
class _DebuggingSession {
Future<bool> start(
- String host, String port, bool disableServiceAuthCodes) async {
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ String devToolsHost,
+ String devToolsPort,
+ ) async {
final serviceInfo = await Service.getInfo();
- final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
+ final fullSdk = dirname(sdk.dart).endsWith('bin');
+ final ddsSnapshot = fullSdk
? sdk.ddsSnapshot
: absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
+ final devToolsBinaries = fullSdk
+ ? sdk.devToolsBinaries
+ : absolute(dirname(sdk.dart), 'devtools');
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
return false;
}
final process = await Process.start(
- sdk.dart,
- [
- if (dirname(sdk.dart).endsWith('bin'))
- sdk.ddsSnapshot
- else
- absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
- serviceInfo.serverUri.toString(),
- host,
- port,
- disableServiceAuthCodes.toString(),
- ],
- mode: ProcessStartMode.detachedWithStdio);
+ sdk.dart,
+ [
+ ddsSnapshot,
+ serviceInfo.serverUri.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsHost,
+ devToolsPort,
+ devToolsBinaries,
+ ],
+ mode: ProcessStartMode.detachedWithStdio,
+ );
final completer = Completer<void>();
StreamSubscription sub;
sub = process.stderr.transform(utf8.decoder).listen((event) {
- if (event == 'DDS started') {
+ final result = json.decode(event) as Map<String, dynamic>;
+ final state = result['state'];
+ if (state == 'started') {
+ if (result.containsKey('devToolsUri')) {
+ final devToolsUri = result['devToolsUri'];
+ print(
+ 'The Dart DevTools debugger and profiler is available at: $devToolsUri',
+ );
+ }
sub.cancel();
completer.complete();
- } else if (event.contains('Failed to start DDS')) {
+ } else {
sub.cancel();
- completer.completeError(event.replaceAll(
- 'Failed to start DDS',
+ completer.completeError(
'Could not start Observatory HTTP server',
- ));
+ );
}
});
try {
diff --git a/pkg/dartdev/lib/src/sdk.dart b/pkg/dartdev/lib/src/sdk.dart
index 3e8a12a..426f810 100644
--- a/pkg/dartdev/lib/src/sdk.dart
+++ b/pkg/dartdev/lib/src/sdk.dart
@@ -68,6 +68,13 @@
'dds.dart.snapshot',
);
+ String get devToolsBinaries => path.absolute(
+ sdkPath,
+ 'bin',
+ 'resources',
+ 'devtools',
+ );
+
String get pubSnapshot => path.absolute(
sdkPath,
'bin',
diff --git a/pkg/dartdev/test/commands/run_test.dart b/pkg/dartdev/test/commands/run_test.dart
index b8cc5e8..1174158 100644
--- a/pkg/dartdev/test/commands/run_test.dart
+++ b/pkg/dartdev/test/commands/run_test.dart
@@ -300,4 +300,94 @@
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
});
+
+ group('DevTools', () {
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at: ';
+
+ Future<int> findOpenPort() async {
+ final server = await HttpServer.bind(InternetAddress.anyIPv4, 0);
+ final port = server.port;
+ await server.close();
+ return port;
+ }
+
+ test('spawn simple', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ '--devtools',
+ p.relativeFilePath,
+ ]);
+ expect(
+ result.stdout, contains(devToolsMessagePrefix + 'http://127.0.0.1:'));
+ });
+
+ test('implicit spawn', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ '--enable-vm-service',
+ '--devtools',
+ p.relativeFilePath,
+ ]);
+ expect(
+ result.stdout, contains(devToolsMessagePrefix + 'http://127.0.0.1:'));
+ });
+
+ test('custom port', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ final port = await findOpenPort();
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ '--devtools=$port',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout,
+ contains(devToolsMessagePrefix + 'http://127.0.0.1:$port'));
+ });
+
+ test('custom port with IPv4 bind address', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ final port = await findOpenPort();
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ '--devtools=$port/127.0.0.1',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout,
+ contains(devToolsMessagePrefix + 'http://127.0.0.1:$port'));
+ });
+
+ test('custom port with IPv6 bind address', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ final port = await findOpenPort();
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ '--devtools=$port/::1',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout,
+ contains(devToolsMessagePrefix + 'http://[::1]:$port'));
+ });
+
+ test('- prints warning when --disable-dart-dev is provided', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ final port = await findOpenPort();
+ ProcessResult result = p.runSync([
+ '--disable-dart-dev',
+ '--enable-vm-service',
+ '--devtools=$port',
+ p.relativeFilePath,
+ ]);
+ expect(
+ result.stderr,
+ contains(
+ 'Warning: DevTools cannot be launched when --disable-dart-dev is provided.'),
+ );
+ });
+ });
}
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 62904a1..86d4d29 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 1.8.0-dev
+- Add support for launching DevTools from DDS.
+
# 1.7.5
- Add 30 second keep alive period for SSE connections.
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index 3ac9a09..8ca4a66 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -2,6 +2,7 @@
// for details. 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:convert';
import 'dart:io';
import 'package:dds/dds.dart';
@@ -14,6 +15,10 @@
/// - DDS bind address
/// - DDS port
/// - Disable service authentication codes
+/// - Start DevTools
+/// - DevTools bind address
+/// - DevTools port
+/// - DevTools build directory
Future<void> main(List<String> args) async {
if (args.isEmpty) return;
@@ -35,16 +40,48 @@
port: int.parse(args[2]),
);
final disableServiceAuthCodes = args[3] == 'true';
+
+ final startDevTools = args[4] == 'true';
+ InternetAddress devToolsAddress;
+ int devToolsPort;
+ Uri devToolsBuildDirectory;
+ if (args[5].isNotEmpty) {
+ final devToolsAddresses = await InternetAddress.lookup(args[5]);
+ // Prefer IPv4 addresses.
+ for (int i = 0; i < devToolsAddresses.length; i++) {
+ devToolsAddress = devToolsAddresses[i];
+ if (devToolsAddress.type == InternetAddressType.IPv4) {
+ break;
+ }
+ }
+ devToolsPort = int.parse(args[6]);
+ devToolsBuildDirectory = Uri.parse(args[7]);
+ }
try {
// TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
// See https://github.com/dart-lang/sdk/issues/43192.
- await DartDevelopmentService.startDartDevelopmentService(
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
serviceUri: serviceUri,
enableAuthCodes: !disableServiceAuthCodes,
+ devToolsConfiguration: startDevTools
+ ? DevToolsConfiguration(
+ enable: startDevTools,
+ hostname: devToolsAddress?.address,
+ port: devToolsPort,
+ customBuildDirectoryPath: devToolsBuildDirectory,
+ )
+ : null,
);
- stderr.write('DDS started');
- } catch (e) {
- stderr.writeln('Failed to start DDS:\n$e');
+ stderr.write(json.encode({
+ 'state': 'started',
+ if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(),
+ }));
+ } catch (e, st) {
+ stderr.write(json.encode({
+ 'state': 'error',
+ 'error': '$e',
+ 'stacktrace': '$st',
+ }));
}
}
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index a9024dd..d4fd156 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -42,6 +42,7 @@
Uri serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
+ DevToolsConfiguration devToolsConfiguration,
}) async {
if (remoteVmServiceUri == null) {
throw ArgumentError.notNull('remoteVmServiceUri');
@@ -78,6 +79,7 @@
serviceUri,
enableAuthCodes,
ipv6,
+ devToolsConfiguration,
);
await service.startService();
return service;
@@ -123,6 +125,11 @@
/// Returns `null` if the service is not running.
Uri get wsUri;
+ /// The HTTP [Uri] of the hosted DevTools instance.
+ ///
+ /// Returns `null` if DevTools is not running.
+ Uri get devToolsUri;
+
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
/// requests.
bool get isRunning;
@@ -166,3 +173,17 @@
final int errorCode;
final String message;
}
+
+class DevToolsConfiguration {
+ DevToolsConfiguration({
+ this.enable = false,
+ this.customBuildDirectoryPath,
+ this.hostname,
+ this.port = 0,
+ });
+
+ final bool enable;
+ final Uri customBuildDirectoryPath;
+ final String hostname;
+ final int port;
+}
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 6daaedd..2dc17be 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -8,6 +8,7 @@
import 'dart:math';
import 'dart:typed_data';
+import 'package:devtools_server/devtools_server.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:meta/meta.dart';
import 'package:pedantic/pedantic.dart';
@@ -49,7 +50,12 @@
class DartDevelopmentServiceImpl implements DartDevelopmentService {
DartDevelopmentServiceImpl(
- this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
+ this._remoteVmServiceUri,
+ this._uri,
+ this._authCodesEnabled,
+ this._ipv6,
+ this._devToolsConfiguration,
+ ) {
_clientManager = ClientManager(this);
_expressionEvaluator = ExpressionEvaluator(this);
_isolateManager = IsolateManager(this);
@@ -96,6 +102,11 @@
// Once we have a connection to the VM service, we're ready to spawn the intermediary.
await _startDDSServer();
started = true;
+
+ // Startup DevTools if requested.
+ if (_devToolsConfiguration?.enable ?? false) {
+ await _serveDevTools();
+ }
completer.complete();
} on StateError {
/* Ignore json-rpc state errors */
@@ -147,6 +158,29 @@
_uri = tmpUri;
}
+ Future<void> _serveDevTools() async {
+ final buildDir =
+ _devToolsConfiguration.customBuildDirectoryPath?.toFilePath();
+ _devtoolsServer = await serveDevTools(
+ customDevToolsPath: buildDir,
+ hostname: _devToolsConfiguration.hostname,
+ port: _devToolsConfiguration.port,
+ machineMode: true,
+ enableStdinCommands: false,
+ );
+
+ final queryParams = <String, String>{
+ 'uri': wsUri.toString(),
+ };
+
+ _devToolsUri = Uri(
+ scheme: 'http',
+ host: _devtoolsServer.address.host,
+ port: _devtoolsServer.port,
+ queryParameters: queryParams,
+ );
+ }
+
/// Stop accepting requests after gracefully handling existing requests.
@override
Future<void> shutdown() async {
@@ -157,6 +191,7 @@
_shuttingDown = true;
// Don't accept anymore HTTP requests.
await _server?.close();
+ await _devtoolsServer?.close();
// Close connections to clients.
await clientManager.shutdown();
@@ -318,10 +353,15 @@
Uri get wsUri => _toWebSocket(_uri);
Uri _uri;
+ Uri get devToolsUri => _devToolsUri;
+ Uri _devToolsUri;
+
final bool _ipv6;
bool get isRunning => _uri != null;
+ final DevToolsConfiguration _devToolsConfiguration;
+
Future<void> get done => _done.future;
Completer _done = Completer<void>();
bool _shuttingDown = false;
@@ -343,4 +383,5 @@
json_rpc.Peer vmServiceClient;
WebSocketChannel _vmServiceSocket;
HttpServer _server;
+ HttpServer _devtoolsServer;
}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 469fc0e..3e067bd 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 1.7.5
+version: 1.8.0-dev
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -12,6 +12,8 @@
dependencies:
async: ^2.4.1
+ devtools_server: ^2.0.0
+ devtools_shared: ^2.0.0
json_rpc_2: ^2.2.0
meta: ^1.1.8
pedantic: ^1.7.0
diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc
index e60f6c8..0f2a76d 100644
--- a/runtime/bin/main_options.cc
+++ b/runtime/bin/main_options.cc
@@ -42,6 +42,7 @@
SnapshotKind Options::gen_snapshot_kind_ = kNone;
VerbosityLevel Options::verbosity_ = kAll;
bool Options::enable_vm_service_ = false;
+bool Options::enable_devtools_ = false;
#define OPTION_FIELD(variable) Options::variable##_
@@ -365,6 +366,26 @@
return true;
}
+const char* Options::devtools_server_ip_ = DEFAULT_DEVTOOLS_SERVER_IP;
+int Options::devtools_server_port_ = DEFAULT_DEVTOOLS_SERVER_PORT;
+bool Options::ProcessDevToolsOption(const char* arg,
+ CommandLineOptions* vm_options) {
+ const char* value = OptionProcessor::ProcessOption(arg, "--devtools");
+ if (value == NULL) {
+ return false;
+ }
+ if (!ExtractPortAndAddress(value, &devtools_server_port_,
+ &devtools_server_ip_, DEFAULT_DEVTOOLS_SERVER_PORT,
+ DEFAULT_DEVTOOLS_SERVER_IP)) {
+ Syslog::PrintErr(
+ "unrecognized --devtools option syntax. "
+ "Use --devtools[=<port number>[/<bind address>]]\n");
+ return false;
+ }
+ enable_devtools_ = true;
+ return true;
+}
+
// Explicitly handle VM flags that can be parsed by DartDev's run command.
bool Options::ProcessVMDebuggingOptions(const char* arg,
CommandLineOptions* vm_options) {
@@ -583,7 +604,7 @@
run_command = true;
}
if (!Options::disable_dart_dev() && enable_vm_service_ && run_command) {
- const char* dds_format_str = "--launch-dds=%s:%d";
+ const char* dds_format_str = "--launch-dds=%s\\:%d";
size_t size =
snprintf(nullptr, 0, dds_format_str, vm_service_server_ip(),
vm_service_server_port());
@@ -594,6 +615,18 @@
vm_service_server_port());
dart_options->AddArgument(dds_uri);
+ if (enable_devtools_) {
+ const char* devtools_format_str = "--launch-devtools=%s\\:%d";
+ size = snprintf(nullptr, 0, devtools_format_str, devtools_server_ip(),
+ devtools_server_port());
+ // Make room for '\0'
+ ++size;
+ char* devtools_uri = new char[size];
+ snprintf(devtools_uri, size, devtools_format_str,
+ devtools_server_ip(), devtools_server_port());
+ dart_options->AddArgument(devtools_uri);
+ }
+
// Only add --disable-service-auth-codes if dartdev is being run
// implicitly. Otherwise it will already be forwarded.
if (implicitly_use_dart_dev && Options::vm_service_auth_disabled()) {
@@ -603,6 +636,12 @@
first_option = false;
}
}
+ if (Options::disable_dart_dev() && enable_devtools_) {
+ Syslog::PrintErr(
+ "Warning: DevTools cannot be launched when --disable-dart-dev is "
+ "provided.\n");
+ }
+
// Verify consistency of arguments.
// snapshot_depfile is an alias for depfile. Passing them both is an error.
diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h
index adec23c..4fa1aec 100644
--- a/runtime/bin/main_options.h
+++ b/runtime/bin/main_options.h
@@ -72,6 +72,7 @@
V(ProcessEnvironmentOption) \
V(ProcessEnableVmServiceOption) \
V(ProcessObserveOption) \
+ V(ProcessDevToolsOption) \
V(ProcessVMDebuggingOptions)
// This enum must match the strings in kSnapshotKindNames in main_options.cc.
@@ -92,6 +93,8 @@
static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost";
static constexpr int DEFAULT_VM_SERVICE_SERVER_PORT = 8181;
static constexpr int INVALID_VM_SERVICE_SERVER_PORT = -1;
+static constexpr const char* DEFAULT_DEVTOOLS_SERVER_IP = "localhost";
+static constexpr int DEFAULT_DEVTOOLS_SERVER_PORT = 8182;
class Options {
public:
@@ -141,6 +144,9 @@
static const char* vm_service_server_ip() { return vm_service_server_ip_; }
static int vm_service_server_port() { return vm_service_server_port_; }
+ static const char* devtools_server_ip() { return devtools_server_ip_; }
+ static int devtools_server_port() { return devtools_server_port_; }
+
static Dart_KernelCompilationVerbosityLevel verbosity_level() {
return VerbosityLevelToDartAPI(verbosity_);
}
@@ -207,6 +213,9 @@
const char** out_ip,
int default_port,
const char* default_ip);
+ static bool enable_devtools_;
+ static const char* devtools_server_ip_;
+ static int devtools_server_port_;
#define OPTION_FRIEND(flag, variable) friend class OptionProcessor_##flag;
STRING_OPTIONS_LIST(OPTION_FRIEND)
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index b6091dc..16ec25d 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -254,6 +254,18 @@
},
]
+# This rule copies the pre-built DevTools application to
+# bin/resources/devtools/
+copy_tree_specs += [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":create_common_sdk" ]
+ source = "../third_party/devtools/web"
+ dest = "$root_out_dir/dart-sdk/bin/resources/devtools"
+ ignore_patterns = "{}"
+ },
+]
+
# This loop generates rules to copy libraries to lib/
foreach(library, _full_sdk_libraries) {
copy_tree_specs += [
@@ -836,6 +848,7 @@
":copy_libraries_dart",
":copy_libraries_specification",
":copy_license",
+ ":copy_prebuilt_devtools",
":copy_readme",
":copy_vm_dill_files",
":write_dartdoc_options",
diff --git a/tools/generate_package_config.dart b/tools/generate_package_config.dart
index babc89d..4cf47e0 100644
--- a/tools/generate_package_config.dart
+++ b/tools/generate_package_config.dart
@@ -57,6 +57,8 @@
packageDirectory(
'runtime/observatory_2/tests/service_2/observatory_test_package_2'),
packageDirectory('sdk/lib/_internal/sdk_library_metadata'),
+ packageDirectory('third_party/devtools/devtools_server'),
+ packageDirectory('third_party/devtools/devtools_shared'),
packageDirectory('third_party/pkg/protobuf/protobuf'),
packageDirectory('tools/package_deps'),
];
diff --git a/utils/dartdev/BUILD.gn b/utils/dartdev/BUILD.gn
index c7d8c12..5bcbc61 100644
--- a/utils/dartdev/BUILD.gn
+++ b/utils/dartdev/BUILD.gn
@@ -2,6 +2,7 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
+import("../../build/dart/copy_tree.gni")
import("../application_snapshot.gni")
dartdev_files = exec_script("../../tools/list_dart_files.py",
@@ -15,6 +16,7 @@
public_deps = [
":copy_dartdev_kernel",
":copy_dartdev_snapshot",
+ ":copy_prebuilt_devtools",
]
}
@@ -48,3 +50,15 @@
inputs = dartdev_files
output = "$root_gen_dir/dartdev.dart.snapshot"
}
+
+copy_trees("copy_prebuilt_devtools") {
+ sources = [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":dartdev" ]
+ source = "../../third_party/devtools/web"
+ dest = "$root_out_dir/devtools"
+ ignore_patterns = "{}"
+ },
+ ]
+}
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Ben Konyi.
3 comments:
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #3, Line 56: 'combined with --observe or --enable-vm-service.',
why should --observe or --enable-vm-service be combine with --devtools, can we not imply --observe when --devtools is specified.
File pkg/dartdev/test/commands/run_test.dart:
Patch Set #3, Line 344: '--devtools=$port',
what happens if --devtools=$port and --observe=$port are specified in the same run command ?
File runtime/bin/main_options.h:
Patch Set #3, Line 97: static constexpr int DEFAULT_DEVTOOLS_SERVER_PORT = 8182;
I am a little confused here, why do we want a devtools server and an observatory vm_service server running at the same time ?
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
3 comments:
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #3, Line 56: 'combined with --observe or --enable-vm-service.',
why should --observe or --enable-vm-service be combine with --devtools, can we not imply --observe w […]
That's an option as well, but we don't necessarily want to imply --observe with --devtools since --observe includes debugging options that users won't necessarily always want (e.g., --pause-isolates-on-exit). We have the opposite problem if we decide to imply --enable-vm-service when --devtools is provided.
Basically, this allows for a bit more control over the debugging flags provided to the VM rather than making assumptions about flags one way or the other.
File pkg/dartdev/test/commands/run_test.dart:
Patch Set #3, Line 344: '--devtools=$port',
what happens if --devtools=$port and --observe=$port are specified in the same run command ?
I'm 99% sure the VM service will take $port and DevTools will try and listen on the next available port (e.g., $port + 1). We should test this behavior.
File runtime/bin/main_options.h:
Patch Set #3, Line 97: static constexpr int DEFAULT_DEVTOOLS_SERVER_PORT = 8182;
I am a little confused here, why do we want a devtools server and an observatory vm_service server r […]
Observatory is served by the VM service, and to use DevTools we still need a VM service present as a target as DevTools isn't served by the VM service.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Ben Konyi.
1 comment:
Patchset:
Based on the offline discussion is this CL being reworked on ?
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
1 comment:
Patchset:
Based on the offline discussion is this CL being reworked on ?
It's on hold until at least end of week after I've sat down with the DevTools folks to get a better idea of what package:devtools_server is responsible for and whether or not we can host DevTools without it or migrate some of its functionality into DDS.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
Ben Konyi uploaded patch set #11 to this change.
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Example output on stdout when DevTools is enabled:
Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws
hello world!
vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug.
Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec
---
M .dart_tool/package_config.json
M .packages
M DEPS
M pkg/dartdev/lib/dartdev.dart
M pkg/dartdev/lib/src/commands/run.dart
M pkg/dartdev/lib/src/sdk.dart
M pkg/dartdev/test/commands/run_test.dart
M pkg/dds/CHANGELOG.md
M pkg/dds/bin/dds.dart
M pkg/dds/lib/dds.dart
M pkg/dds/lib/src/client.dart
M pkg/dds/lib/src/constants.dart
M pkg/dds/lib/src/dds_impl.dart
M pkg/dds/pubspec.yaml
M runtime/bin/main.cc
M runtime/bin/main_options.cc
M sdk/BUILD.gn
M sdk/lib/_internal/vm/bin/vmservice_io.dart
M sdk/lib/_internal/vm/bin/vmservice_server.dart
M sdk/lib/vmservice/vmservice.dart
M third_party/devtools/update.sh
M tools/bots/test_matrix.json
M tools/generate_package_config.dart
M utils/dartdev/BUILD.gn
24 files changed, 435 insertions(+), 102 deletions(-)
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
Patch set 11:Commit-Queue +1
3 comments:
Patchset:
Alright, this should be ready for a first pass. PTAL!
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #3, Line 56: 'combined with --observe or --enable-vm-service.',
That's an option as well, but we don't necessarily want to imply --observe with --devtools since --o […]
Ack
File pkg/dartdev/test/commands/run_test.dart:
Patch Set #3, Line 344: '--devtools=$port',
I'm 99% sure the VM service will take $port and DevTools will try and listen on the next available p […]
Ack
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Siva Annamalai.
1 comment:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
Ideally we can drop these deps by copying package:devtools_server/src/server_api.dart into DDS.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
File sdk/lib/_internal/vm/bin/vmservice_io.dart:
/// Responsible for launching a DevTools instance when the service is started
/// via SIGQUIT.
class _DebuggingSession {
If we want to support SIGQUIT, we'll need to launch DDS from the VM service itself. At this point, maybe it makes more sense for the service to spawn DDS rather than the CLI isolate?
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
File sdk/lib/_internal/vm/bin/vmservice_io.dart:
/// Responsible for launching a DevTools instance when the service is started
/// via SIGQUIT.
class _DebuggingSession {
If we want to support SIGQUIT, we'll need to launch DDS from the VM service itself. […]
Gave this some more thought, we'd have to make this configurable by the embedder if we just wanted to start DDS from the VM service (e.g., Flutter wouldn't want to start DDS on device as flutter_tools would spawn its own instance on the host). Maybe something to think about in the future.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Ben Konyi.
Patch set 15:Code-Review +1
7 comments:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
Ideally we can drop these deps by copying package:devtools_server/src/server_api.dart into DDS.
Do we need a TODO here for that change ?
Patchset:
lgtm with some comments. I think we should have Kenzie or Jacob also review the code in devtools_client.dart and devtools_handler.dart
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #15, Line 165: hide: true,
default value for this would be false correct also no help text ?
Should this be visible in verbose mode? i.e hide: !verbose
Patch Set #15, Line 187: // TODO(bkonyi): remove?
Can we have issue numbers instead of ldaps for TODO.
Patch Set #15, Line 270: : absolute(dirname(sdk.dart), 'devtools');
should you hoist 'dirname(sdk.dart)' into a temp ?
Patch Set #15, Line 303: 'A DevTools debugger for DDS is available at: $ddsDebuggingUri',
why not just
'A DevTools debugger and profiler is available at ...."
File runtime/bin/main.cc:
Patch Set #15, Line 561: bool wait_for_dds_to_advertise_service = !Options::disable_dart_dev();
Why are we dropping Options::enable_vm_service() now, I remember this change being made as part of a issue raised earlier.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
6 comments:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
Do we need a TODO here for that change ?
I'd like to do that as part of this CL, just need some input from DevTools folks to confirm.
Patchset:
+Kenzie and Jacob for reviews of DDS changes.
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #15, Line 165: hide: true,
default value for this would be false correct also no help text ? […]
Default value is false since it's a flag. It's just completely hidden at this point since it's not meant to be used by anyone who's not working on DDS.
Patch Set #15, Line 187: // TODO(bkonyi): remove?
Can we have issue numbers instead of ldaps for TODO.
This is more of a note for myself. It'll be removed before this lands.
Patch Set #15, Line 303: 'A DevTools debugger for DDS is available at: $ddsDebuggingUri',
why not just […]
This is only handling the DDS case, so I figured I'd make it more explicit so it's clear which process the URI is from.
File runtime/bin/main.cc:
Patch Set #15, Line 561: bool wait_for_dds_to_advertise_service = !Options::disable_dart_dev();
Why are we dropping Options::enable_vm_service() now, I remember this change being made as part of a […]
We're dropping this now to support the SIGQUIT case (that's actually why this was added in before). The VM service will launch DDS when started via SIGQUIT, so we want the service to wait for DDS to connect before advertising its URI.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Ben Konyi would like Jacob Richman and Kenzie Schmoll to review this change.
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Example output on stdout when DevTools is enabled:
Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws
hello world!
vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug.
Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec
---
M .dart_tool/package_config.json
M .packages
M DEPS
M pkg/dartdev/lib/dartdev.dart
M pkg/dartdev/lib/src/commands/run.dart
M pkg/dartdev/lib/src/sdk.dart
M pkg/dartdev/test/commands/run_test.dart
M pkg/dds/CHANGELOG.md
M pkg/dds/bin/dds.dart
M pkg/dds/lib/dds.dart
M pkg/dds/lib/src/client.dart
M pkg/dds/lib/src/constants.dart
M pkg/dds/lib/src/dds_impl.dart
A pkg/dds/lib/src/devtools/devtools_client.dart
A pkg/dds/lib/src/devtools/devtools_handler.dart
M pkg/dds/pubspec.yaml
M runtime/bin/main.cc
M runtime/bin/main_options.cc
M sdk/BUILD.gn
M sdk/lib/_internal/vm/bin/vmservice_io.dart
M sdk/lib/_internal/vm/bin/vmservice_server.dart
M sdk/lib/vmservice/vmservice.dart
M third_party/devtools/update.sh
M tools/bots/test_matrix.json
M tools/generate_package_config.dart
M utils/dartdev/BUILD.gn
26 files changed, 611 insertions(+), 103 deletions(-)
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 5bd7c51..1ae3a58 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-04-16T13:34:20.183158",
+ "generated": "2021-04-21T11:18:31.921159",
"generator": "tools/generate_package_config.dart",
"packages": [
{
@@ -253,6 +253,18 @@
"languageVersion": "2.3"
},
{
+ "name": "devtools_server",
+ "rootUri": "../third_party/devtools/devtools_server",
+ "packageUri": "lib/",
+ "languageVersion": "2.6"
+ },
+ {
+ "name": "devtools_shared",
+ "rootUri": "../third_party/devtools/devtools_shared",
+ "packageUri": "lib/",
+ "languageVersion": "2.3"
+ },
+ {
"name": "diagnostic",
"rootUri": "../pkg/diagnostic",
"packageUri": "lib/",
diff --git a/.packages b/.packages
index af07227..873baad 100644
--- a/.packages
+++ b/.packages
@@ -22,6 +22,7 @@
boolean_selector:third_party/pkg/boolean_selector/lib
browser_launcher:third_party/pkg/browser_launcher/lib
build_integration:pkg/build_integration/lib
+browser_launcher:third_party/pkg/browser_launcher/lib
charcode:third_party/pkg/charcode/lib
cli_util:third_party/pkg/cli_util/lib
collection:third_party/pkg/collection/lib
@@ -38,6 +39,8 @@
dartdoc:third_party/pkg/dartdoc/lib
dds:pkg/dds/lib
dev_compiler:pkg/dev_compiler/lib
+devtools_server:third_party/devtools/devtools_server/lib
+devtools_shared:third_party/devtools/devtools_shared/lib
diagnostic:pkg/diagnostic/lib
expect:pkg/expect/lib
ffi:third_party/pkg/ffi/lib
diff --git a/DEPS b/DEPS
index 7c4b97d..10276eb 100644
--- a/DEPS
+++ b/DEPS
@@ -80,6 +80,7 @@
"boringssl_gen_rev": "7322fc15cc065d8d2957fccce6b62a509dc4d641",
"boringssl_rev" : "1607f54fed72c6589d560254626909a64124f091",
"browser-compat-data_tag": "v1.0.22",
+ "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"charcode_rev": "bcd8a12c315b7a83390e4865ad847ecd9344cba2",
"chrome_rev" : "19997",
"cli_util_rev" : "fd1b716e8a350a454e01ae56df540293d31ff6c8",
@@ -105,7 +106,6 @@
"dart_style_rev": "f17c23e0eea9a870601c19d904e2a9c1a7c81470",
"chromedriver_tag": "83.0.4103.39",
- "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"dartdoc_rev" : "505f163f7cb48e917503e4a23fbff1227e08b263",
"ffi_rev": "f3346299c55669cc0db48afae85b8110088bf8da",
"fixnum_rev": "16d3890c6dc82ca629659da1934e412292508bba",
@@ -228,7 +228,7 @@
Var("dart_root") + "/third_party/devtools": {
"packages": [{
"package": "dart/third_party/flutter/devtools",
- "version": "revision:6729ec62c3548839018c32fa711756202431ccf7",
+ "version": "git_revision:12ad5341ae0a275042c84a4e7be9a6c98db65612",
}],
"dep_type": "cipd",
},
@@ -301,6 +301,9 @@
Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' +
"@" + Var("browser-compat-data_tag"),
+ Var("dart_root") + "/third_party/pkg/browser_launcher":
+ Var("dart_git") + "browser_launcher.git" + "@" + Var("browser_launcher_rev"),
+
Var("dart_root") + "/third_party/tcmalloc/gperftools":
Var('chromium_git') + '/external/github.com/gperftools/gperftools.git' +
"@" + Var("gperftools_revision"),
@@ -317,9 +320,6 @@
Var("dart_root") + "/third_party/pkg/boolean_selector":
Var("dart_git") + "boolean_selector.git" +
"@" + Var("boolean_selector_rev"),
- Var("dart_root") + "/third_party/pkg/browser_launcher":
- Var("dart_git") + "browser_launcher.git" +
- "@" + Var("browser_launcher_rev"),
Var("dart_root") + "/third_party/pkg/charcode":
Var("dart_git") + "charcode.git" + "@" + Var("charcode_rev"),
Var("dart_root") + "/third_party/pkg/cli_util":
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index b72fbba..5e581c5 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -40,7 +40,8 @@
args = args
.where(
(element) => !(element.contains('--observe') ||
- element.contains('--enable-vm-service')),
+ element.contains('--enable-vm-service') ||
+ element.contains('--devtools')),
)
.toList();
}
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index db32bfe..052d372 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -57,6 +57,7 @@
valueHelp: '[<port>[/<bind-address>]]',
)
..addOption('launch-dds', hide: true, help: 'Launch DDS.')
+ ..addOption('launch-devtools', hide: true, help: 'Launch DevTools.')
..addSeparator(
'Options implied by --observe are currently:',
)
@@ -158,6 +159,10 @@
hide: !verbose,
negatable: false,
help: 'Enables tracing of library and script loading.',
+ )
+ ..addFlag(
+ 'debug-dds',
+ hide: true,
);
addExperimentalFlags(argParser, verbose);
}
@@ -179,13 +184,16 @@
String launchDdsArg = argResults['launch-dds'];
String ddsHost = '';
String ddsPort = '';
+ // TODO(bkonyi): remove?
+ const bool launchDevTools = true;
bool launchDds = false;
if (launchDdsArg != null) {
launchDds = true;
- final ddsUrl = launchDdsArg.split(':');
+ final ddsUrl = launchDdsArg.split('\\:');
ddsHost = ddsUrl[0];
ddsPort = ddsUrl[1];
}
+ final bool debugDds = argResults['debug-dds'];
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
@@ -198,7 +206,12 @@
if (launchDds) {
debugSession = _DebuggingSession();
if (!await debugSession.start(
- ddsHost, ddsPort, disableServiceAuthCodes)) {
+ ddsHost,
+ ddsPort,
+ disableServiceAuthCodes,
+ launchDevTools,
+ debugDds,
+ )) {
return errorExitCode;
}
}
@@ -242,10 +255,19 @@
class _DebuggingSession {
Future<bool> start(
- String host, String port, bool disableServiceAuthCodes) async {
- final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ bool debugDds,
+ ) async {
+ final fullSdk = dirname(sdk.dart).endsWith('bin');
+ final ddsSnapshot = fullSdk
? sdk.ddsSnapshot
: absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
+ final devToolsBinaries = fullSdk
+ ? sdk.devToolsBinaries
+ : absolute(dirname(sdk.dart), 'devtools');
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
return false;
}
@@ -256,30 +278,50 @@
serviceInfo = await Service.getInfo();
}
final process = await Process.start(
- sdk.dart,
- [
- if (dirname(sdk.dart).endsWith('bin'))
- sdk.ddsSnapshot
- else
- absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
- serviceInfo.serverUri.toString(),
- host,
- port,
- disableServiceAuthCodes.toString(),
- ],
- mode: ProcessStartMode.detachedWithStdio);
+ sdk.dart,
+ [
+ if (debugDds) '--enable-vm-service=0',
+ ddsSnapshot,
+ serviceInfo.serverUri.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ ],
+ mode: ProcessStartMode.detachedWithStdio,
+ );
final completer = Completer<void>();
- StreamSubscription sub;
- sub = process.stderr.transform(utf8.decoder).listen((event) {
- if (event == 'DDS started') {
- sub.cancel();
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at:';
+ if (debugDds) {
+ StreamSubscription stdoutSub;
+ stdoutSub = process.stdout.transform(utf8.decoder).listen((event) {
+ if (event.startsWith(devToolsMessagePrefix)) {
+ final ddsDebuggingUri = event.split(' ').last;
+ print(
+ 'A DevTools debugger for DDS is available at: $ddsDebuggingUri',
+ );
+ stdoutSub.cancel();
+ }
+ });
+ }
+ StreamSubscription stderrSub;
+ stderrSub = process.stderr.transform(utf8.decoder).listen((event) {
+ final result = json.decode(event) as Map<String, dynamic>;
+ final state = result['state'];
+ if (state == 'started') {
+ if (result.containsKey('devToolsUri')) {
+ final devToolsUri = result['devToolsUri'];
+ print('$devToolsMessagePrefix $devToolsUri');
+ }
+ stderrSub.cancel();
completer.complete();
- } else if (event.contains('Failed to start DDS')) {
- sub.cancel();
- completer.completeError(event.replaceAll(
- 'Failed to start DDS',
+ } else {
+ stderrSub.cancel();
+ completer.completeError(
'Could not start Observatory HTTP server',
- ));
+ );
}
});
try {
diff --git a/pkg/dartdev/lib/src/sdk.dart b/pkg/dartdev/lib/src/sdk.dart
index 3e8a12a..426f810 100644
--- a/pkg/dartdev/lib/src/sdk.dart
+++ b/pkg/dartdev/lib/src/sdk.dart
@@ -68,6 +68,13 @@
'dds.dart.snapshot',
);
+ String get devToolsBinaries => path.absolute(
+ sdkPath,
+ 'bin',
+ 'resources',
+ 'devtools',
+ );
+
String get pubSnapshot => path.absolute(
sdkPath,
'bin',
diff --git a/pkg/dartdev/test/commands/run_test.dart b/pkg/dartdev/test/commands/run_test.dart
index b8cc5e8..58a4d56 100644
--- a/pkg/dartdev/test/commands/run_test.dart
+++ b/pkg/dartdev/test/commands/run_test.dart
@@ -2,6 +2,8 @@
// for details. 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 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
@@ -300,4 +302,61 @@
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
});
+
+ group('DevTools', () {
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at: http://127.0.0.1:';
+
+ test('spawn simple', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ 'run',
+ '--enable-vm-service',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout, contains(devToolsMessagePrefix));
+ });
+
+ test('implicit spawn', () async {
+ p = project(mainSrc: "void main() { print('Hello World'); }");
+ ProcessResult result = p.runSync([
+ '--enable-vm-service',
+ p.relativeFilePath,
+ ]);
+ expect(result.stdout, contains(devToolsMessagePrefix));
+ });
+
+ test(
+ 'spawn via SIGQUIT',
+ () async {
+ p = project(
+ mainSrc:
+ 'void main() { print("ready"); int i = 0; while(true) { i++; } }',
+ );
+ Process process = await p.start([
+ p.relativeFilePath,
+ ]);
+
+ final readyCompleter = Completer<void>();
+ final completer = Completer<void>();
+
+ StreamSubscription sub;
+ sub = process.stdout.transform(utf8.decoder).listen((event) async {
+ if (event.contains('ready')) {
+ readyCompleter.complete();
+ } else if (event.contains(devToolsMessagePrefix)) {
+ await sub.cancel();
+ completer.complete();
+ }
+ });
+ // Wait for process to start.
+ await readyCompleter.future;
+ process.kill(ProcessSignal.sigquit);
+ await completer.future;
+ process.kill();
+ },
+ // No support for SIGQUIT on Windows.
+ skip: Platform.isWindows,
+ );
+ });
}
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 3db7549..b39156e 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,4 +1,5 @@
-# 1.7.7-dev
+# 1.8.0-dev
+- Add support for launching DevTools from DDS.
- Fixed issue where two clients subscribing to the same stream in close succession
could result in DDS sending multiple `streamListen` requests to the VM service.
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index 9937d26..b240af3 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -4,6 +4,7 @@
// @dart=2.10
+import 'dart:convert';
import 'dart:io';
import 'package:dds/dds.dart';
@@ -16,6 +17,8 @@
/// - DDS bind address
/// - DDS port
/// - Disable service authentication codes
+/// - Start DevTools
+/// - DevTools build directory
Future<void> main(List<String> args) async {
if (args.isEmpty) return;
@@ -37,16 +40,35 @@
port: int.parse(args[2]),
);
final disableServiceAuthCodes = args[3] == 'true';
+
+ final startDevTools = args[4] == 'true';
+ Uri devToolsBuildDirectory;
+ if (args[5].isNotEmpty) {
+ devToolsBuildDirectory = Uri.parse(args[5]);
+ }
try {
// TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
// See https://github.com/dart-lang/sdk/issues/43192.
- await DartDevelopmentService.startDartDevelopmentService(
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
serviceUri: serviceUri,
enableAuthCodes: !disableServiceAuthCodes,
+ devToolsConfiguration: startDevTools
+ ? DevToolsConfiguration(
+ enable: startDevTools,
+ customBuildDirectoryPath: devToolsBuildDirectory,
+ )
+ : null,
);
- stderr.write('DDS started');
- } catch (e) {
- stderr.writeln('Failed to start DDS:\n$e');
+ stderr.write(json.encode({
+ 'state': 'started',
+ if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(),
+ }));
+ } catch (e, st) {
+ stderr.write(json.encode({
+ 'state': 'error',
+ 'error': '$e',
+ 'stacktrace': '$st',
+ }));
}
}
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index f7c7a05..8dba361 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -44,6 +44,7 @@
Uri serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
+ DevToolsConfiguration devToolsConfiguration = const DevToolsConfiguration(),
}) async {
if (remoteVmServiceUri == null) {
throw ArgumentError.notNull('remoteVmServiceUri');
@@ -80,6 +81,7 @@
serviceUri,
enableAuthCodes,
ipv6,
+ devToolsConfiguration,
);
await service.startService();
return service;
@@ -125,6 +127,11 @@
/// Returns `null` if the service is not running.
Uri get wsUri;
+ /// The HTTP [Uri] of the hosted DevTools instance.
+ ///
+ /// Returns `null` if DevTools is not running.
+ Uri get devToolsUri;
+
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
/// requests.
bool get isRunning;
@@ -168,3 +175,13 @@
final int errorCode;
final String message;
}
+
+class DevToolsConfiguration {
+ const DevToolsConfiguration({
+ this.enable = false,
+ this.customBuildDirectoryPath,
+ });
+
+ final bool enable;
+ final Uri customBuildDirectoryPath;
+}
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index f81bd25..9ceb162 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -21,27 +21,25 @@
/// Representation of a single DDS client which manages the connection and
/// DDS request intercepting / forwarding.
class DartDevelopmentServiceClient {
- factory DartDevelopmentServiceClient.fromWebSocket(
+ DartDevelopmentServiceClient.fromWebSocket(
DartDevelopmentService dds,
WebSocketChannel ws,
json_rpc.Peer vmServicePeer,
- ) =>
- DartDevelopmentServiceClient._(
- dds,
- ws,
- vmServicePeer,
- );
+ ) : this._(
+ dds,
+ ws,
+ vmServicePeer,
+ );
- factory DartDevelopmentServiceClient.fromSSEConnection(
+ DartDevelopmentServiceClient.fromSSEConnection(
DartDevelopmentService dds,
SseConnection sse,
json_rpc.Peer vmServicePeer,
- ) =>
- DartDevelopmentServiceClient._(
- dds,
- sse,
- vmServicePeer,
- );
+ ) : this._(
+ dds,
+ sse,
+ vmServicePeer,
+ );
DartDevelopmentServiceClient._(
this.dds,
diff --git a/pkg/dds/lib/src/constants.dart b/pkg/dds/lib/src/constants.dart
index 2466390..b69ae7c 100644
--- a/pkg/dds/lib/src/constants.dart
+++ b/pkg/dds/lib/src/constants.dart
@@ -16,6 +16,10 @@
};
}
+// Give connections time to reestablish before considering them closed.
+// Required to reestablish connections killed by UberProxy.
+const sseKeepAlive = Duration(seconds: 30);
+
abstract class PauseTypeMasks {
static const pauseOnStartMask = 1 << 0;
static const pauseOnReloadMask = 1 << 1;
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index db355fb..5d24ec1 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -24,6 +24,8 @@
import 'binary_compatible_peer.dart';
import 'client.dart';
import 'client_manager.dart';
+import 'constants.dart';
+import 'devtools/devtools_handler.dart';
import 'expression_evaluator.dart';
import 'isolate_manager.dart';
import 'stream_manager.dart';
@@ -51,12 +53,17 @@
class DartDevelopmentServiceImpl implements DartDevelopmentService {
DartDevelopmentServiceImpl(
- this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
+ this._remoteVmServiceUri,
+ this._uri,
+ this._authCodesEnabled,
+ this._ipv6,
+ this._devToolsConfiguration,
+ ) {
_clientManager = ClientManager(this);
_expressionEvaluator = ExpressionEvaluator(this);
_isolateManager = IsolateManager(this);
_streamManager = StreamManager(this);
- _authCode = _authCodesEnabled ? _makeAuthToken() : '';
+ authCode = _authCodesEnabled ? _makeAuthToken() : '';
}
Future<void> startService() async {
@@ -117,6 +124,9 @@
// Start the DDS server.
_server = await io.serve(
const Pipeline()
+ .addMiddleware(logRequests(logger: (String message, bool isError) {
+ print('Log: $message');
+ }))
.addMiddleware(_authCodeMiddleware)
.addHandler(_handlers().handler),
host,
@@ -126,7 +136,7 @@
scheme: 'http',
host: host,
port: _server.port,
- path: '$_authCode/',
+ path: '$authCode/',
);
// Notify the VM service that this client is DDS and that it should close
@@ -157,7 +167,7 @@
return;
}
_shuttingDown = true;
- // Don't accept anymore HTTP requests.
+ // Don't accept any more HTTP requests.
await _server?.close();
// Close connections to clients.
@@ -197,7 +207,7 @@
return forbidden;
}
final authToken = pathSegments[0];
- if (authToken != _authCode) {
+ if (authToken != authCode) {
return forbidden;
}
// Creates a new request with the authentication code stripped from
@@ -233,18 +243,12 @@
});
Handler _sseHandler() {
- // Give connections time to reestablish before considering them closed.
- // Required to reestablish connections killed by UberProxy.
- const keepAlive = Duration(seconds: 30);
- final handler = authCodesEnabled
- ? SseHandler(
- Uri.parse('/$_authCode/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- )
- : SseHandler(
- Uri.parse('/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- );
+ final handler = SseHandler(
+ authCodesEnabled
+ ? Uri.parse('/$authCode/$_kSseHandlerPath')
+ : Uri.parse('/$_kSseHandlerPath'),
+ keepAlive: sseKeepAlive,
+ );
handler.connections.rest.listen((sseConnection) {
final client = DartDevelopmentServiceClient.fromSSEConnection(
@@ -259,10 +263,18 @@
}
Handler _httpHandler() {
- // DDS doesn't support any HTTP requests itself, so we just forward all of
- // them to the VM service.
- final cascade = Cascade().add(proxyHandler(remoteVmServiceUri));
- return cascade.handler;
+ if (_devToolsConfiguration != null && _devToolsConfiguration.enable) {
+ // Install the DevTools handlers and forward any unhandled HTTP requests to
+ // the VM service.
+ final buildDir =
+ _devToolsConfiguration.customBuildDirectoryPath?.toFilePath();
+ return devtoolsHandler(
+ dds: this,
+ buildDir: buildDir,
+ notFoundHandler: proxyHandler(remoteVmServiceUri),
+ );
+ }
+ return proxyHandler(remoteVmServiceUri);
}
List<String> _cleanupPathSegments(Uri uri) {
@@ -296,13 +308,39 @@
return uri.replace(scheme: 'sse', pathSegments: pathSegments);
}
+ Uri _toDevTools(Uri uri) {
+ // The DevTools URI is a bit strange as the query parameters appear after
+ // the fragment. There's no nice way to encode the query parameters
+ // properly, so we create another Uri just to grab the formatted query.
+ // The result will need to have '/?' prepended when being used as the
+ // fragment to get the correct format.
+ final query = Uri(
+ queryParameters: {
+ 'uri': wsUri.toString(),
+ },
+ ).query;
+ return Uri(
+ scheme: 'http',
+ host: uri.host,
+ port: uri.port,
+ pathSegments: [
+ ...uri.pathSegments.where(
+ (e) => e.isNotEmpty,
+ ),
+ 'devtools',
+ '',
+ ],
+ fragment: '/?$query',
+ );
+ }
+
String getNamespace(DartDevelopmentServiceClient client) =>
clientManager.clients.keyOf(client);
@override
bool get authCodesEnabled => _authCodesEnabled;
final bool _authCodesEnabled;
- String _authCode;
+ String authCode;
@override
Uri get remoteVmServiceUri => _remoteVmServiceUri;
@@ -313,17 +351,21 @@
@override
Uri get uri => _uri;
+ Uri _uri;
@override
Uri get sseUri => _toSse(_uri);
Uri get wsUri => _toWebSocket(_uri);
- Uri _uri;
+
+ Uri get devToolsUri => _toDevTools(_uri);
final bool _ipv6;
bool get isRunning => _uri != null;
+ final DevToolsConfiguration _devToolsConfiguration;
+
Future<void> get done => _done.future;
Completer _done = Completer<void>();
bool _shuttingDown = false;
diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart
new file mode 100644
index 0000000..72bff09
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_client.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:devtools_server/src/server_api.dart';
+import 'package:json_rpc_2/src/server.dart' as json_rpc;
+import 'package:sse/src/server/sse_handler.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+class LoggingMiddlewareSink<S> implements StreamSink<S> {
+ LoggingMiddlewareSink(this.sink);
+
+ @override
+ void add(S event) {
+ print('DevTools SSE response: $event');
+ sink.add(event);
+ }
+
+ @override
+ void addError(Object error, [StackTrace stackTrace]) {
+ print('DevTools SSE error response: $error');
+ sink.addError(error);
+ }
+
+ @override
+ Future addStream(Stream<S> stream) {
+ return sink.addStream(stream);
+ }
+
+ @override
+ Future close() => sink.close();
+
+ @override
+ Future get done => sink.done;
+
+ final StreamSink sink;
+}
+
+/// Represents a DevTools client connection to the DevTools server API.
+class DevToolsClient {
+ DevToolsClient.fromSSEConnection(
+ SseConnection sse,
+ ) : _server = json_rpc.Server(
+ StreamChannel(
+ sse.stream.map<String>((String e) {
+ print('DevTools SSE event: $e');
+ return e;
+ }),
+ LoggingMiddlewareSink(sse.sink),
+ ),
+ strictProtocolChecks: false,
+ ) {
+ _registerJsonRpcMethods();
+ _server.listen();
+ }
+
+ void _registerJsonRpcMethods() {
+ _server.registerMethod('connected', (parameters) {
+ // TODO: implement
+ });
+
+ _server.registerMethod('currentPage', (parameters) {
+ // TODO: implement
+ });
+
+ _server.registerMethod('disconnected', (parameters) {
+ // TODO: implement
+ });
+
+ _server.registerMethod('getPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = ServerApi.devToolsPreferences.properties[key];
+ return value;
+ });
+
+ _server.registerMethod('setPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = parameters['value'].value;
+ ServerApi.devToolsPreferences.properties[key] = value;
+ });
+ }
+
+ final json_rpc.Server _server;
+}
diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/devtools_handler.dart
new file mode 100644
index 0000000..ec9230b
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_handler.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:dds/src/constants.dart';
+import 'package:devtools_server/src/server_api.dart';
+import 'package:meta/meta.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:sse/server/sse_handler.dart';
+
+import '../dds_impl.dart';
+import 'devtools_client.dart';
+
+/// Returns a [Handler] which handles serving DevTools and the DevTools server
+/// API under $DDS_URI/devtools/.
+///
+/// [buildDir] is the path to the pre-compiled DevTools instance to be served.
+///
+/// [notFoundHandler] is a [Handler] to which requests that could not be handled
+/// by the DevTools handler are forwarded (e.g., a proxy to the VM service).
+FutureOr<Handler> devtoolsHandler({
+ @required DartDevelopmentServiceImpl dds,
+ @required String buildDir,
+ @required Handler notFoundHandler,
+}) {
+ // Serves the web assets for DevTools.
+ final devtoolsAssetHandler = createStaticHandler(
+ buildDir,
+ defaultDocument: 'index.html',
+ );
+
+ // Support DevTools client-server interface via SSE.
+ // Note: the handler path needs to match the full *original* path, not the
+ // current request URL (we remove '/devtools' in the initial router but we
+ // need to include it here).
+ const devToolsSseHandlerPath = '/devtools/api/sse';
+ final devToolsApiHandler = SseHandler(
+ dds.authCodesEnabled
+ ? Uri.parse('/${dds.authCode}$devToolsSseHandlerPath')
+ : Uri.parse(devToolsSseHandlerPath),
+ keepAlive: sseKeepAlive,
+ );
+
+ devToolsApiHandler.connections.rest.listen(
+ (sseConnection) => DevToolsClient.fromSSEConnection(
+ sseConnection,
+ ),
+ );
+
+ final devtoolsHandler = (Request request) {
+ // If the request isn't of the form api/<method> assume it's a request for
+ // DevTools assets.
+ if (request.url.pathSegments.length < 2 ||
+ request.url.pathSegments.first != 'api') {
+ return devtoolsAssetHandler(request);
+ }
+ final method = request.url.pathSegments[1];
+ if (method == 'ping') {
+ // Note: we have an 'OK' body response, otherwise the response has an
+ // incorrect status code (204 instead of 200).
+ return Response.ok('OK');
+ }
+ if (method == 'sse') {
+ return devToolsApiHandler.handler(request);
+ }
+ if (!ServerApi.canHandle(request)) {
+ return Response.notFound('$method is not a valid API');
+ }
+ return ServerApi.handle(request);
+ };
+
+ return (request) {
+ final pathSegments = request.url.pathSegments;
+ if (pathSegments.isEmpty || pathSegments.first != 'devtools') {
+ return notFoundHandler(request);
+ }
+ // Forward all requests to /devtools/* to the DevTools handler.
+ request = request.change(path: 'devtools');
+ return devtoolsHandler(request);
+ };
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 221e3df..38d647b 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 1.7.6
+version: 1.8.0-dev
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -12,11 +12,13 @@
dependencies:
async: ^2.4.1
+ devtools_server: ^2.0.0
json_rpc_2: ^2.2.0
meta: ^1.1.8
pedantic: ^1.7.0
shelf: ^1.0.0
shelf_proxy: ^1.0.0
+ shelf_static: ^1.0.0-dev
shelf_web_socket: ^1.0.0
sse: ^3.7.0
stream_channel: ^2.0.0
@@ -24,6 +26,5 @@
web_socket_channel: ^2.0.0
dev_dependencies:
- shelf_static: ^1.0.0
test: ^1.0.0
webdriver: ^3.0.0
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 67d9c2d..95c7166 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -553,13 +553,12 @@
vm_service_server_port = 0;
}
- // We do not want to wait for DDS to advertise availability of VM service in the
- // following scenarios:
- // - When the VM service is disabled (can be started at a later time via SIGQUIT).
- // - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is enabled.
- bool wait_for_dds_to_advertise_service =
- !Options::disable_dart_dev() && Options::enable_vm_service();
-
+ // We do not want to wait for DDS to advertise availability of VM service in
+ // the following scenarios:
+ // - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is
+ // enabled.
+ // TODO(bkonyi): do we want to tie DevTools / DDS to the CLI in the long run?
+ bool wait_for_dds_to_advertise_service = !Options::disable_dart_dev();
// Load embedder specific bits and return.
if (!VmService::Setup(
Options::disable_dart_dev() ? Options::vm_service_server_ip()
diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc
index e60f6c8..77b794c 100644
--- a/runtime/bin/main_options.cc
+++ b/runtime/bin/main_options.cc
@@ -583,7 +583,7 @@
run_command = true;
}
if (!Options::disable_dart_dev() && enable_vm_service_ && run_command) {
- const char* dds_format_str = "--launch-dds=%s:%d";
+ const char* dds_format_str = "--launch-dds=%s\\:%d";
size_t size =
snprintf(nullptr, 0, dds_format_str, vm_service_server_ip(),
vm_service_server_port());
@@ -603,6 +603,7 @@
first_option = false;
}
}
+
// Verify consistency of arguments.
// snapshot_depfile is an alias for depfile. Passing them both is an error.
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index 15a1abf..1390092 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -252,6 +252,18 @@
},
]
+# This rule copies the pre-built DevTools application to
+# bin/resources/devtools/
+copy_tree_specs += [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":create_common_sdk" ]
+ source = "../third_party/devtools/web"
+ dest = "$root_out_dir/dart-sdk/bin/resources/devtools"
+ ignore_patterns = "{}"
+ },
+]
+
# This loop generates rules to copy libraries to lib/
foreach(library, _full_sdk_libraries) {
copy_tree_specs += [
@@ -811,6 +823,7 @@
":copy_libraries_dart",
":copy_libraries_specification",
":copy_license",
+ ":copy_prebuilt_devtools",
":copy_readme",
":copy_vm_dill_files",
":write_dartdoc_options",
diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart
index d1f8a5d..9610f3c 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -43,6 +43,7 @@
// HTTP server.
Server? server;
Future<Server>? serverFuture;
+_DebuggingSession? ddsInstance;
Server _lazyServerBoot() {
var localServer = server;
@@ -58,6 +59,88 @@
return localServer;
}
+/// Responsible for launching a DevTools instance when the service is started
+/// via SIGQUIT.
+class _DebuggingSession {
+ Future<bool> start(
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ ) async {
+ final dartPath = Uri.parse(Platform.resolvedExecutable);
+ final dartDir = [
+ '', // Include leading '/'
+ ...dartPath.pathSegments.sublist(
+ 0,
+ dartPath.pathSegments.length - 1,
+ ),
+ ].join('/');
+
+ final fullSdk = dartDir.endsWith('bin');
+
+ final ddsSnapshot = [
+ dartDir,
+ fullSdk ? 'snapshots' : 'gen',
+ 'dds.dart.snapshot',
+ ].join('/');
+
+ final devToolsBinaries = [
+ dartDir,
+ if (fullSdk) 'bin/resources',
+ 'devtools',
+ ].join('/');
+
+ _process = await Process.start(
+ dartPath.toString(),
+ [
+ ddsSnapshot,
+ server!.serverAddress!.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ ],
+ mode: ProcessStartMode.detachedWithStdio,
+ );
+ final completer = Completer<void>();
+ late StreamSubscription stderrSub;
+ stderrSub = _process!.stderr.transform(utf8.decoder).listen((event) {
+ final result = json.decode(event) as Map<String, dynamic>;
+ final state = result['state'];
+ if (state == 'started') {
+ if (result.containsKey('devToolsUri')) {
+ // NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message
+ // is changed to ensure consistency.
+ const devToolsMessagePrefix =
+ 'The Dart DevTools debugger and profiler is available at:';
+ final devToolsUri = result['devToolsUri'];
+ print('$devToolsMessagePrefix $devToolsUri');
+ }
+ stderrSub.cancel();
+ completer.complete();
+ } else {
+ stderrSub.cancel();
+ completer.completeError(
+ 'Could not start Observatory HTTP server',
+ );
+ }
+ });
+ try {
+ await completer.future;
+ return true;
+ } catch (e) {
+ stderr.write(e);
+ return false;
+ }
+ }
+
+ void shutdown() => _process!.kill();
+
+ Process? _process;
+}
+
Future cleanupCallback() async {
// Cancel the sigquit subscription.
if (_signalSubscription != null) {
@@ -221,10 +304,6 @@
_server.acceptNewWebSocketConnections = enable;
}
-void _clearFuture(_) {
- serverFuture = null;
-}
-
_onSignal(ProcessSignal signal) {
if (serverFuture != null) {
// Still waiting.
@@ -233,9 +312,21 @@
final _server = _lazyServerBoot();
// Toggle HTTP server.
if (_server.running) {
- _server.shutdown(true).then(_clearFuture);
+ _server.shutdown(true).then((_) async {
+ ddsInstance?.shutdown();
+ await VMService().clearState();
+ serverFuture = null;
+ });
} else {
- _server.startup().then(_clearFuture);
+ _server.startup().then((_) {
+ ddsInstance = _DebuggingSession()
+ ..start(
+ _server._ip,
+ _server._port.toString(),
+ false,
+ true,
+ );
+ });
}
}
diff --git a/sdk/lib/_internal/vm/bin/vmservice_server.dart b/sdk/lib/_internal/vm/bin/vmservice_server.dart
index 69aa7f8..f5742ed 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_server.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_server.dart
@@ -26,9 +26,9 @@
socket.done.then((_) => close());
}
- disconnect() {
+ Future<void> disconnect() async {
if (socket != null) {
- socket.close();
+ await socket.close();
}
}
@@ -102,8 +102,8 @@
HttpRequestClient(this.request, VMService service)
: super(service, sendEvents: false);
- disconnect() {
- request.response.close();
+ Future<void> disconnect() async {
+ await request.response.close();
close();
}
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index a3f7e8b..d492dcf 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -411,6 +411,16 @@
replyPort.send(bytes);
}
+ Future<void> clearState() async {
+ // Create a copy of the set as a list because client.disconnect() will
+ // alter the connected clients set.
+ final clientsList = clients.toList();
+ for (final client in clientsList) {
+ await client.disconnect();
+ }
+ devfs.cleanup();
+ }
+
Future _exit() async {
isExiting = true;
@@ -423,14 +433,7 @@
// Close receive ports.
isolateControlPort.close();
scriptLoadPort.close();
-
- // Create a copy of the set as a list because client.disconnect() will
- // alter the connected clients set.
- final clientsList = clients.toList();
- for (final client in clientsList) {
- client.disconnect();
- }
- devfs.cleanup();
+ await clearState();
final cleanup = VMServiceEmbedderHooks.cleanup;
if (cleanup != null) {
await cleanup();
diff --git a/third_party/devtools/update.sh b/third_party/devtools/update.sh
index a69f6ee..55591be 100755
--- a/third_party/devtools/update.sh
+++ b/third_party/devtools/update.sh
@@ -37,5 +37,5 @@
-name dart/third_party/flutter/devtools \
-in cipd_package \
-install-mode copy \
- -tag revision:$1
+ -tag git_revision:$1
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 7f33648..c0bc00f 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -322,6 +322,7 @@
"xcodebuild/ReleaseSIMARM64C/",
"xcodebuild/ReleaseX64/",
"xcodebuild/ReleaseX64C/",
+ "pkg/",
"samples/",
"samples_2/",
"samples-dev/",
@@ -329,6 +330,7 @@
"third_party/android_tools/sdk/platform-tools/adb",
"third_party/android_tools/ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip",
"third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip",
+ "third_party/devtools/",
"third_party/webdriver/",
"third_party/pkg/",
"third_party/pkg_tested/",
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Ben Konyi, Kenzie Schmoll, Siva Annamalai.
6 comments:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
I'd like to do that as part of this CL, just need some input from DevTools folks to confirm.
I would prefer if we create a thin devtools_server_api package instead of forking the code.
Patchset:
Looks good overall. Some minor comments probably mainly due to my lack of familiarity with the DDS codebase.
File pkg/dds/bin/dds.dart:
Patch Set #15, Line 63: stderr.write(json.encode({
why is json being written to STDERR? Is the protocol documented somewhere and why stderr instead of stdout?
File pkg/dds/lib/src/dds_impl.dart:
Patch Set #15, Line 343: String authCode;
nit: make authCode final.
File pkg/dds/lib/src/devtools/devtools_client.dart:
Patch Set #15, Line 50: print('DevTools SSE event: $e');
Did you mean to enable the logging middleware sink by default and include this log message?
File sdk/lib/_internal/vm/bin/vmservice_io.dart:
Patch Set #15, Line 117: 'The Dart DevTools debugger and profiler is available at:';
Lets align this message with our docs.
Currently we call Dart DevTools a suite of debugging and performance tools.
https://dart.dev/tools/dart-devtools#:~:text=Dart%20DevTools%20is%20a%20suite,tool%2C%20and%20the%20devtools%20package.&text=As%20the%20table%20shows%2C%20the,available%20to%20all%20app%20types.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Ben Konyi, Kenzie Schmoll, Siva Annamalai.
3 comments:
File pkg/dds/lib/src/devtools/devtools_client.dart:
Patch Set #15, Line 14: class LoggingMiddlewareSink<S> implements StreamSink<S> {
Consider enabling and disabling debug logging based on a top level field in this file (e.g. debugTraceSSEEvents). I imagine you didn't mean to leave the logging on as part of this CL.
Patch Set #15, Line 63: // TODO: implement
do connected and disconnected even make sense for the DDS embedded case? Will this devtools server continue running after DDS stops? Consider just omitting these apis or intentionally making them no-ops with a comment explaining why.
Patch Set #15, Line 67: // TODO: implement
this one should be easy to implement reusing the implementation in https://github.com/flutter/devtools/blob/3fa58bd957ddc2aaf2b80d62cb71b9d0cea08e0e/packages/devtools_server/lib/src/client_manager.dart
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Patch set 16:Commit-Queue +1
9 comments:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
I would prefer if we create a thin devtools_server_api package instead of forking the code.
As discussed offline, we'll just fork what we need for now to remove these dependencies until we have a dedicated server_api package.
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #15, Line 270: : absolute(dirname(sdk.dart), 'devtools');
should you hoist 'dirname(sdk. […]
Done
File pkg/dds/bin/dds.dart:
Patch Set #15, Line 63: stderr.write(json.encode({
why is json being written to STDERR? Is the protocol documented somewhere and why stderr instead of […]
This was more of an internal protocol used by the CLI, so there's currently no documentation. We might start launching a DDS snapshot bundled with the SDK from flutter_tools, at which point we'd want this documented.
We use stderr since the VM service URI is output on stdout. If --debug-dds is provided to the VM, it will launch DDS with --observe and write to stdout.
File pkg/dds/lib/src/dds_impl.dart:
Patch Set #15, Line 343: String authCode;
nit: make authCode final.
Done
File pkg/dds/lib/src/devtools/devtools_client.dart:
Patch Set #15, Line 14: class LoggingMiddlewareSink<S> implements StreamSink<S> {
Consider enabling and disabling debug logging based on a top level field in this file (e.g. […]
I'll probably wire this up to --debug-dds. The logs aren't visible to users anyway since they're in the DDS process, but it doesn't make sense to scream into the void if there's nobody listening... 😊
Patch Set #15, Line 50: print('DevTools SSE event: $e');
Did you mean to enable the logging middleware sink by default and include this log message?
Added some logic so this needs to be explicitly enabled.
Patch Set #15, Line 63: // TODO: implement
do connected and disconnected even make sense for the DDS embedded case? Will this devtools server c […]
Not at this point, but I can envision DDS having a long-running mode in the future. I'll no-op these for now.
Patch Set #15, Line 67: // TODO: implement
this one should be easy to implement reusing the implementation in https://github. […]
Took a look at the code and I'm thinking this should just be another no-op for now. We don't have a use for this functionality in the SDK at this point.
File sdk/lib/_internal/vm/bin/vmservice_io.dart:
Patch Set #15, Line 117: 'The Dart DevTools debugger and profiler is available at:';
Lets align this message with our docs. […]
Pretty sure this is copied right from flutter_tools output which is:
The Flutter DevTools debugger and profiler on macOS is available at: http://127.0.0.1:9101?uri=http%3A%2F%2F127.0.0.1%3A51840%2Fx6KHW4Z8J8I%3D%2F
I think we should stay consistent. If that means we change the message in flutter_tools as well, that's fine with me 😊.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
3 comments:
File .packages:
devtools_server:third_party/devtools/devtools_server/lib
devtools_shared:third_party/devtools/devtools_shared/lib
As discussed offline, we'll just fork what we need for now to remove these dependencies until we hav […]
Done
File pkg/dartdev/lib/src/commands/run.dart:
Patch Set #15, Line 187: // TODO(bkonyi): remove?
This is more of a note for myself. It'll be removed before this lands.
Done
File pkg/dds/lib/src/devtools/devtools_client.dart:
Patch Set #15, Line 14: class LoggingMiddlewareSink<S> implements StreamSink<S> {
I'll probably wire this up to --debug-dds. […]
Done
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Ben Konyi uploaded patch set #18 to this change.
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Example output on stdout when DevTools is enabled:
Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws
hello world!
vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug.
TEST=pkg/dartdev/test/commands/run_test.dart
Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec
---
M .dart_tool/package_config.json
M .packages
M DEPS
M pkg/dartdev/lib/dartdev.dart
M pkg/dartdev/lib/src/commands/run.dart
M pkg/dartdev/lib/src/sdk.dart
M pkg/dartdev/test/commands/run_test.dart
M pkg/dds/CHANGELOG.md
M pkg/dds/bin/dds.dart
M pkg/dds/lib/dds.dart
M pkg/dds/lib/src/client.dart
M pkg/dds/lib/src/constants.dart
M pkg/dds/lib/src/dds_impl.dart
A pkg/dds/lib/src/devtools/devtools_client.dart
A pkg/dds/lib/src/devtools/devtools_handler.dart
A pkg/dds/lib/src/devtools/file_system.dart
A pkg/dds/lib/src/devtools/server_api.dart
A pkg/dds/lib/src/devtools/usage.dart
M pkg/dds/pubspec.yaml
M runtime/bin/main.cc
M runtime/bin/main_options.cc
M sdk/BUILD.gn
M sdk/lib/_internal/vm/bin/vmservice_io.dart
M sdk/lib/_internal/vm/bin/vmservice_server.dart
M sdk/lib/vmservice/vmservice.dart
M third_party/devtools/update.sh
M tools/bots/test_matrix.json
M tools/generate_package_config.dart
M utils/dartdev/BUILD.gn
29 files changed, 1,191 insertions(+), 109 deletions(-)
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Ben Konyi removed a vote from this change.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Patch set 19:Commit-Queue +2
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Ben Konyi removed a vote from this change.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)
Details: https://goto.google.com/dart-cbuild/find/91eb593a0117ffb0f401ed707716a2967bbefee0
Bugs: go/dart-cbuild-bug/91eb593a0117ffb0f401ed707716a2967bbefee0
Attention is currently required from: Ryan Macnak, Jacob Richman, Ben Konyi, Kenzie Schmoll, Siva Annamalai.
1 comment:
Patchset:
Patch Set 20:
go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)
Details: https://goto.google.com/dart-cbuild/find/91eb593a0117ffb0f401ed707716a2967bbefee0
Bugs: go/dart-cbuild-bug/91eb593a0117ffb0f401ed707716a2967bbefee0
The detected regression is an increase of helloflutter binary size in bytes by 20095 (from 1501740 to 1521835), or 1.3%, which increases 1 percent threshold. If that is expected, this regression can be ignored
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Ben Konyi, Ivan Inozemtsev, Kenzie Schmoll.
Patch set 20:Code-Review +1
1 comment:
Patchset:
> Patch Set 20: […]
Is the size regression test building hello_world release or profile builds ?
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Ben Konyi, Kenzie Schmoll, Siva Annamalai.
1 comment:
Patchset:
Is the size regression test building hello_world release or profile builds ?
release, more details at b/155337225#comment19
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Ryan Macnak, Jacob Richman, Ben Konyi, Kenzie Schmoll, Siva Annamalai.
Set Ready For Review
Attention is currently required from: Ryan Macnak, Jacob Richman, Kenzie Schmoll, Siva Annamalai.
Patch set 22:Commit-Queue +2
commi...@chromium.org submitted this change.
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Example output on stdout when DevTools is enabled:
Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws
hello world!
vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug.
TEST=pkg/dartdev/test/commands/run_test.dart
Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/188361
Commit-Queue: Ben Konyi <bko...@google.com>
Reviewed-by: Siva Annamalai <as...@google.com>
29 files changed, 1,199 insertions(+), 110 deletions(-)
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 99dc94b..6787873 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-04-30T16:02:33.294454",
+ "generated": "2021-05-03T09:47:39.938400",
"generator": "tools/generate_package_config.dart",
"packages": [
{
@@ -253,6 +253,18 @@
"languageVersion": "2.3"
},
{
+ "name": "devtools_server",
+ "rootUri": "../third_party/devtools/devtools_server",
+ "packageUri": "lib/",
+ "languageVersion": "2.6"
+ },
+ {
+ "name": "devtools_shared",
+ "rootUri": "../third_party/devtools/devtools_shared",
+ "packageUri": "lib/",
+ "languageVersion": "2.3"
+ },
+ {
"name": "diagnostic",
"rootUri": "../pkg/diagnostic",
"packageUri": "lib/",
@@ -728,6 +740,12 @@
"languageVersion": "2.12"
},
{
+ "name": "uuid",
+ "rootUri": "../third_party/pkg/uuid",
+ "packageUri": "lib/",
+ "languageVersion": "2.0"
+ },
+ {
"name": "vector_math",
"rootUri": "../third_party/pkg/vector_math",
"packageUri": "lib/",
diff --git a/.packages b/.packages
index af07227..124a2a3 100644
--- a/.packages
+++ b/.packages
@@ -22,6 +22,7 @@
boolean_selector:third_party/pkg/boolean_selector/lib
browser_launcher:third_party/pkg/browser_launcher/lib
build_integration:pkg/build_integration/lib
+browser_launcher:third_party/pkg/browser_launcher/lib
charcode:third_party/pkg/charcode/lib
cli_util:third_party/pkg/cli_util/lib
collection:third_party/pkg/collection/lib
@@ -38,6 +39,7 @@
dartdoc:third_party/pkg/dartdoc/lib
dds:pkg/dds/lib
dev_compiler:pkg/dev_compiler/lib
+devtools_shared:third_party/devtools/devtools_shared/lib
diagnostic:pkg/diagnostic/lib
expect:pkg/expect/lib
ffi:third_party/pkg/ffi/lib
diff --git a/DEPS b/DEPS
index 2c75fde..83e3cd0 100644
--- a/DEPS
+++ b/DEPS
@@ -80,6 +80,7 @@
"boringssl_gen_rev": "7322fc15cc065d8d2957fccce6b62a509dc4d641",
"boringssl_rev" : "1607f54fed72c6589d560254626909a64124f091",
"browser-compat-data_tag": "v1.0.22",
+ "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"charcode_rev": "bcd8a12c315b7a83390e4865ad847ecd9344cba2",
"chrome_rev" : "19997",
"cli_util_rev" : "fd1b716e8a350a454e01ae56df540293d31ff6c8",
@@ -105,7 +106,6 @@
"dart_style_rev": "f17c23e0eea9a870601c19d904e2a9c1a7c81470",
"chromedriver_tag": "83.0.4103.39",
- "browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
"dartdoc_rev" : "505f163f7cb48e917503e4a23fbff1227e08b263",
"jsshell_tag": "version:88.0",
"ffi_rev": "f3346299c55669cc0db48afae85b8110088bf8da",
@@ -246,7 +246,7 @@
Var("dart_root") + "/third_party/devtools": {
"packages": [{
"package": "dart/third_party/flutter/devtools",
- "version": "revision:6729ec62c3548839018c32fa711756202431ccf7",
+ "version": "git_revision:12ad5341ae0a275042c84a4e7be9a6c98db65612",
}],
"dep_type": "cipd",
},
@@ -319,6 +319,9 @@
Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' +
"@" + Var("browser-compat-data_tag"),
+ Var("dart_root") + "/third_party/pkg/browser_launcher":
+ Var("dart_git") + "browser_launcher.git" + "@" + Var("browser_launcher_rev"),
+
Var("dart_root") + "/third_party/tcmalloc/gperftools":
Var('chromium_git') + '/external/github.com/gperftools/gperftools.git' +
"@" + Var("gperftools_revision"),
@@ -335,9 +338,6 @@
Var("dart_root") + "/third_party/pkg/boolean_selector":
Var("dart_git") + "boolean_selector.git" +
"@" + Var("boolean_selector_rev"),
- Var("dart_root") + "/third_party/pkg/browser_launcher":
- Var("dart_git") + "browser_launcher.git" +
- "@" + Var("browser_launcher_rev"),
Var("dart_root") + "/third_party/pkg/charcode":
Var("dart_git") + "charcode.git" + "@" + Var("charcode_rev"),
Var("dart_root") + "/third_party/pkg/cli_util":
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index b72fbba..5e581c5 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -40,7 +40,8 @@
args = args
.where(
(element) => !(element.contains('--observe') ||
- element.contains('--enable-vm-service')),
+ element.contains('--enable-vm-service') ||
+ element.contains('--devtools')),
)
.toList();
}
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index db32bfe..d996c3c 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -158,6 +158,10 @@
hide: !verbose,
negatable: false,
help: 'Enables tracing of library and script loading.',
+ )
+ ..addFlag(
+ 'debug-dds',
+ hide: true,
);
addExperimentalFlags(argParser, verbose);
}
@@ -179,13 +183,18 @@
String launchDdsArg = argResults['launch-dds'];
String ddsHost = '';
String ddsPort = '';
+
+ // TODO(bkonyi): allow for users to choose not to launch DevTools
+ // See https://github.com/dart-lang/sdk/issues/45867.
+ const bool launchDevTools = true;
bool launchDds = false;
if (launchDdsArg != null) {
launchDds = true;
- final ddsUrl = launchDdsArg.split(':');
+ final ddsUrl = launchDdsArg.split('\\:');
ddsHost = ddsUrl[0];
ddsPort = ddsUrl[1];
}
+ final bool debugDds = argResults['debug-dds'];
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
@@ -198,7 +207,12 @@
if (launchDds) {
debugSession = _DebuggingSession();
if (!await debugSession.start(
- ddsHost, ddsPort, disableServiceAuthCodes)) {
+ ddsHost,
+ ddsPort,
+ disableServiceAuthCodes,
+ launchDevTools,
+ debugDds,
+ )) {
return errorExitCode;
}
}
@@ -242,10 +256,19 @@
class _DebuggingSession {
Future<bool> start(
- String host, String port, bool disableServiceAuthCodes) async {
- final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
+ String host,
+ String port,
+ bool disableServiceAuthCodes,
+ bool enableDevTools,
+ bool debugDds,
+ ) async {
+ final sdkDir = dirname(sdk.dart);
+ final fullSdk = sdkDir.endsWith('bin');
+ final ddsSnapshot = fullSdk
? sdk.ddsSnapshot
- : absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
+ : absolute(sdkDir, 'gen', 'dds.dart.snapshot');
+ final devToolsBinaries =
+ fullSdk ? sdk.devToolsBinaries : absolute(sdkDir, 'devtools');
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
return false;
}
@@ -256,30 +279,51 @@
serviceInfo = await Service.getInfo();
}
final process = await Process.start(
- sdk.dart,
- [
- if (dirname(sdk.dart).endsWith('bin'))
- sdk.ddsSnapshot
- else
- absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
- serviceInfo.serverUri.toString(),
- host,
- port,
- disableServiceAuthCodes.toString(),
- ],
- mode: ProcessStartMode.detachedWithStdio);
+ sdk.dart,
+ [
+ if (debugDds) '--enable-vm-service=0',
+ ddsSnapshot,
+ serviceInfo.serverUri.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ debugDds.toString(),index 9937d26..a8c72ee 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -4,6 +4,7 @@
// @dart=2.10
+import 'dart:convert';
import 'dart:io';
import 'package:dds/dds.dart';
@@ -16,6 +17,9 @@
/// - DDS bind address
/// - DDS port
/// - Disable service authentication codes
+/// - Start DevTools
+/// - DevTools build directory
+/// - Enable logging
Future<void> main(List<String> args) async {
if (args.isEmpty) return;
@@ -37,16 +41,37 @@
port: int.parse(args[2]),
);
final disableServiceAuthCodes = args[3] == 'true';
+
+ final startDevTools = args[4] == 'true';
+ Uri devToolsBuildDirectory;
+ if (args[5].isNotEmpty) {
+ devToolsBuildDirectory = Uri.parse(args[5]);
+ }
+ final logRequests = args[6] == 'true';
try {
// TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
// See https://github.com/dart-lang/sdk/issues/43192.
- await DartDevelopmentService.startDartDevelopmentService(
+ final dds = await DartDevelopmentService.startDartDevelopmentService(
remoteVmServiceUri,
serviceUri: serviceUri,
enableAuthCodes: !disableServiceAuthCodes,
+ devToolsConfiguration: startDevTools
+ ? DevToolsConfiguration(
+ enable: startDevTools,
+ customBuildDirectoryPath: devToolsBuildDirectory,
+ )
+ : null,
+ logRequests: logRequests,
);
- stderr.write('DDS started');
- } catch (e) {
- stderr.writeln('Failed to start DDS:\n$e');
+ stderr.write(json.encode({
+ 'state': 'started',
+ if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(),
+ }));
+ } catch (e, st) {
+ stderr.write(json.encode({
+ 'state': 'error',
+ 'error': '$e',
+ 'stacktrace': '$st',
+ }));
}
}
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index f7c7a05..f0e913b 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -44,6 +44,8 @@
Uri serviceUri,
bool enableAuthCodes = true,
bool ipv6 = false,
+ DevToolsConfiguration devToolsConfiguration = const DevToolsConfiguration(),
+ bool logRequests = false,
}) async {
if (remoteVmServiceUri == null) {
throw ArgumentError.notNull('remoteVmServiceUri');
@@ -80,6 +82,8 @@
serviceUri,
enableAuthCodes,
ipv6,
+ devToolsConfiguration,
+ logRequests,
);
await service.startService();
return service;
@@ -125,6 +129,11 @@
/// Returns `null` if the service is not running.
Uri get wsUri;
+ /// The HTTP [Uri] of the hosted DevTools instance.
+ ///
+ /// Returns `null` if DevTools is not running.
+ Uri get devToolsUri;
+
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
/// requests.
bool get isRunning;
@@ -168,3 +177,13 @@index db355fb..85be647 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -24,6 +24,8 @@
import 'binary_compatible_peer.dart';
import 'client.dart';
import 'client_manager.dart';
+import 'constants.dart';
+import 'devtools/devtools_handler.dart';
import 'expression_evaluator.dart';
import 'isolate_manager.dart';
import 'stream_manager.dart';
@@ -51,7 +53,13 @@
class DartDevelopmentServiceImpl implements DartDevelopmentService {
DartDevelopmentServiceImpl(
- this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
+ this._remoteVmServiceUri,
+ this._uri,
+ this._authCodesEnabled,
+ this._ipv6,
+ this._devToolsConfiguration,
+ this.shouldLogRequests,
+ ) {
_clientManager = ClientManager(this);
_expressionEvaluator = ExpressionEvaluator(this);
_isolateManager = IsolateManager(this);
@@ -113,20 +121,26 @@
(_ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4)
.host;
final port = uri?.port ?? 0;
-
+ final pipeline = const Pipeline();
+ if (shouldLogRequests) {
+ pipeline.addMiddleware(
+ logRequests(
+ logger: (String message, bool isError) {
+ print('Log: $message');
+ },
+ ),
+ );
+ }
+ pipeline.addMiddleware(_authCodeMiddleware);
+ final handler = pipeline.addHandler(_handlers().handler);
// Start the DDS server.
- _server = await io.serve(
- const Pipeline()
- .addMiddleware(_authCodeMiddleware)
- .addHandler(_handlers().handler),
- host,
- port);
+ _server = await io.serve(handler, host, port);
final tmpUri = Uri(
scheme: 'http',
host: host,
port: _server.port,
- path: '$_authCode/',
+ path: '$authCode/',
);
// Notify the VM service that this client is DDS and that it should close
@@ -157,7 +171,7 @@
return;
}
_shuttingDown = true;
- // Don't accept anymore HTTP requests.
+ // Don't accept any more HTTP requests.
await _server?.close();
// Close connections to clients.
@@ -197,7 +211,7 @@
return forbidden;
}
final authToken = pathSegments[0];
- if (authToken != _authCode) {
+ if (authToken != authCode) {
return forbidden;
}
// Creates a new request with the authentication code stripped from
@@ -233,18 +247,12 @@
});
Handler _sseHandler() {
- // Give connections time to reestablish before considering them closed.
- // Required to reestablish connections killed by UberProxy.
- const keepAlive = Duration(seconds: 30);
- final handler = authCodesEnabled
- ? SseHandler(
- Uri.parse('/$_authCode/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- )
- : SseHandler(
- Uri.parse('/$_kSseHandlerPath'),
- keepAlive: keepAlive,
- );
+ final handler = SseHandler(
+ authCodesEnabled
+ ? Uri.parse('/$authCode/$_kSseHandlerPath')
+ : Uri.parse('/$_kSseHandlerPath'),
+ keepAlive: sseKeepAlive,
+ );
handler.connections.rest.listen((sseConnection) {
final client = DartDevelopmentServiceClient.fromSSEConnection(
@@ -259,10 +267,18 @@
}
Handler _httpHandler() {
- // DDS doesn't support any HTTP requests itself, so we just forward all of
- // them to the VM service.
- final cascade = Cascade().add(proxyHandler(remoteVmServiceUri));
- return cascade.handler;
+ if (_devToolsConfiguration != null && _devToolsConfiguration.enable) {
+ // Install the DevTools handlers and forward any unhandled HTTP requests to
+ // the VM service.
+ final buildDir =
+ _devToolsConfiguration.customBuildDirectoryPath?.toFilePath();
+ return devtoolsHandler(
+ dds: this,
+ buildDir: buildDir,
+ notFoundHandler: proxyHandler(remoteVmServiceUri),
+ );
+ }
+ return proxyHandler(remoteVmServiceUri);
}
List<String> _cleanupPathSegments(Uri uri) {
@@ -296,14 +312,43 @@+ String get authCode => _authCode;
String _authCode;
+ final bool shouldLogRequests;
+
@override
Uri get remoteVmServiceUri => _remoteVmServiceUri;
@@ -313,17 +358,21 @@
@override
Uri get uri => _uri;
+ Uri _uri;
@override
Uri get sseUri => _toSse(_uri);
Uri get wsUri => _toWebSocket(_uri);
- Uri _uri;
+
+ Uri get devToolsUri => _toDevTools(_uri);
final bool _ipv6;
bool get isRunning => _uri != null;
+ final DevToolsConfiguration _devToolsConfiguration;
+
Future<void> get done => _done.future;
Completer _done = Completer<void>();
bool _shuttingDown = false;
diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart
new file mode 100644
index 0000000..5f8670e
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_client.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:json_rpc_2/src/server.dart' as json_rpc;
+import 'package:sse/src/server/sse_handler.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import 'server_api.dart';+ bool loggingEnabled,
+ ) {
+ Stream<String> stream = sse.stream;
+ StreamSink sink = sse.sink;
+
+ if (loggingEnabled) {
+ stream = stream.map<String>((String e) {
+ print('DevTools SSE request: $e');
+ return e;
+ });
+ sink = LoggingMiddlewareSink(sink);
+ }
+
+ _server = json_rpc.Server(
+ StreamChannel(stream, sink),
+ strictProtocolChecks: false,
+ );
+ _registerJsonRpcMethods();
+ _server.listen();
+ }
+
+ void _registerJsonRpcMethods() {
+ _server.registerMethod('connected', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('currentPage', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('disconnected', (parameters) {
+ // Nothing to do here.
+ });
+
+ _server.registerMethod('getPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = ServerApi.devToolsPreferences.properties[key];
+ return value;
+ });
+
+ _server.registerMethod('setPreferenceValue', (parameters) {
+ final key = parameters['key'].asString;
+ final value = parameters['value'].value;
+ ServerApi.devToolsPreferences.properties[key] = value;
+ });
+ }
+
+ json_rpc.Server _server;
+}
diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/devtools_handler.dart
new file mode 100644
index 0000000..a0a4bf1
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/devtools_handler.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:async';
+
+import 'package:dds/src/constants.dart';
+import 'package:meta/meta.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:sse/server/sse_handler.dart';
+
+import '../dds_impl.dart';
+import 'devtools_client.dart';
+import 'server_api.dart';+ dds.shouldLogRequests,diff --git a/pkg/dds/lib/src/devtools/file_system.dart b/pkg/dds/lib/src/devtools/file_system.dart
new file mode 100644
index 0000000..9a05de7
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/file_system.dart
@@ -0,0 +1,84 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
+import 'usage.dart';
+
+class LocalFileSystem {
+ static String _userHomeDir() {
+ final String envKey =
+ Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
+ final String value = Platform.environment[envKey];
+ return value == null ? '.' : value;
+ }
+
+ /// Returns the path to the DevTools storage directory.
+ static String devToolsDir() {
+ return path.join(_userHomeDir(), '.flutter-devtools');
+ }
+
+ /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools file
+ /// exists in the user's home directory.
+ static void maybeMoveLegacyDevToolsStore() {
+ final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName));
+ if (file.existsSync()) {
+ ensureDevToolsDirectory();
+ file.copySync(path.join(devToolsDir(), DevToolsUsage.storeName));
+ file.deleteSync();
+ }
+ }
+
+ /// Creates the ~/.flutter-devtools directory if it does not already exist.
+ static void ensureDevToolsDirectory() {
+ Directory('${LocalFileSystem.devToolsDir()}').createSync();
+ }
+
+ /// Returns a DevTools file from the given path.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static File devToolsFileFromPath(String pathFromDevToolsDir) {
+ if (pathFromDevToolsDir.contains('..')) {
+ // The passed in path should not be able to walk up the directory tree
+ // outside of the ~/.flutter-devtools/ directory.
+ return null;
+ }
+ ensureDevToolsDirectory();
+ final file = File(path.join(devToolsDir(), pathFromDevToolsDir));
+ if (!file.existsSync()) {
+ return null;
+ }
+ return file;
+ }
+
+ /// Returns a DevTools file from the given path as encoded json.
+ ///
+ /// Only files within ~/.flutter-devtools/ can be accessed.
+ static String devToolsFileAsJson(String pathFromDevToolsDir) {
+ final file = devToolsFileFromPath(pathFromDevToolsDir);
+ if (file == null) return null;
+
+ final fileName = path.basename(file.path);
+ if (!fileName.endsWith('.json')) return null;
+
+ final content = file.readAsStringSync();
+ final json = jsonDecode(content);
+ json['lastModifiedTime'] = file.lastModifiedSync().toString();
+ return jsonEncode(json);
+ }
+
+ /// Whether the flutter store file exists.
+ static bool flutterStoreExists() {
+ final flutterStore = File('${_userHomeDir()}/.flutter');
+ return flutterStore.existsSync();
+ }
+}
diff --git a/pkg/dds/lib/src/devtools/server_api.dart b/pkg/dds/lib/src/devtools/server_api.dart
new file mode 100644
index 0000000..b866f44
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/server_api.dart
@@ -0,0 +1,230 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:devtools_shared/devtools_shared.dart';
+import 'package:shelf/shelf.dart' as shelf;
+
+import 'file_system.dart';
+import 'usage.dart';
+
+/// The DevTools server API.
+///
+/// This defines endpoints that serve all requests that come in over api/.
+class ServerApi {
+ static const errorNoActiveSurvey = 'ERROR: setActiveSurvey not called.';
+
+ /// Determines whether or not [request] is an API call.
+ static bool canHandle(shelf.Request request) {
+ return request.url.path.startsWith(apiPrefix);
+ }
+
+ /// Handles all requests.
+ ///
+ /// To override an API call, pass in a subclass of [ServerApi].
+ static FutureOr<shelf.Response> handle(
+ shelf.Request request, [
+ ServerApi api,
+ ]) {
+ api ??= ServerApi();
+ switch (request.url.path) {
+ // ----- Flutter Tool GA store. -----
+ case apiGetFlutterGAEnabled:
+ // Is Analytics collection enabled?
+ return api.getCompleted(
+ request,
+ json.encode(FlutterUsage.doesStoreExist ? _usage.enabled : null),
+ );
+ case apiGetFlutterGAClientId:
+ // Flutter Tool GA clientId - ONLY get Flutter's clientId if enabled is
+ // true.
+ return (FlutterUsage.doesStoreExist)
+ ? api.getCompleted(
+ request,
+ json.encode(_usage.enabled ? _usage.clientId : null),
+ )
+ : api.getCompleted(
+ request,
+ json.encode(null),
+ );
+
+ // ----- DevTools GA store. -----
+
+ case apiResetDevTools:
+ _devToolsUsage.reset();
+ return api.getCompleted(request, json.encode(true));
+ case apiGetDevToolsFirstRun:
+ // Has DevTools been run first time? To bring up welcome screen.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.isFirstRun),
+ );
+ case apiGetDevToolsEnabled:
+ // Is DevTools Analytics collection enabled?
+ return api.getCompleted(request, json.encode(_devToolsUsage.enabled));
+ case apiSetDevToolsEnabled:
+ // Enable or disable DevTools analytics collection.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(devToolsEnabledPropertyName)) {
+ _devToolsUsage.enabled =
+ json.decode(queryParams[devToolsEnabledPropertyName]);
+ }
+ return api.setCompleted(request, json.encode(_devToolsUsage.enabled));
+
+ // ----- DevTools survey store. -----
+
+ case apiSetActiveSurvey:
+ // Assume failure.
+ bool result = false;
+
+ // Set the active survey used to store subsequent apiGetSurveyActionTaken,
+ // apiSetSurveyActionTaken, apiGetSurveyShownCount, and
+ // apiIncrementSurveyShownCount calls.
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.keys.length == 1 &&
+ queryParams.containsKey(activeSurveyName)) {
+ final String theSurveyName = queryParams[activeSurveyName];
+
+ // Set the current activeSurvey.
+ _devToolsUsage.activeSurvey = theSurveyName;
+ result = true;
+ }
+
+ return api.getCompleted(request, json.encode(result));
+ case apiGetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyActionTaken');
+ }
+ // SurveyActionTaken has the survey been acted upon (taken or dismissed)
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ // TODO(terry): remove the query param logic for this request.
+ // setSurveyActionTaken should only be called with the value of true, so
+ // we can remove the extra complexity.
+ case apiSetSurveyActionTaken:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiSetSurveyActionTaken');
+ }
+ // Set the SurveyActionTaken.
+ // Has the survey been taken or dismissed..
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(surveyActionTakenPropertyName)) {
+ _devToolsUsage.surveyActionTaken =
+ json.decode(queryParams[surveyActionTakenPropertyName]);
+ }
+ return api.setCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyActionTaken),
+ );
+ case apiGetSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiGetSurveyShownCount');
+ }
+ // SurveyShownCount how many times have we asked to take survey.
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiIncrementSurveyShownCount:
+ // Request setActiveSurvey has not been requested.
+ if (_devToolsUsage.activeSurvey == null) {
+ return api.badRequest('$errorNoActiveSurvey '
+ '- $apiIncrementSurveyShownCount');
+ }
+ // Increment the SurveyShownCount, we've asked about the survey.
+ _devToolsUsage.incrementSurveyShownCount();
+ return api.getCompleted(
+ request,
+ json.encode(_devToolsUsage.surveyShownCount),
+ );
+ case apiGetBaseAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(baseAppSizeFilePropertyName)) {
+ final filePath = queryParams[baseAppSizeFilePropertyName];
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for base app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$baseAppSizeFilePropertyName');
+ case apiGetTestAppSizeFile:
+ final queryParams = request.requestedUri.queryParameters;
+ if (queryParams.containsKey(testAppSizeFilePropertyName)) {
+ final filePath = queryParams[testAppSizeFilePropertyName];
+ final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
+ if (fileJson == null) {
+ return api.badRequest('No JSON file available at $filePath.');
+ }
+ return api.getCompleted(request, fileJson);
+ }
+ return api.badRequest('Request for test app size file does not '
+ 'contain a query parameter with the expected key: '
+ '$testAppSizeFilePropertyName');
+ default:
+ return api.notImplemented(request);
+ }
+ }
+
+ // Accessing Flutter usage file e.g., ~/.flutter.
+ // NOTE: Only access the file if it exists otherwise Flutter Tool hasn't yet
+ // been run.
+ static final FlutterUsage _usage =
+ FlutterUsage.doesStoreExist ? FlutterUsage() : null;
+
+ // Accessing DevTools usage file e.g., ~/.devtools
+ static final DevToolsUsage _devToolsUsage = DevToolsUsage();
+
+ static DevToolsUsage get devToolsPreferences => _devToolsUsage;
+
+ /// Logs a page view in the DevTools server.
+ ///
+ /// In the open-source version of DevTools, Google Analytics handles this
+ /// without any need to involve the server.
+ FutureOr<shelf.Response> logScreenView(shelf.Request request) =>
+ notImplemented(request);
+
+ /// Return the value of the property.
+ FutureOr<shelf.Response> getCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// Return the value of the property after the property value has been set.
+ FutureOr<shelf.Response> setCompleted(shelf.Request request, String value) =>
+ shelf.Response.ok('$value');
+
+ /// A [shelf.Response] for API calls that encountered a request problem e.g.,
+ /// setActiveSurvey not called.
+ ///
+ /// This is a 400 Bad Request response.
+ FutureOr<shelf.Response> badRequest([String logError]) {
+ if (logError != null) print(logError);
+ return shelf.Response(HttpStatus.badRequest);
+ }
+
+ /// A [shelf.Response] for API calls that have not been implemented in this
+ /// server.
+ ///
+ /// This is a no-op 204 No Content response because returning 404 Not Found
+ /// creates unnecessary noise in the console.
+ FutureOr<shelf.Response> notImplemented(shelf.Request request) =>
+ shelf.Response(HttpStatus.noContent);
+}
diff --git a/pkg/dds/lib/src/devtools/usage.dart b/pkg/dds/lib/src/devtools/usage.dart
new file mode 100644
index 0000000..afa35d7
--- /dev/null
+++ b/pkg/dds/lib/src/devtools/usage.dart
@@ -0,0 +1,236 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart=2.9
+
+// TODO(bkonyi): remove once package:devtools_server_api is available
+// See https://github.com/flutter/devtools/issues/2958.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:usage/usage_io.dart';
+
+import 'file_system.dart';
+
+/// Access the file '~/.flutter'.
+class FlutterUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ FlutterUsage({
+ String settingsName = 'flutter',
+ String versionOverride,
+ String configDirOverride,
+ }) {
+ _analytics = AnalyticsIO('', settingsName, '');
+ }
+
+ Analytics _analytics;
+
+ /// Does the .flutter store exist?
+ static bool get doesStoreExist {
+ return LocalFileSystem.flutterStoreExists();
+ }
+
+ bool get isFirstRun => _analytics.firstRun;
+
+ bool get enabled => _analytics.enabled;
+
+ set enabled(bool value) => _analytics.enabled = value;
+
+ String get clientId => _analytics.clientId;
+}
+
+// Access the DevTools on disk store (~/.devtools/.devtools).
+class DevToolsUsage {
+ /// Create a new Usage instance; [versionOverride] and [configDirOverride] are
+ /// used for testing.
+ DevToolsUsage({
+ String versionOverride,
+ String configDirOverride,
+ }) {
+ LocalFileSystem.maybeMoveLegacyDevToolsStore();
+ properties = IOPersistentProperties(
+ storeName,
+ documentDirPath: LocalFileSystem.devToolsDir(),
+ );
+ }
+
+ static const storeName = '.devtools';
+
+ /// The activeSurvey is the property name of a top-level property
+ /// existing or created in the file ~/.devtools
+ /// If the property doesn't exist it is created with default survey values:
+ ///
+ /// properties[activeSurvey]['surveyActionTaken'] = false;
+ /// properties[activeSurvey]['surveyShownCount'] = 0;
+ ///
+ /// It is a requirement that the API apiSetActiveSurvey must be called before
+ /// calling any survey method on DevToolsUsage (addSurvey, rewriteActiveSurvey,
+ /// surveyShownCount, incrementSurveyShownCount, or surveyActionTaken).
+ String _activeSurvey;
+
+ IOPersistentProperties properties;
+
+ static const _surveyActionTaken = 'surveyActionTaken';
+ static const _surveyShownCount = 'surveyShownCount';
+
+ void reset() {
+ properties.remove('firstRun');
+ properties['enabled'] = false;
+ }
+
+ bool get isFirstRun {
+ properties['firstRun'] = properties['firstRun'] == null;
+ return properties['firstRun'];
+ }
+
+ bool get enabled {
+ if (properties['enabled'] == null) {
+ properties['enabled'] = false;
+ }
+
+ return properties['enabled'];
+ }
+
+ set enabled(bool value) {
+ properties['enabled'] = value;
+ return properties['enabled'];
+ }
+
+ bool surveyNameExists(String surveyName) => properties[surveyName] != null;
+
+ void _addSurvey(String surveyName) {
+ assert(activeSurvey != null);
+ assert(activeSurvey == surveyName);
+ rewriteActiveSurvey(false, 0);
+ }
+
+ String get activeSurvey => _activeSurvey;
+
+ set activeSurvey(String surveyName) {
+ assert(surveyName != null);
+ _activeSurvey = surveyName;
+
+ if (!surveyNameExists(activeSurvey)) {
+ // Create the survey if property is non-existent in ~/.devtools
+ _addSurvey(activeSurvey);
+ }
+ }
+
+ /// Need to rewrite the entire survey structure for property to be persisted.
+ void rewriteActiveSurvey(bool actionTaken, int shownCount) {
+ assert(activeSurvey != null);
+ properties[activeSurvey] = {
+ _surveyActionTaken: actionTaken,
+ _surveyShownCount: shownCount,
+ };
+ }
+
+ int get surveyShownCount {
+ assert(activeSurvey != null);
+ final prop = properties[activeSurvey];
+ if (prop[_surveyShownCount] == null) {
+ rewriteActiveSurvey(prop[_surveyActionTaken], 0);
+ }
+ return properties[activeSurvey][_surveyShownCount];
+ }
+
+ void incrementSurveyShownCount() {
+ assert(activeSurvey != null);
+ surveyShownCount; // Ensure surveyShownCount has been initialized.
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(prop[_surveyActionTaken], prop[_surveyShownCount] + 1);
+ }
+
+ bool get surveyActionTaken {
+ assert(activeSurvey != null);
+ return properties[activeSurvey][_surveyActionTaken] == true;
+ }
+
+ set surveyActionTaken(bool value) {
+ assert(activeSurvey != null);
+ final prop = properties[activeSurvey];
+ rewriteActiveSurvey(value, prop[_surveyShownCount]);
+ }
+}
+
+abstract class PersistentProperties {
+ PersistentProperties(this.name);
+
+ final String name;
+
+ dynamic operator [](String key);
+
+ void operator []=(String key, dynamic value);
+
+ /// Re-read settings from the backing store.
+ ///
+ /// May be a no-op on some platforms.
+ void syncSettings();
+}
+
+const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' ');
+
+class IOPersistentProperties extends PersistentProperties {
+ IOPersistentProperties(
+ String name, {
+ String documentDirPath,
+ }) : super(name) {
+ final String fileName = name.replaceAll(' ', '_');
+ documentDirPath ??= LocalFileSystem.devToolsDir();
+ _file = File(path.join(documentDirPath, fileName));
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) {
+ _file = file;
+ if (!_file.existsSync()) {
+ _file.createSync(recursive: true);
+ }
+ syncSettings();
+ }
+
+ File _file;
+
+ Map _map;
+
+ @override
+ dynamic operator [](String key) => _map[key];
+
+ @override
+ void operator []=(String key, dynamic value) {
+ if (value == null && !_map.containsKey(key)) return;
+ if (_map[key] == value) return;
+
+ if (value == null) {
+ _map.remove(key);
+ } else {
+ _map[key] = value;
+ }
+
+ try {
+ _file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n');
+ } catch (_) {}
+ }
+
+ @override
+ void syncSettings() {
+ try {
+ String contents = _file.readAsStringSync();
+ if (contents.isEmpty) contents = '{}';
+ _map = jsonDecode(contents);
+ } catch (_) {
+ _map = {};
+ }
+ }
+
+ void remove(String propertyName) {
+ _map.remove(propertyName);
+ }
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 221e3df..a69236c 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
A library used to spawn the Dart Developer Service, used to communicate with
a Dart VM Service instance.
-version: 1.7.6
+version: 1.8.0-dev
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
@@ -12,18 +12,21 @@
dependencies:
async: ^2.4.1
+ devtools_shared: ^2.0.0
json_rpc_2: ^2.2.0
meta: ^1.1.8
+ path: ^1.8.0
pedantic: ^1.7.0
shelf: ^1.0.0
shelf_proxy: ^1.0.0
+ shelf_static: ^1.0.0-dev
shelf_web_socket: ^1.0.0
sse: ^3.7.0
stream_channel: ^2.0.0
+ usage: ^4.0.0
vm_service: ^6.0.1-nullsafety.0
index d1f8a5d..833e5a2 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -43,6 +43,7 @@
// HTTP server.
Server? server;
Future<Server>? serverFuture;
+_DebuggingSession? ddsInstance;
Server _lazyServerBoot() {
var localServer = server;
@@ -58,6 +59,90 @@+ if (fullSdk) 'resources',
+ 'devtools',
+ ].join('/');
+
+ const enableLogging = false;
+ _process = await Process.start(
+ dartPath.toString(),
+ [
+ ddsSnapshot,
+ server!.serverAddress!.toString(),
+ host,
+ port,
+ disableServiceAuthCodes.toString(),
+ enableDevTools.toString(),
+ devToolsBinaries,
+ enableLogging.toString(),@@ -221,10 +306,6 @@
_server.acceptNewWebSocketConnections = enable;
}
-void _clearFuture(_) {
- serverFuture = null;
-}
-
_onSignal(ProcessSignal signal) {
if (serverFuture != null) {
// Still waiting.
@@ -233,9 +314,21 @@index a69f6ee..077d758 100755
--- a/third_party/devtools/update.sh
+++ b/third_party/devtools/update.sh
@@ -30,12 +30,11 @@
# to serve from DDS.
mkdir cipd_package
cp -R packages/devtools/build/ cipd_package/web
-cp -r packages/devtools_server cipd_package
cp -r packages/devtools_shared cipd_package
cipd create \
-name dart/third_party/flutter/devtools \
-in cipd_package \
-install-mode copy \
- -tag revision:$1
+ -tag git_revision:$1
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index be5f821..bfa020b 100644index 7f33d99..3ce3bc3 100644
--- a/utils/dartdev/BUILD.gn
+++ b/utils/dartdev/BUILD.gn
@@ -2,12 +2,14 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
+import("../../build/dart/copy_tree.gni")
import("../application_snapshot.gni")
group("dartdev") {
public_deps = [
":copy_dartdev_kernel",
":copy_dartdev_snapshot",
+ ":copy_prebuilt_devtools",
]
}
@@ -39,3 +41,15 @@
deps = [ "../dds:dds" ]
output = "$root_gen_dir/dartdev.dart.snapshot"
}
+
+copy_trees("copy_prebuilt_devtools") {
+ sources = [
+ {
+ target = "copy_prebuilt_devtools"
+ visibility = [ ":dartdev" ]
+ source = "../../third_party/devtools/web"
+ dest = "$root_out_dir/devtools"
+ ignore_patterns = "{}"
+ },
+ ]
+}
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
go/dart-cbuild result: FAILURE (REGRESSIONS DETECTED)
Details: https://goto.google.com/dart-cbuild/find/ef0e4ea10713b546363f24511bfdc2c6ce52d861
Bugs: go/dart-cbuild-bug/ef0e4ea10713b546363f24511bfdc2c6ce52d861
1 comment:
Patchset:
This caused a Flutter HHH build failure because the devtools CIPD package was not rolled automatically.
Addressed by https://dart-review.googlesource.com/c/sdk/+/198042 and https://github.com/flutter/engine/pull/25912.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.
Alexander Markov has created a revert of this change.
To view, visit change 188361. To unsubscribe, or for help writing mail filters, visit settings.