| // 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.', |
| verbose, |
| ) { |
| // 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( |
| 'serve-devtools', |
| help: 'Serves an instance of the Dart DevTools debugger and profiler ' |
| 'via the VM service at <vm-service-uri>/devtools.', |
| defaultsTo: true, |
| ) |
| ..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.', |
| ) |
| ..addFlag( |
| 'debug-dds', |
| hide: true, |
| ); |
| addExperimentalFlags(argParser, verbose); |
| } |
| |
| @override |
| String get invocation => |
| '${super.invocation} [<dart-file|package-target> [args]]'; |
| |
| @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 launchDevTools = argResults['serve-devtools']; |
| bool launchDds = false; |
| if (launchDdsArg != null) { |
| launchDds = true; |
| final ddsUrl = launchDdsArg.split('\\:'); |
| ddsHost = ddsUrl[0]; |
| ddsPort = ddsUrl[1]; |
| } |
| final bool debugDds = argResults['debug-dds']; |
| |
| 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, |
| launchDevTools, |
| debugDds, |
| )) { |
| 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, |
| bool enableDevTools, |
| bool debugDds, |
| ) async { |
| final sdkDir = dirname(sdk.dart); |
| final fullSdk = sdkDir.endsWith('bin'); |
| final ddsSnapshot = fullSdk |
| ? sdk.ddsSnapshot |
| : absolute(sdkDir, 'gen', 'dds.dart.snapshot'); |
| final devToolsBinaries = |
| fullSdk ? sdk.devToolsBinaries : absolute(sdkDir, 'devtools'); |
| 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 (debugDds) '--enable-vm-service=0', |
| ddsSnapshot, |
| serviceInfo.serverUri.toString(), |
| host, |
| port, |
| disableServiceAuthCodes.toString(), |
| enableDevTools.toString(), |
| devToolsBinaries, |
| debugDds.toString(), |
| ], |
| mode: ProcessStartMode.detachedWithStdio, |
| ); |
| final completer = Completer<void>(); |
| 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 { |
| stderrSub.cancel(); |
| completer.completeError( |
| 'Could not start Observatory HTTP server', |
| ); |
| } |
| }); |
| try { |
| await completer.future; |
| return true; |
| } catch (e) { |
| stderr.write(e); |
| return false; |
| } |
| } |
| } |