Reapply "[ Service ] Start DDS and serve DevTools when the VM service is started via dart:developer"

In the previous version of this change, if the user had 'dart' on their
PATH and invoked 'dart compile js' (which spawns the VM service after
compilation completes), the VM service would attempt to spawn DDS using
'./dart' as the executable path instead of 'dart'. This would result in
DDS failing to start, causing the VM to print an error and hang.

This updated change checks to see if the parent directory of
`Platform.executable` is '.' and then verifies if './dart' exists or
not. If it doesn't, 'dart' is likely on the user's PATH and should be
used directly as the executable path.

See https://github.com/dart-lang/sdk/issues/56087 for details.

This reverts commit 4b88698e48.

TEST=pkg/dds/test/control_web_server_starts_dds_with_dart_on_path_test.dart

Change-Id: Id0f1dadd01d9202cbf7717f31393b43171cf3968
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373561
Auto-Submit: Ben Konyi <bkonyi@google.com>
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi 2024-06-27 22:47:46 +00:00 committed by Commit Queue
parent 8dbe35d5f6
commit 44d4451476
4 changed files with 155 additions and 45 deletions

View file

@ -0,0 +1,52 @@
// Copyright (c) 2024, 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.
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
void main() {
HttpClient? client;
VmService? service;
tearDown(() async {
client?.close();
await service?.dispose();
});
test('Enabling the VM service starts DDS and serves DevTools', () async {
var serviceInfo = await Service.getInfo();
expect(serviceInfo.serverUri, isNull);
serviceInfo = await Service.controlWebServer(
enable: true,
silenceOutput: true,
);
print('VM service started');
expect(serviceInfo.serverUri, isNotNull);
final serverWebSocketUri = serviceInfo.serverWebSocketUri!;
service = await vmServiceConnectUri(
serverWebSocketUri.toString(),
);
// Check that DDS has been launched.
final supportedProtocols =
(await service!.getSupportedProtocols()).protocols!;
expect(supportedProtocols.length, 2);
expect(supportedProtocols.map((e) => e.protocolName), contains('DDS'));
// Check that DevTools assets are accessible.
client = HttpClient();
final devtoolsRequest = await client!.getUrl(serviceInfo.serverUri!);
final devtoolsResponse = await devtoolsRequest.close();
expect(devtoolsResponse.statusCode, 200);
final devtoolsContent =
await devtoolsResponse.transform(utf8.decoder).join();
expect(devtoolsContent, startsWith('<!DOCTYPE html>'));
});
}

View file

@ -0,0 +1,56 @@
// Copyright (c) 2024, 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
// Regression test for https://github.com/dart-lang/sdk/issues/56087
void main() {
late final Process? process;
tearDown(() {
process?.kill();
});
test('Enabling the VM service with dart on PATH spawns DDS', () async {
final script = path.join(
path.dirname(Platform.script.toString()),
'control_web_server_starts_dds_test.dart',
);
process = await Process.start(
'dart',
[script],
environment: {'PATH': path.dirname(Platform.resolvedExecutable)},
);
final completer = Completer<void>();
late final StreamSubscription<String>? stdoutSub;
late final StreamSubscription<String>? stderrSub;
stdoutSub = process!.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((event) {
if (event == 'VM service started') {
stdoutSub!.cancel();
stderrSub?.cancel();
completer.complete();
}
});
stderrSub = process!.stderr.transform(utf8.decoder).listen((_) {
stdoutSub!.cancel();
stderrSub!.cancel();
completer.completeError(
'Unexpected output on stderr! DDS likely failed to start',
);
});
await completer.future;
});
}

View file

@ -15,7 +15,7 @@ Uri? wsServerUri;
Future<Null> testeeBefore() async {
print('testee before');
// First grab the URL where the observatory is listening on and the
// First grab the URL where the VM service is listening and the
// service protocol version numbers. We expect the URL to be null as
// the server has not been started yet.
ServiceProtocolInfo info = await Service.getInfo();

View file

@ -96,11 +96,21 @@ class _DebuggingSession {
bool disableServiceAuthCodes,
bool enableDevTools,
) async {
final dartDir = File(Platform.resolvedExecutable).parent.path;
final executable = [
final dartDir = File(Platform.executable).parent.path;
final dart = 'dart${Platform.isWindows ? '.exe' : ''}';
var executable = [
dartDir,
'dart${Platform.isWindows ? '.exe' : ''}',
dart,
].join(Platform.pathSeparator);
// If the directory of dart is '.' it's likely that dart is on the user's
// PATH. If so, './dart' might not exist and we should be using 'dart'
// instead.
if (dartDir == '.' &&
(await FileSystemEntity.type(executable)) ==
FileSystemEntityType.notFound) {
executable = dart;
}
_process = await Process.start(
executable,
[
@ -133,7 +143,7 @@ class _DebuggingSession {
// is changed to ensure consistency.
const devToolsMessagePrefix =
'The Dart DevTools debugger and profiler is available at:';
print('$devToolsMessagePrefix $devToolsUri');
serverPrint('$devToolsMessagePrefix $devToolsUri');
}
if (result
case {
@ -141,7 +151,7 @@ class _DebuggingSession {
'uri': String dtdUri,
}
} when _printDtd) {
print('The Dart Tooling Daemon (DTD) is available at: $dtdUri');
serverPrint('The Dart Tooling Daemon (DTD) is available at: $dtdUri');
}
} else {
printError(result['error'] ?? result);
@ -308,18 +318,37 @@ Future<List<Map<String, dynamic>>> listFilesCallback(Uri dirPath) async {
Uri? serverInformationCallback() => _lazyServerBoot().serverAddress;
Future<void> _toggleWebServer(Server server) async {
// Toggle HTTP server.
if (server.running) {
await server.shutdown(true).then((_) async {
ddsInstance?.shutdown();
ddsInstance = null;
await VMService().clearState();
serverFuture = null;
});
} else {
await server.startup().then((_) async {
if (_waitForDdsToAdvertiseService) {
ddsInstance = _DebuggingSession();
await ddsInstance!.start(
_ddsIP,
_ddsPort.toString(),
_authCodesDisabled,
_serveDevtools,
);
}
});
}
}
Future<Uri?> webServerControlCallback(bool enable, bool? silenceOutput) async {
if (silenceOutput != null) {
silentObservatory = silenceOutput;
}
final _server = _lazyServerBoot();
if (_server.running != enable) {
if (enable) {
await _server.startup();
// TODO: if dds is enabled a dds instance needs to be started.
} else {
await _server.shutdown(true);
}
await _toggleWebServer(_server);
}
return _server.serverAddress;
}
@ -329,32 +358,13 @@ void webServerAcceptNewWebSocketConnections(bool enable) {
_server.acceptNewWebSocketConnections = enable;
}
_onSignal(ProcessSignal signal) async {
Future<void> _onSignal(ProcessSignal signal) async {
if (serverFuture != null) {
// Still waiting.
return;
}
final _server = _lazyServerBoot();
// Toggle HTTP server.
if (_server.running) {
_server.shutdown(true).then((_) async {
ddsInstance?.shutdown();
await VMService().clearState();
serverFuture = null;
});
} else {
_server.startup().then((_) {
if (_waitForDdsToAdvertiseService) {
ddsInstance = _DebuggingSession()
..start(
_ddsIP,
_ddsPort.toString(),
_authCodesDisabled,
_serveDevtools,
);
}
});
}
final server = _lazyServerBoot();
await _toggleWebServer(server);
}
Timer? _registerSignalHandlerTimer;
@ -400,18 +410,10 @@ main() {
// can be delivered and waiting loaders can be cancelled.
VMService();
if (_autoStart) {
assert(server == null);
final _server = _lazyServerBoot();
_server.startup().then((_) {
if (_waitForDdsToAdvertiseService) {
ddsInstance = _DebuggingSession()
..start(
_ddsIP,
_ddsPort.toString(),
_authCodesDisabled,
_serveDevtools,
);
}
});
assert(!_server.running);
_toggleWebServer(_server);
// It's just here to push an event on the event loop so that we invoke the
// scheduled microtasks.
Timer.run(() {});