[ CLI ] Add dart development-service command

The `dart development-service` command will be used by tooling to launch
DDS from the SDK instead of shipping DDS via package:dds.

TEST=Existing service test suite

Change-Id: Ib928aa5b8961caf87d7074884c3d226b5c096ccd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/361180
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi
2024-04-05 16:30:11 +00:00
committed by Commit Queue
parent d1f41416c1
commit e82ff48027
10 changed files with 250 additions and 159 deletions
+2
View File
@@ -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<int> {
));
addCommand(CreateCommand(verbose: verbose));
addCommand(DebugAdapterCommand(verbose: verbose));
addCommand(DevelopmentServiceCommand(verbose: verbose));
addCommand(DevToolsCommand(verbose: verbose));
addCommand(DocCommand(verbose: verbose));
addCommand(FixCommand(verbose: verbose));
@@ -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<int> 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,
);
}
}
@@ -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<int>();
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,
);
}
}
@@ -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<int>();
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,
);
}
}
+71 -50
View File
@@ -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);
}
}
+47
View File
@@ -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<int> runFromSnapshot({
required String snapshot,
required List<String> args,
required bool verbose,
}) async {
if (!Sdk.checkArtifactExists(snapshot)) return 255;
int retval = 0;
final result = Completer<int>();
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 **
+5 -45
View File
@@ -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<void> main(List<String> 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(),
}));
}
+11
View File
@@ -206,6 +206,11 @@ class DartDevelopmentServiceException implements Exception {
@override
String toString() => 'DartDevelopmentServiceException: $message';
Map<String, Object?> 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<String, Object?> toJson() => {
...super.toJson(),
'uri': ddsUri.toString(),
};
}
class DevToolsConfiguration {
+54
View File
@@ -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;
}
}
+4 -6
View File
@@ -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',