| // 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:io'; |
| |
| import 'package:args/args.dart'; |
| import 'package:dartdev/src/commands/compile.dart'; |
| import 'package:front_end/src/api_prototype/compiler_options.dart' |
| show Verbosity; |
| import 'package:frontend_server/resident_frontend_server_utils.dart' |
| show invokeReplaceCachedDill; |
| import 'package:path/path.dart'; |
| import 'package:pub/pub.dart'; |
| |
| import '../core.dart'; |
| import '../experiments.dart'; |
| import '../generate_kernel.dart'; |
| import '../native_assets.dart'; |
| import '../resident_frontend_constants.dart'; |
| import '../resident_frontend_utils.dart'; |
| import '../utils.dart'; |
| import '../vm_interop_handler.dart'; |
| import 'compilation_server.dart'; |
| |
| class RunCommand extends DartdevCommand { |
| static const bool isProductMode = bool.fromEnvironment('dart.vm.product'); |
| 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, |
| ); |
| } |
| |
| final bool nativeAssetsExperimentEnabled; |
| |
| RunCommand({ |
| bool verbose = false, |
| this.nativeAssetsExperimentEnabled = false, |
| }) : super( |
| cmdName, |
| 'Run a Dart program.', |
| verbose, |
| ) { |
| argParser |
| ..addFlag( |
| residentOption, |
| abbr: 'r', |
| negatable: false, |
| help: 'Enable faster startup times by using a resident frontend ' |
| 'compiler for compilation.\n' |
| 'If --resident-compiler-info-file is provided in conjunction with ' |
| 'this flag, the specified info file will be used, otherwise the ' |
| 'default info file will be used. If there is not already a ' |
| 'compiler associated with the selected info file, one will be ' |
| "started. Refer to 'dart ${CompilationServerCommand.commandName} " |
| "start -h' for more information about info files.", |
| hide: !verbose, |
| ) |
| ..addFlag( |
| quietOption, |
| hide: !verbose, |
| help: 'Disable the printing of messages about the resident compiler ' |
| 'starting up / shutting down.', |
| ) |
| ..addOption( |
| CompilationServerCommand.residentCompilerInfoFileFlag, |
| hide: !verbose, |
| help: CompilationServerCommand.residentCompilerInfoFileFlagDescription, |
| ) |
| ..addOption( |
| CompilationServerCommand.legacyResidentServerInfoFileFlag, |
| // This option is only available for backwards compatibility, and should |
| // never be shown in the help message. |
| hide: true, |
| ); |
| // 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:', |
| ); |
| if (!isProductMode) { |
| argParser |
| ..addOption( |
| 'observe', |
| help: 'The observe flag is a convenience flag used to run a program ' |
| 'with a set of common options useful for debugging. ' |
| 'Run `dart help -v run` for details.', |
| valueHelp: '[<port>[/<bind-address>]]', |
| ) |
| ..addFlag( |
| 'enable-asserts', |
| help: 'Enable assert statements.', |
| ) |
| ..addOption( |
| 'launch-dds', |
| hide: true, |
| help: 'Launch DDS.', |
| ); |
| |
| if (verbose) { |
| argParser.addSeparator( |
| verbose ? 'Options implied by --observe are currently:' : ''); |
| } |
| argParser |
| ..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>]]', |
| hide: !verbose, |
| ) |
| ..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, |
| hide: !verbose, |
| ) |
| ..addFlag( |
| 'pause-isolates-on-exit', |
| help: 'Pause isolates on exit when ' |
| 'running with --enable-vm-service.', |
| hide: !verbose, |
| ) |
| ..addFlag( |
| 'pause-isolates-on-unhandled-exceptions', |
| help: 'Pause isolates when an unhandled exception is encountered ' |
| 'when running with --enable-vm-service.', |
| hide: !verbose, |
| ) |
| ..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.', |
| hide: !verbose, |
| ) |
| ..addOption( |
| 'timeline-streams', |
| help: 'Enables recording for specific timeline streams.\n' |
| 'Valid streams include: all, API, Compiler, CompilerVerbose, Dart, ' |
| 'Debugger, Embedder, GC, Isolate, Microtask, VM.\n' |
| 'Defaults to "Compiler, Dart, GC, Microtask" when --observe is ' |
| 'provided.', |
| valueHelp: 'str1, str2, ...', |
| hide: !verbose, |
| ); |
| |
| if (verbose) { |
| argParser.addSeparator('Other debugging options:'); |
| } |
| argParser |
| ..addFlag( |
| 'pause-isolates-on-start', |
| help: 'Pause isolates on start when ' |
| 'running with --enable-vm-service.', |
| hide: !verbose, |
| ) |
| ..addOption( |
| 'timeline-recorder', |
| help: 'Selects the timeline recorder to use.\n' |
| 'Valid recorders include: none, ring, endless, startup, ' |
| 'systrace, file, callback, perfettofile.\n' |
| 'Defaults to ring.', |
| valueHelp: 'recorder', |
| hide: !verbose, |
| ) |
| ..addFlag( |
| 'profile-microtasks', |
| hide: !verbose, |
| negatable: false, |
| help: 'Record information about each microtask. Information about ' |
| 'completed microtasks will be written to the "Microtask" ' |
| 'timeline stream.', |
| ); |
| } else { |
| argParser.addOption('timeline-recorder', |
| help: 'Selects the timeline recorder to use.\n' |
| 'Valid recorders include: none, systrace, file, callback.\n' |
| 'Defaults to none.', |
| valueHelp: 'recorder'); |
| } |
| |
| argParser.addSeparator('Logging options:'); |
| argParser.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.', |
| hide: !verbose, |
| ); |
| if (!isProductMode) { |
| argParser |
| ..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 instead of failing to ' |
| 'start.', |
| ); |
| } |
| argParser |
| ..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.', |
| ) |
| ..addOption( |
| 'packages', |
| hide: !verbose, |
| valueHelp: 'path', |
| help: 'The path to the package resolution configuration file, which ' |
| 'supplies a mapping of package names\ninto paths.', |
| ); |
| |
| if (!isProductMode) { |
| argParser |
| ..addOption( |
| 'write-service-info', |
| help: 'Outputs information necessary to connect to the VM service to ' |
| 'specified file in JSON format. Useful for clients which are ' |
| 'unable to listen to stdout for the Dart VM service listening ' |
| 'message.', |
| valueHelp: 'file', |
| hide: !verbose, |
| ) |
| ..addFlag('dds', |
| hide: !verbose, |
| help: |
| 'Use the Dart Development Service (DDS) for enhanced debugging ' |
| 'functionality. Note: Disabling DDS may break some ' |
| 'functionality in IDEs and other tooling.', |
| defaultsTo: true) |
| ..addFlag('serve-observatory', |
| hide: !verbose, |
| help: 'Enable hosting Observatory through the VM Service.', |
| defaultsTo: true) |
| ..addFlag( |
| 'print-dtd', |
| hide: !verbose, |
| help: 'Prints connection details for the Dart Tooling Daemon (DTD).' |
| 'Useful for Dart DevTools extension authors working with DTD in the ' |
| 'extension development environment.', |
| ) |
| ..addFlag( |
| 'debug-dds', |
| hide: true, |
| ); |
| } |
| argParser.addExperimentalFlags(verbose: verbose); |
| } |
| |
| @override |
| String get invocation => |
| '${super.invocation} [<dart-file|package-target> [args]]'; |
| |
| /// Attempts to compile [executable] to a kernel file using the Resident |
| /// Frontend Compiler associated with [residentCompilerInfoFile]. If |
| /// [shouldRetryOnFrontendCompilerException] is true, when a |
| /// [FrontendCompilerException] is encountered during compilation, the |
| /// Resident Frontend Compiler will be restarted, and compilation will be |
| /// retried. This method returns the compiled kernel file if compilation |
| /// succeeds, otherwise it returns null. |
| static Future<DartExecutableWithPackageConfig?> |
| _compileToKernelUsingResidentCompiler({ |
| required DartExecutableWithPackageConfig executable, |
| required File residentCompilerInfoFile, |
| required ArgResults args, |
| required bool shouldRetryOnFrontendCompilerException, |
| required bool quiet, |
| }) async { |
| final executableFile = File(executable.executable); |
| assert(!await isFileKernelFile(executableFile) && |
| !await isFileAppJitSnapshot(executableFile) && |
| !await isFileAotSnapshot(executableFile)); |
| |
| try { |
| return await generateKernel( |
| executable, |
| residentCompilerInfoFile, |
| args, |
| createCompileJitJson, |
| quiet: quiet, |
| ); |
| } on FrontendCompilerException catch (e) { |
| if (e.issue == CompilationIssue.serverError) { |
| if (shouldRetryOnFrontendCompilerException) { |
| if (!quiet) { |
| log.stderr( |
| 'Error: A connection to the Resident Frontend Compiler could ' |
| 'not be established. Restarting the Resident Frontend Compiler ' |
| 'and retrying compilation.', |
| ); |
| } |
| await shutDownOrForgetResidentFrontendCompiler( |
| residentCompilerInfoFile, |
| ); |
| return _compileToKernelUsingResidentCompiler( |
| executable: executable, |
| residentCompilerInfoFile: residentCompilerInfoFile, |
| args: args, |
| shouldRetryOnFrontendCompilerException: false, |
| quiet: quiet, |
| ); |
| } else { |
| log.stderr( |
| 'Error: A connection to the Resident Frontend Compiler could ' |
| "not be established. Please re-run 'dart run --resident' and a " |
| 'new compiler will automatically be started in its place.', |
| ); |
| await shutDownOrForgetResidentFrontendCompiler( |
| residentCompilerInfoFile, |
| ); |
| return null; |
| } |
| } else { |
| log.stderr( |
| '${ansi.yellow}Failed to build ${executable.executable}:${ansi.none}'); |
| log.stderr(e.message); |
| return null; |
| } |
| } |
| } |
| |
| @override |
| FutureOr<int> run() async { |
| final args = argResults!; |
| var mainCommand = ''; |
| var runArgs = <String>[]; |
| if (args.rest.isNotEmpty) { |
| mainCommand = args.rest.first; |
| // The command line arguments after the command name. |
| runArgs = args.rest.skip(1).toList(); |
| } |
| |
| String? nativeAssets; |
| final packageConfigUri = await DartNativeAssetsBuilder.ensurePackageConfig( |
| Directory.current.uri, |
| ); |
| if (packageConfigUri != null) { |
| final packageConfig = |
| await DartNativeAssetsBuilder.loadPackageConfig(packageConfigUri); |
| if (packageConfig == null) { |
| return compileErrorExitCode; |
| } |
| final runPackageName = getPackageForCommand(mainCommand) ?? |
| await DartNativeAssetsBuilder.findRootPackageName( |
| Directory.current.uri, |
| ); |
| if (runPackageName != null) { |
| final pubspecUri = await DartNativeAssetsBuilder.findWorkspacePubspec( |
| packageConfigUri); |
| final builder = DartNativeAssetsBuilder( |
| pubspecUri: pubspecUri, |
| packageConfigUri: packageConfigUri, |
| packageConfig: packageConfig, |
| runPackageName: runPackageName, |
| verbose: verbose, |
| ); |
| if (!nativeAssetsExperimentEnabled) { |
| if (await builder.warnOnNativeAssets()) { |
| return errorExitCode; |
| } |
| } else { |
| final assetsYamlFileUri = |
| await builder.compileNativeAssetsJitYamlFile(); |
| if (assetsYamlFileUri == null) { |
| log.stderr('Error: Compiling native assets failed.'); |
| return errorExitCode; |
| } |
| nativeAssets = assetsYamlFileUri.toFilePath(); |
| } |
| } |
| } |
| |
| final String? residentCompilerInfoFileArg = |
| args[CompilationServerCommand.residentCompilerInfoFileFlag] ?? |
| args[CompilationServerCommand.legacyResidentServerInfoFileFlag]; |
| final useResidentCompiler = args.wasParsed(residentOption); |
| if (residentCompilerInfoFileArg != null && !useResidentCompiler) { |
| log.stderr( |
| 'Error: the --resident flag must be passed whenever the ' |
| '--resident-compiler-info-file option is passed.', |
| ); |
| return errorExitCode; |
| } |
| if (args.wasParsed(quietOption) && !useResidentCompiler) { |
| log.stderr( |
| 'Error: the --resident flag must be passed whenever the --quiet flag ' |
| 'is passed.', |
| ); |
| return errorExitCode; |
| } |
| |
| DartExecutableWithPackageConfig executable; |
| final hasExperiments = args.enabledExperiments.isNotEmpty; |
| try { |
| executable = await getExecutableForCommand( |
| mainCommand, |
| allowSnapshot: !(useResidentCompiler || hasExperiments), |
| nativeAssets: nativeAssets, |
| ); |
| } on CommandResolutionFailedException catch (e) { |
| log.stderr(e.message); |
| return errorExitCode; |
| } |
| |
| if (useResidentCompiler) { |
| final File? residentCompilerInfoFile = |
| getResidentCompilerInfoFileConsideringArgs(args); |
| if (residentCompilerInfoFile == null) { |
| log.stderr( |
| CompilationServerCommand |
| .inaccessibleDefaultResidentCompilerInfoFileMessage, |
| ); |
| return errorExitCode; |
| } |
| |
| // Ensure the parent directory exists. |
| if (!residentCompilerInfoFile.parent.existsSync()) { |
| residentCompilerInfoFile.parent.createSync(); |
| } |
| |
| final executableFile = File(executable.executable); |
| if (await isFileKernelFile(executableFile)) { |
| // If the file is a kernel file, we do not need to compile it, but we do |
| // need to replace the file in the resident frontend compiler kernel |
| // cache associated with this executable, because the cached kernel file |
| // may be used to populate context for expression evaluation later. |
| await ensureCompilationServerIsRunning( |
| residentCompilerInfoFile, |
| quiet: args[quietOption] ?? false, |
| ); |
| final succeeded = await invokeReplaceCachedDill( |
| replacementDillPath: executableFile.absolute.path, |
| serverInfoFile: residentCompilerInfoFile, |
| ); |
| if (!succeeded) { |
| log.stderr( |
| 'Error: Encountered a problem accessing the Resident Frontend ' |
| "Compiler's kernel file cache. Please try re-running the same " |
| 'command again. If the error persists, please file an issue at ' |
| 'https://github.com/dart-lang/sdk/issues/new.', |
| ); |
| return errorExitCode; |
| } |
| } else if (!await isFileAppJitSnapshot(executableFile) && |
| !await isFileAotSnapshot(executableFile)) { |
| final compiledKernelFile = await _compileToKernelUsingResidentCompiler( |
| executable: executable, |
| residentCompilerInfoFile: residentCompilerInfoFile, |
| args: args, |
| shouldRetryOnFrontendCompilerException: true, |
| quiet: args[quietOption] ?? false, |
| ); |
| if (compiledKernelFile == null) { |
| return errorExitCode; |
| } else { |
| executable = compiledKernelFile; |
| } |
| } |
| } |
| |
| VmInteropHandler.run( |
| executable.executable, |
| runArgs, |
| packageConfigOverride: |
| args.option('packages') ?? executable.packageConfig, |
| ); |
| return 0; |
| } |
| } |
| |
| /// Keep in sync with [getExecutableForCommand]. |
| /// |
| /// Returns `null` if root package should be used. |
| // TODO(https://github.com/dart-lang/pub/issues/4067): Don't duplicate logic. |
| String? getPackageForCommand(String descriptor) { |
| final root = current; |
| var asPath = descriptor; |
| try { |
| asPath = Uri.parse(descriptor).toFilePath(); |
| } catch (_) { |
| /// Here to get the same logic as[getExecutableForCommand]. |
| } |
| final asDirectFile = join(root, asPath); |
| if (File(asDirectFile).existsSync()) { |
| return null; // root package. |
| } |
| if (!File(join(root, 'pubspec.yaml')).existsSync()) { |
| return null; |
| } |
| String package; |
| if (descriptor.contains(':')) { |
| final parts = descriptor.split(':'); |
| if (parts.length > 2) { |
| return null; |
| } |
| package = parts[0]; |
| if (package.isEmpty) { |
| return null; // root package. |
| } |
| } else { |
| package = descriptor; |
| if (package.isEmpty) { |
| return null; // root package. |
| } |
| } |
| if (package == 'test') { |
| // `dart run test` is expected to behave as `dart test`. |
| return null; // root package. |
| } |
| return package; |
| } |