diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart index d21b74d4a38..d1466e91b53 100644 --- a/pkg/dartdev/lib/dartdev.dart +++ b/pkg/dartdev/lib/dartdev.dart @@ -21,6 +21,7 @@ import 'src/commands/compilation_server.dart'; import 'src/commands/compile.dart'; import 'src/commands/create.dart'; import 'src/commands/debug_adapter.dart'; +import 'src/commands/development_service.dart'; import 'src/commands/devtools.dart'; import 'src/commands/doc.dart'; import 'src/commands/fix.dart'; @@ -106,6 +107,7 @@ class DartdevRunner extends CommandRunner { )); addCommand(CreateCommand(verbose: verbose)); addCommand(DebugAdapterCommand(verbose: verbose)); + addCommand(DevelopmentServiceCommand(verbose: verbose)); addCommand(DevToolsCommand(verbose: verbose)); addCommand(DocCommand(verbose: verbose)); addCommand(FixCommand(verbose: verbose)); diff --git a/pkg/dartdev/lib/src/commands/development_service.dart b/pkg/dartdev/lib/src/commands/development_service.dart new file mode 100644 index 00000000000..6811c9533bb --- /dev/null +++ b/pkg/dartdev/lib/src/commands/development_service.dart @@ -0,0 +1,46 @@ +// 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 'package:args/args.dart'; +import 'package:dds/src/arg_parser.dart'; + +import '../core.dart'; +import '../sdk.dart'; +import '../utils.dart'; + +class DevelopmentServiceCommand extends DartdevCommand { + static const String commandName = 'development-service'; + + static const String commandDescription = "Start Dart's development service."; + + DevelopmentServiceCommand({bool verbose = false}) + : super( + commandName, + commandDescription, + verbose, + hidden: !verbose, + ); + + @override + ArgParser createArgParser() { + return DartDevelopmentServiceOptions.createArgParser( + usageLineLength: dartdevUsageLineLength, + ); + } + + @override + Future run() async { + // Need to make a copy as argResults!.arguments is an + // UnmodifiableListView object which cannot be passed as + // the args for spawnUri. + final args = [...argResults!.arguments]; + return await runFromSnapshot( + snapshot: sdk.ddsSnapshot, + args: args, + verbose: verbose, + ); + } +} diff --git a/pkg/dartdev/lib/src/commands/language_server.dart b/pkg/dartdev/lib/src/commands/language_server.dart index 4aac0bc00ee..553732f3669 100644 --- a/pkg/dartdev/lib/src/commands/language_server.dart +++ b/pkg/dartdev/lib/src/commands/language_server.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:isolate'; import 'package:analysis_server/src/server/driver.dart' as server; import 'package:args/args.dart'; @@ -43,8 +42,6 @@ For more information about the server's capabilities and configuration, see: const protocol = server.Driver.SERVER_PROTOCOL; const lsp = server.Driver.PROTOCOL_LSP; - if (!Sdk.checkArtifactExists(sdk.analysisServerSnapshot)) return 255; - var args = argResults!.arguments; if (!args.any((arg) => arg.startsWith('--$protocol'))) { args = [...args, '--$protocol=$lsp']; @@ -54,31 +51,10 @@ For more information about the server's capabilities and configuration, see: // the args for spawnUri. args = [...args]; } - - var retval = 0; - final result = Completer(); - final exitPort = ReceivePort() - ..listen((msg) { - result.complete(0); - }); - final errorPort = ReceivePort() - ..listen((error) { - log.stderr(error.toString()); - result.complete(255); - }); - try { - await Isolate.spawnUri(Uri.file(sdk.analysisServerSnapshot), args, null, - onExit: exitPort.sendPort, onError: errorPort.sendPort); - retval = await result.future; - } catch (e, st) { - log.stderr(e.toString()); - if (verbose) { - log.stderr(st.toString()); - } - retval = 255; - } - errorPort.close(); - exitPort.close(); - return retval; + return await runFromSnapshot( + snapshot: sdk.analysisServerSnapshot, + args: args, + verbose: verbose, + ); } } diff --git a/pkg/dartdev/lib/src/commands/tooling_daemon.dart b/pkg/dartdev/lib/src/commands/tooling_daemon.dart index b9c52628f29..44e6f14b502 100644 --- a/pkg/dartdev/lib/src/commands/tooling_daemon.dart +++ b/pkg/dartdev/lib/src/commands/tooling_daemon.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:isolate'; import 'package:args/args.dart'; import 'package:dtd_impl/dart_tooling_daemon.dart' as dtd @@ -39,33 +38,10 @@ class ToolingDaemonCommand extends DartdevCommand { // UnmodifiableListView object which cannot be passed as // the args for spawnUri. final args = [...argResults!.arguments]; - - if (!Sdk.checkArtifactExists(sdk.dtdSnapshot)) return 255; - - var retval = 0; - final result = Completer(); - final exitPort = ReceivePort() - ..listen((msg) { - result.complete(0); - }); - final errorPort = ReceivePort() - ..listen((error) { - log.stderr(error.toString()); - result.complete(255); - }); - try { - await Isolate.spawnUri(Uri.file(sdk.dtdSnapshot), args, null, - onExit: exitPort.sendPort, onError: errorPort.sendPort); - retval = await result.future; - } catch (e, st) { - log.stderr(e.toString()); - if (verbose) { - log.stderr(st.toString()); - } - retval = 255; - } - errorPort.close(); - exitPort.close(); - return retval; + return await runFromSnapshot( + snapshot: sdk.dtdSnapshot, + args: args, + verbose: verbose, + ); } } diff --git a/pkg/dartdev/lib/src/sdk.dart b/pkg/dartdev/lib/src/sdk.dart index 73cb6072211..0e1b9f831da 100644 --- a/pkg/dartdev/lib/src/sdk.dart +++ b/pkg/dartdev/lib/src/sdk.dart @@ -20,88 +20,108 @@ class Sdk { /// The SDK's semantic versioning version (x.y.z-a.b.channel). final String version; + final bool _runFromBuildRoot; + factory Sdk() => _instance; - Sdk._(this.sdkPath, this.version); + Sdk._(this.sdkPath, this.version, bool runFromBuildRoot) + : _runFromBuildRoot = runFromBuildRoot; // Assume that we want to use the same Dart executable that we used to spawn // DartDev. We should be able to run programs with out/ReleaseX64/dart even // if the SDK isn't completely built. String get dart => Platform.resolvedExecutable; - String get dartAotRuntime => path.join( - sdkPath, - 'bin', - 'dartaotruntime${Platform.isWindows ? '.exe' : ''}', - ); + String get dartAotRuntime => _runFromBuildRoot + ? path.absolute( + sdkPath, + Platform.isWindows + ? 'dart_precompiled_runtime_product.exe' + : 'dart_precompiled_runtime_product', + ) + : path.absolute( + sdkPath, + 'bin', + Platform.isWindows ? 'dartaotruntime.exe' : 'dartaotruntime', + ); - String get analysisServerSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get analysisServerSnapshot => _snapshotPathFor( 'analysis_server.dart.snapshot', ); - String get dart2jsSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get dart2jsSnapshot => _snapshotPathFor( 'dart2js.dart.snapshot', ); - String get dart2wasmSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get dart2wasmSnapshot => _snapshotPathFor( 'dart2wasm_product.snapshot', ); - String get ddsSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get ddsSnapshot => _snapshotPathFor( 'dds.dart.snapshot', ); - String get ddsAotSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get ddsAotSnapshot => _snapshotPathFor( 'dds_aot.dart.snapshot', ); - String get frontendServerSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get frontendServerSnapshot => _snapshotPathFor( 'frontend_server.dart.snapshot', ); - String get frontendServerAotSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get frontendServerAotSnapshot => _snapshotPathFor( 'frontend_server_aot.dart.snapshot', ); - String get dtdSnapshot => path.absolute( - sdkPath, - 'bin', - 'snapshots', + String get dtdSnapshot => _snapshotPathFor( 'dart_tooling_daemon.dart.snapshot', ); String get devToolsBinaries => path.absolute( - sdkPath, - 'bin', - 'resources', + _runFromBuildRoot + ? sdkPath + : path.absolute( + sdkPath, + 'bin', + 'resources', + ), 'devtools', ); - String get wasmOpt => path.join(sdkPath, 'bin', 'utils', - Platform.isWindows ? 'wasm-opt.exe' : 'wasm-opt'); + String get wasmOpt => path.absolute( + _runFromBuildRoot + ? sdkPath + : path.absolute( + sdkPath, + 'bin', + 'utils', + ), + Platform.isWindows ? 'wasm-opt.exe' : 'wasm-opt', + ); - String get librariesJson => path.absolute(sdkPath, 'lib', 'libraries.json'); + // This file is only generated when building the SDK and isn't generated for + // non-SDK build targets. + String get librariesJson { + if (_runFromBuildRoot) { + log.stderr( + "WARNING: attempting to access 'libraries.json' from a build root " + 'executable. This file is only present in the context of a full Dart ' + 'SDK.', + ); + } + return path.absolute(sdkPath, 'lib', 'libraries.json'); + } + + String _snapshotPathFor(String snapshotName) => path.absolute( + _runFromBuildRoot + ? sdkPath + : path.absolute( + sdkPath, + 'bin', + 'snapshots', + ), + snapshotName, + ); static bool checkArtifactExists(String path, {bool logError = true}) { if (!File(path).existsSync()) { @@ -124,17 +144,18 @@ class Sdk { var sdkPath = path.absolute(path.dirname(path.dirname(Platform.resolvedExecutable))); var snapshotsDir = path.join(sdkPath, 'bin', 'snapshots'); + var runFromBuildRoot = false; if (!Directory(snapshotsDir).existsSync()) { // This is the less common case where the user is in // the checked out Dart SDK, and is executing `dart` via: // ./out/ReleaseX64/dart ... - // We confirm in a similar manner with the snapshot directory existence + // We confirm in a similar manner with the gen directory existence // and then return the correct sdk path: - var altPath = - path.absolute(path.dirname(Platform.resolvedExecutable), 'dart-sdk'); - var snapshotsDir = path.join(altPath, 'bin', 'snapshots'); - if (Directory(snapshotsDir).existsSync()) { + final altPath = path.absolute(path.dirname(Platform.resolvedExecutable)); + final genPath = path.join(altPath, 'gen'); + if (Directory(genPath).existsSync()) { sdkPath = altPath; + runFromBuildRoot = true; } // If that snapshot dir does not exist either, // we use the first guess anyway. @@ -143,7 +164,7 @@ class Sdk { // Defer to [Runtime] for the version. var version = Runtime.runtime.version; - return Sdk._(sdkPath, version); + return Sdk._(sdkPath, version, runFromBuildRoot); } } diff --git a/pkg/dartdev/lib/src/utils.dart b/pkg/dartdev/lib/src/utils.dart index ae168b28991..aa52ac08fb2 100644 --- a/pkg/dartdev/lib/src/utils.dart +++ b/pkg/dartdev/lib/src/utils.dart @@ -2,17 +2,64 @@ // 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:io'; +import 'dart:isolate'; import 'dart:math' as math; import 'package:args/args.dart'; import 'package:path/path.dart' as p; +import 'core.dart'; +import 'sdk.dart'; + /// For commands where we are able to initialize the [ArgParser], this value /// is used as the usageLineLength. int? get dartdevUsageLineLength => stdout.hasTerminal ? stdout.terminalColumns : null; +/// Runs a tool's snapshot in an isolate. +/// +/// Waits for the spawned isolate to exit before returning. +Future runFromSnapshot({ + required String snapshot, + required List args, + required bool verbose, +}) async { + if (!Sdk.checkArtifactExists(snapshot)) return 255; + + int retval = 0; + final result = Completer(); + final exitPort = ReceivePort() + ..listen((msg) { + result.complete(0); + }); + final errorPort = ReceivePort() + ..listen((error) { + log.stderr(error.toString()); + result.complete(255); + }); + try { + await Isolate.spawnUri( + Uri.file(snapshot), + args, + null, + onExit: exitPort.sendPort, + onError: errorPort.sendPort, + ); + retval = await result.future; + } catch (e, st) { + log.stderr(e.toString()); + if (verbose) { + log.stderr(st.toString()); + } + retval = 255; + } + errorPort.close(); + exitPort.close(); + return retval; +} + /// Global options for dartdev. /// /// ** READ THIS BEFORE MODIFYING ** diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart index fc1b036ab76..2c4cc26240f 100644 --- a/pkg/dds/bin/dds.dart +++ b/pkg/dds/bin/dds.dart @@ -5,51 +5,8 @@ import 'dart:convert'; import 'dart:io'; -import 'package:args/args.dart'; import 'package:dds/dds.dart'; - -abstract class DartDevelopmentServiceOptions { - static const vmServiceUriOption = 'vm-service-uri'; - static const bindAddressOption = 'bind-address'; - static const bindPortOption = 'bind-port'; - static const disableServiceAuthCodesFlag = 'disable-service-auth-codes'; - static const serveDevToolsFlag = 'serve-devtools'; - static const enableServicePortFallbackFlag = 'enable-service-port-fallback'; - - static ArgParser createArgParser() { - return ArgParser() - ..addOption( - vmServiceUriOption, - help: 'The VM service URI DDS will connect to.', - valueHelp: 'uri', - mandatory: true, - ) - ..addOption(bindAddressOption, - help: 'The address DDS should bind to.', - valueHelp: 'address', - defaultsTo: 'localhost') - ..addOption( - bindPortOption, - help: 'The port DDS should be served on.', - valueHelp: 'port', - defaultsTo: '0', - ) - ..addFlag( - disableServiceAuthCodesFlag, - help: 'Disables authentication codes.', - ) - ..addFlag( - serveDevToolsFlag, - help: 'If provided, DDS will serve DevTools.', - ) - ..addFlag( - enableServicePortFallbackFlag, - help: 'Bind to a random port if DDS fails to bind to the provided ' - 'port.', - ) - ..addFlag('help', negatable: false); - } -} +import 'package:dds/src/arg_parser.dart'; Uri _getDevToolsAssetPath() { final dartPath = Uri.parse(Platform.resolvedExecutable); @@ -71,7 +28,9 @@ Uri _getDevToolsAssetPath() { } Future main(List args) async { - final argParser = DartDevelopmentServiceOptions.createArgParser(); + final argParser = DartDevelopmentServiceOptions.createArgParser( + includeHelp: true, + ); final argResults = argParser.parse(args); if (args.isEmpty || argResults.wasParsed('help')) { print(''' @@ -164,5 +123,6 @@ void writeErrorResponse(Object e, StackTrace st) { 'state': 'error', 'error': '$e', 'stacktrace': '$st', + if (e is DartDevelopmentServiceException) 'ddsExceptionDetails': e.toJson(), })); } diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart index ab4de82862f..894983be775 100644 --- a/pkg/dds/lib/dds.dart +++ b/pkg/dds/lib/dds.dart @@ -206,6 +206,11 @@ class DartDevelopmentServiceException implements Exception { @override String toString() => 'DartDevelopmentServiceException: $message'; + Map toJson() => { + 'error_code': errorCode, + 'message': message, + }; + final int errorCode; final String message; } @@ -226,6 +231,12 @@ class ExistingDartDevelopmentServiceException /// not the WebSocket URI (which can be obtained by mapping the scheme to /// `ws` (or `wss`) and appending `ws` to the path segments). final Uri? ddsUri; + + @override + Map toJson() => { + ...super.toJson(), + 'uri': ddsUri.toString(), + }; } class DevToolsConfiguration { diff --git a/pkg/dds/lib/src/arg_parser.dart b/pkg/dds/lib/src/arg_parser.dart new file mode 100644 index 00000000000..1534e136947 --- /dev/null +++ b/pkg/dds/lib/src/arg_parser.dart @@ -0,0 +1,54 @@ +// 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 'package:args/args.dart'; + +abstract class DartDevelopmentServiceOptions { + static const vmServiceUriOption = 'vm-service-uri'; + static const bindAddressOption = 'bind-address'; + static const bindPortOption = 'bind-port'; + static const disableServiceAuthCodesFlag = 'disable-service-auth-codes'; + static const serveDevToolsFlag = 'serve-devtools'; + static const enableServicePortFallbackFlag = 'enable-service-port-fallback'; + + static ArgParser createArgParser({ + int? usageLineLength, + bool includeHelp = false, + }) { + final args = ArgParser(usageLineLength: usageLineLength) + ..addOption( + vmServiceUriOption, + help: 'The VM service URI DDS will connect to.', + valueHelp: 'uri', + mandatory: true, + ) + ..addOption(bindAddressOption, + help: 'The address DDS should bind to.', + valueHelp: 'address', + defaultsTo: 'localhost') + ..addOption( + bindPortOption, + help: 'The port DDS should be served on.', + valueHelp: 'port', + defaultsTo: '0', + ) + ..addFlag( + disableServiceAuthCodesFlag, + help: 'Disables authentication codes.', + ) + ..addFlag( + serveDevToolsFlag, + help: 'If provided, DDS will serve DevTools.', + ) + ..addFlag( + enableServicePortFallbackFlag, + help: 'Bind to a random port if DDS fails to bind to the provided ' + 'port.', + ); + if (includeHelp) { + args.addFlag('help', negatable: false); + } + return args; + } +} diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart index 19b26d8cd06..4ac1f205e23 100644 --- a/sdk/lib/_internal/vm/bin/vmservice_io.dart +++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart @@ -97,16 +97,14 @@ class _DebuggingSession { bool enableDevTools, ) async { final dartDir = File(Platform.resolvedExecutable).parent.path; - final fullSdk = dartDir.endsWith('bin'); - final snapshotName = [ + final executable = [ dartDir, - fullSdk ? 'snapshots' : 'gen', - 'dds.dart.snapshot', + 'dart${Platform.isWindows ? '.exe' : ''}', ].join(Platform.pathSeparator); _process = await Process.start( - Platform.resolvedExecutable, + executable, [ - snapshotName, + 'development-service', '--vm-service-uri=${server!.serverAddress!}', '--bind-address=$host', '--bind-port=$port',