blob: db32bfed05753e8127b7066c327ea63b47888f1b [file] [log] [blame]
// Copyright (c) 2020, 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:developer';
import 'dart:io';
import 'package:args/args.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart'
show Verbosity;
import 'package:path/path.dart';
import 'package:pub/pub.dart';
import '../core.dart';
import '../experiments.dart';
import '../sdk.dart';
import '../utils.dart';
import '../vm_interop_handler.dart';
class RunCommand extends DartdevCommand {
static const String cmdName = 'run';
// kErrorExitCode, as defined in runtime/bin/error_exit.h
static const errorExitCode = 255;
// This argument parser is here solely to ensure that VM specific flags are
// provided before any command and to provide a more consistent help message
// with the rest of the tool.
@override
ArgParser createArgParser() {
return ArgParser(
// Don't parse flags after script name.
allowTrailingOptions: false,
usageLineLength: dartdevUsageLineLength,
);
}
RunCommand({bool verbose = false})
: super(
cmdName,
'Run a Dart program.',
) {
// NOTE: When updating this list of flags, be sure to add any VM flags to
// the list of flags in Options::ProcessVMDebuggingOptions in
// runtime/bin/main_options.cc. Failure to do so will result in those VM
// options being ignored.
argParser
..addSeparator(
'Debugging options:',
)
..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.')
..addSeparator(
'Options implied by --observe are currently:',
)
..addOption(
'enable-vm-service',
help: 'Enables the VM service and listens on the specified port for '
'connections (default port number is 8181, default bind address '
'is localhost).',
valueHelp: '[<port>[/<bind-address>]]',
)
..addFlag(
'pause-isolates-on-exit',
help: 'Pause isolates on exit when '
'running with --enable-vm-service.',
)
..addFlag(
'pause-isolates-on-unhandled-exceptions',
help: 'Pause isolates when an unhandled exception is encountered '
'when running with --enable-vm-service.',
)
..addFlag(
'warn-on-pause-with-no-debugger',
help: 'Print a warning when an isolate pauses with no attached debugger'
' when running with --enable-vm-service.',
)
..addSeparator(
'Other debugging options:',
)
..addFlag(
'pause-isolates-on-start',
help: 'Pause isolates on start when '
'running with --enable-vm-service.',
)
..addFlag(
'enable-asserts',
help: 'Enable assert statements.',
)
..addOption(
'verbosity',
help: 'Sets the verbosity level of the compilation.',
defaultsTo: Verbosity.defaultValue,
allowed: Verbosity.allowedValues,
allowedHelp: Verbosity.allowedValuesHelp,
);
if (verbose) {
argParser
..addSeparator(
'Advanced options:',
);
}
argParser
..addMultiOption(
'define',
abbr: 'D',
valueHelp: 'key=value',
help: 'Define an environment declaration.',
)
..addFlag(
'disable-service-auth-codes',
hide: !verbose,
negatable: false,
help: 'Disables the requirement for an authentication code to '
'communicate with the VM service. Authentication codes help '
'protect against CSRF attacks, so it is not recommended to '
'disable them unless behind a firewall on a secure device.',
)
..addFlag(
'enable-service-port-fallback',
hide: !verbose,
negatable: false,
help: 'When the VM service is told to bind to a particular port, '
'fallback to 0 if it fails to bind instread of failing to '
'start.',
)
..addOption(
'namespace',
hide: !verbose,
valueHelp: 'path',
help: 'The path to a directory that dart:io calls will treat as the '
'root of the filesystem.',
)
..addOption(
'root-certs-file',
hide: !verbose,
valueHelp: 'path',
help: 'The path to a file containing the trusted root certificates '
'to use for secure socket connections.',
)
..addOption(
'root-certs-cache',
hide: !verbose,
valueHelp: 'path',
help: 'The path to a cache directory containing the trusted root '
'certificates to use for secure socket connections.',
)
..addFlag(
'trace-loading',
hide: !verbose,
negatable: false,
help: 'Enables tracing of library and script loading.',
);
addExperimentalFlags(argParser, verbose);
}
@override
String get invocation => '${super.invocation} <dart file | package target>';
@override
FutureOr<int> run() async {
var mainCommand = '';
var runArgs = <String>[];
if (argResults.rest.isNotEmpty) {
mainCommand = argResults.rest.first;
// The command line arguments after the command name.
runArgs = argResults.rest.skip(1).toList();
}
// --launch-dds is provided by the VM if the VM service is to be enabled. In
// that case, we need to launch DDS as well.
String launchDdsArg = argResults['launch-dds'];
String ddsHost = '';
String ddsPort = '';
bool launchDds = false;
if (launchDdsArg != null) {
launchDds = true;
final ddsUrl = launchDdsArg.split(':');
ddsHost = ddsUrl[0];
ddsPort = ddsUrl[1];
}
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
// If the user wants to start a debugging session we need to do some extra
// work and spawn a Dart Development Service (DDS) instance. DDS is a VM
// service intermediary which implements the VM service protocol and
// provides non-VM specific extensions (e.g., log caching, client
// synchronization).
_DebuggingSession debugSession;
if (launchDds) {
debugSession = _DebuggingSession();
if (!await debugSession.start(
ddsHost, ddsPort, disableServiceAuthCodes)) {
return errorExitCode;
}
}
String path;
String packagesConfigOverride;
try {
final filename = maybeUriToFilename(mainCommand);
if (File(filename).existsSync()) {
// TODO(sigurdm): getExecutableForCommand is able to figure this out,
// but does not return a package config override.
path = filename;
packagesConfigOverride = null;
} else {
path = await getExecutableForCommand(mainCommand);
packagesConfigOverride =
join(current, '.dart_tool', 'package_config.json');
}
} on CommandResolutionFailedException catch (e) {
log.stderr(e.message);
return errorExitCode;
}
VmInteropHandler.run(
path,
runArgs,
packageConfigOverride: packagesConfigOverride,
);
return 0;
}
}
/// Try parsing [maybeUri] as a file uri or [maybeUri] itself if that fails.
String maybeUriToFilename(String maybeUri) {
try {
return Uri.parse(maybeUri).toFilePath();
} catch (_) {
return maybeUri;
}
}
class _DebuggingSession {
Future<bool> start(
String host, String port, bool disableServiceAuthCodes) async {
final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
? sdk.ddsSnapshot
: absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
return false;
}
ServiceProtocolInfo serviceInfo = await Service.getInfo();
// Wait for VM service to publish its connection info.
while (serviceInfo.serverUri == null) {
await Future.delayed(Duration(milliseconds: 10));
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);
final completer = Completer<void>();
StreamSubscription sub;
sub = process.stderr.transform(utf8.decoder).listen((event) {
if (event == 'DDS started') {
sub.cancel();
completer.complete();
} else if (event.contains('Failed to start DDS')) {
sub.cancel();
completer.completeError(event.replaceAll(
'Failed to start DDS',
'Could not start Observatory HTTP server',
));
}
});
try {
await completer.future;
return true;
} catch (e) {
stderr.write(e);
return false;
}
}
}