| // 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:io'; | |
| import 'package:args/args.dart'; | |
| import 'package:dartdev/src/commands/compile.dart'; | |
| import 'package:dartdev/src/commands/install.dart'; | |
| import 'package:dartdev/src/install/file_system.dart'; | |
| import 'package:dartdev/src/install/pub_formats.dart'; | |
| import 'package:dartdev/src/progress.dart'; | |
| import 'package:front_end/src/api_prototype/compiler_options.dart' | |
| show Verbosity; | |
| import 'package:package_config/package_config.dart'; | |
| import 'package:path/path.dart'; | |
| import 'package:pub/pub.dart'; | |
| import 'package:yaml/yaml.dart'; | |
| import '../core.dart'; | |
| import '../eval_packages.dart'; | |
| import '../executable_compiler.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 String cmdName = 'run'; | |
| static const gitRefOption = 'git-ref'; | |
| static const gitPathOption = 'git-path'; | |
| // kDartFrontendErrorExitCode, as defined in runtime/bin/error_exit.h | |
| static const dartFrontendErrorExitCode = 252; | |
| // kCompilationErrorExitCode, as defined in runtime/bin/error_exit.h | |
| static const compilationErrorExitCode = 254; | |
| // 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; | |
| final bool dataAssetsExperimentEnabled; | |
| final List<String> vmArgs; | |
| RunCommand({ | |
| bool verbose = false, | |
| this.nativeAssetsExperimentEnabled = false, | |
| this.dataAssetsExperimentEnabled = false, | |
| this.vmArgs = const <String>[], | |
| }) : super(cmdName, ''' | |
| Run a Dart program from a file or a local or remote package. | |
| Usage: | |
| Running a local script or package executable: | |
| dart run [vm-options] <dart-file>|<local-package>[:<executable>] args | |
| Running a remote package executable: | |
| dart run <remote-package>[:<executable?]@[<descriptor>]> [args] | |
| <dart-file> | |
| A path to a Dart script (e.g., `bin/main.dart`). | |
| <local-package> | |
| The name of a package in the local package resolution. | |
| <executable> | |
| The name of an executable in the package to execute. | |
| For example, `dart run test:test` runs the `test` executable from the `test` package. | |
| If the executable is not specified, the package name is used. | |
| <descriptor> | |
| A YAML formatted string that describes how to locate the | |
| remote package, the same you could use in a pubspec. | |
| For example, to run the latest stable `pubviz` package from pub.dev: | |
| dart run pubviz@ | |
| To specify a version constraint: | |
| dart run pubviz@^4.0.0 | |
| To specify a custom package host: | |
| dart run 'pubviz@{hosted: https://my_repository.com, version: ^1.0.0}' | |
| To run from a git package: | |
| dart run 'pubviz@{git: https://github.com/kevmoo/pubviz}' | |
| See https://dart.dev/to/package-descriptors for more details.''', verbose) { | |
| argParser | |
| ..addOption( | |
| evalOption, | |
| abbr: 'e', | |
| help: 'Evaluate a Dart code snippet.', | |
| valueHelp: 'code', | |
| ) | |
| ..addMultiOption( | |
| packageConstraintOption, | |
| abbr: 'P', | |
| help: | |
| 'Specific constraints for resolution of a single package ' | |
| '(e.g. "http", "path:^1.8.0").\n' | |
| 'See https://dart.dev/to/package-descriptors for more details.', | |
| valueHelp: 'package-spec', | |
| ) | |
| ..addFlag( | |
| offlineOption, | |
| negatable: false, | |
| help: 'Run offline without querying network services.', | |
| ) | |
| ..addFlag( | |
| residentOption, | |
| abbr: 'r', | |
| negatable: false, | |
| help: | |
| 'Enable faster startup times by using a resident frontend ' | |
| 'compiler for compilation.\n' | |
| 'If --$residentCompilerInfoFileOption 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:'); | |
| 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.', | |
| ) | |
| ..addFlag( | |
| 'profile-startup', | |
| hide: !verbose, | |
| negatable: false, | |
| help: | |
| 'Make the profiler discard new samples once the profiler ' | |
| 'sample buffer is full. When this flag is not set, the ' | |
| 'profiler sample buffer is used as a ring buffer, meaning that ' | |
| 'once it is full, new samples start overwriting the oldest ' | |
| 'ones. This flag itself does not enable the profiler; the ' | |
| 'profiler must be enabled separately, e.g. with --profiler.', | |
| ) | |
| ..addSeparator('Logging options:') | |
| ..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, | |
| ) | |
| ..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.', | |
| ) | |
| ..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.', | |
| ) | |
| ..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) | |
| ..addExperimentalFlags(verbose: verbose); | |
| } | |
| @override | |
| String get invocation => | |
| 'dart run [vm-options] <dart-file>|<local-pkg>|<remote-pkg>@<descriptor> <program-args...>'; | |
| @override | |
| CommandCategory get commandCategory => CommandCategory.project; | |
| /// 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 [_CompileToKernelResult] with the compiled | |
| /// kernel file in `goodResult` if compilation succeeds, or the given | |
| /// [CompilationIssue] in `error` otherwise. | |
| static Future<_CompileToKernelResult> _compileToKernelUsingResidentCompiler({ | |
| required DartExecutableWithPackageConfig executable, | |
| required File residentCompilerInfoFile, | |
| required GenerateKernelArguments args, | |
| required bool shouldRetryOnFrontendCompilerException, | |
| String? nativeAssetsYaml, | |
| }) async { | |
| final executableFile = File(executable.executable); | |
| assert( | |
| !await isFileKernelFile(executableFile) && | |
| !await isFileAppJitSnapshot(executableFile) && | |
| !await isFileAotSnapshot(executableFile), | |
| ); | |
| try { | |
| return _CompileToKernelResult.good( | |
| await generateKernel( | |
| executable, | |
| residentCompilerInfoFile, | |
| args, | |
| createCompileJitJson, | |
| nativeAssetsYaml: nativeAssetsYaml, | |
| ), | |
| ); | |
| } on FrontendCompilerException catch (e) { | |
| if (e.issue == CompilationIssue.serverError) { | |
| if (shouldRetryOnFrontendCompilerException) { | |
| log.stdout( | |
| '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, | |
| nativeAssetsYaml: nativeAssetsYaml, | |
| ); | |
| } else { | |
| log.stderr( | |
| 'Error: A connection to the Resident Frontend Compiler could ' | |
| "not be established. Please re-run 'dart run --$residentOption' and a " | |
| 'new compiler will automatically be started in its place.', | |
| ); | |
| await shutDownOrForgetResidentFrontendCompiler( | |
| residentCompilerInfoFile, | |
| ); | |
| return _CompileToKernelResult.bad(e.issue); | |
| } | |
| } else { | |
| log.stderr( | |
| '${ansi.yellow}Failed to build ${executable.executable}:${ansi.none}', | |
| ); | |
| log.stderr(e.message); | |
| return _CompileToKernelResult.bad(e.issue); | |
| } | |
| } | |
| } | |
| @override | |
| FutureOr<int> run() async { | |
| final args = argResults!; | |
| final quiet = | |
| args.option('verbosity') == Verbosity.error.name || | |
| (args[quietOption] ?? false); | |
| return await withDartdevLogger( | |
| () { | |
| if (args.wasParsed(evalOption)) { | |
| return runEval(args); | |
| } | |
| 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(); | |
| } | |
| final atIndex = mainCommand.indexOf('@'); | |
| if (atIndex != -1) { | |
| return _runRemote(args, mainCommand, runArgs); | |
| } | |
| return _runLocal(args, mainCommand, runArgs); | |
| }, | |
| output: quiet ? ProgressOutput.none : ProgressOutput.stderr, | |
| transientProgress: true, | |
| ); | |
| } | |
| /// Evaluates the Dart code snippet provided via the [--eval] option. | |
| Future<int> runEval(ArgResults args) async { | |
| final code = args.option(evalOption); | |
| if (code == null || code.isEmpty) { | |
| log.stderr('Error: Expected a code snippet for --$evalOption.'); | |
| return errorExitCode; | |
| } | |
| final runArgs = args.rest; | |
| final scriptUri = Uri.dataFromString( | |
| code, | |
| mimeType: dartMimeType, | |
| encoding: utf8, | |
| ); | |
| final packageSpecs = <String>[ | |
| if (args.wasParsed(packageConstraintOption)) | |
| ...args.multiOption(packageConstraintOption), | |
| ]; | |
| final offline = args.flag(offlineOption); | |
| final String? packageConfigOverride; | |
| if (args.wasParsed(packageString)) { | |
| packageConfigOverride = args.option(packageString); | |
| } else { | |
| final configAndFile = await findPackageConfigAndFile(Directory.current); | |
| packageConfigOverride = configAndFile?.file.path; | |
| } | |
| final finalConfigPath = await EvalPackageResolver.resolvePackageConfig( | |
| code, | |
| packageConstraints: packageSpecs.isNotEmpty ? packageSpecs : null, | |
| localPackageConfig: packageConfigOverride, | |
| offline: offline, | |
| ); | |
| VmInteropHandler.run( | |
| scriptUri.toString(), | |
| runArgs, | |
| packageConfigOverride: finalConfigPath, | |
| useExecProcess: true, | |
| ); | |
| return 0; | |
| } | |
| FutureOr<int> _runLocal( | |
| ArgResults args, | |
| String mainCommand, | |
| List<String> runArgs, | |
| ) async { | |
| final String? residentCompilerInfoFileArg = | |
| args[CompilationServerCommand.residentCompilerInfoFileFlag] ?? | |
| args[CompilationServerCommand.legacyResidentServerInfoFileFlag]; | |
| final useResidentCompiler = args.wasParsed(residentOption); | |
| if (residentCompilerInfoFileArg != null && !useResidentCompiler) { | |
| log.stderr( | |
| 'Error: the --$residentOption flag must be passed whenever the ' | |
| '--$residentCompilerInfoFileOption option is passed.', | |
| ); | |
| return errorExitCode; | |
| } | |
| if (args.wasParsed(quietOption) && !useResidentCompiler) { | |
| log.stderr( | |
| 'Error: the --$residentOption flag must be passed whenever the ' | |
| '--$quietOption flag is passed.', | |
| ); | |
| return errorExitCode; | |
| } | |
| String? nativeAssets; | |
| DartNativeAssetsBuilder? builder; | |
| Uri baseUri = Directory.current.uri; | |
| if (mainCommand.isNotEmpty) { | |
| final file = File(mainCommand); | |
| if (file.existsSync()) { | |
| baseUri = file.absolute.uri.resolve('.'); | |
| } | |
| } | |
| final packageConfigUri = await DartNativeAssetsBuilder.ensurePackageConfig( | |
| baseUri, | |
| ); | |
| if (packageConfigUri != null) { | |
| final packageConfig = await DartNativeAssetsBuilder.loadPackageConfig( | |
| packageConfigUri, | |
| ); | |
| if (packageConfig == null) { | |
| return compileErrorExitCode; | |
| } | |
| final runPackageName = | |
| getPackageForCommand(mainCommand) ?? | |
| // TODO(https://dartbug.com/63713): Don't use cwd for test. | |
| packageConfig.packageOf(baseUri)?.name; | |
| if (runPackageName != null) { | |
| final pubspecUri = await DartNativeAssetsBuilder.findWorkspacePubspec( | |
| packageConfigUri, | |
| ); | |
| builder = DartNativeAssetsBuilder( | |
| pubspecUri: pubspecUri, | |
| packageConfigUri: packageConfigUri, | |
| packageConfig: packageConfig, | |
| runPackageName: runPackageName, | |
| // Enable accessing assets of dev dependencies in the debugger and | |
| // enabling commands such as `dart run test` and `dart run | |
| // coverage_with_test` that rely on having dev dependencies. | |
| includeDevDependencies: true, | |
| verbose: verbose, | |
| dataAssetsExperimentEnabled: dataAssetsExperimentEnabled, | |
| ); | |
| if (!nativeAssetsExperimentEnabled) { | |
| if (await builder.warnOnNativeAssets()) { | |
| return errorExitCode; | |
| } | |
| } else if (await builder.hasHooks()) { | |
| final assetsYamlFileUri = await progress( | |
| 'Running build hooks', | |
| builder.compileNativeAssetsJitYamlFile, | |
| ); | |
| if (assetsYamlFileUri == null) { | |
| log.stderr('Error: Running build hooks failed.'); | |
| return errorExitCode; | |
| } | |
| nativeAssets = assetsYamlFileUri.toFilePath(); | |
| } | |
| } | |
| } | |
| DartExecutableWithPackageConfig executable; | |
| final String sourceExecutable; | |
| try { | |
| executable = await withProgressGracePeriod( | |
| () => getExecutableForCommand(mainCommand), | |
| progressGracePeriod: progressGracePeriod, | |
| ); | |
| sourceExecutable = executable.executable; | |
| if (!useResidentCompiler) { | |
| executable = await ExecutableCompiler.compile( | |
| resolvedExecutable: executable, | |
| enabledExperiments: enabledExperiments, | |
| verbose: verbose, | |
| ); | |
| } | |
| } on CommandResolutionFailedException catch (e) { | |
| log.stderr(e.message); | |
| return errorExitCode; | |
| } on CompilationException catch (e) { | |
| log.stderr(e.message); | |
| return errorExitCode; | |
| } | |
| if (useResidentCompiler) { | |
| // We need to merge the vm given arguments and the `dart run` given | |
| // arguments because `dart -Dfoo=bar run -Dbar=baz file.dart` will have | |
| // both `foo=bar` and `bar=baz` as defines, so `run -r` should too. | |
| // We filter because we don't want the parser to throw on other things. | |
| var vmArgsResult = argParser.parse( | |
| vmArgs.where( | |
| (s) { | |
| // The arg parser supports "--define foo=bar", but the VM doesn't, | |
| // so filtering like this for the vm arguments is fine. | |
| return s.startsWith('--define=') || | |
| s.startsWith('-D') || | |
| s == '--enable-asserts' || | |
| s == '--no-enable-asserts' || | |
| s.startsWith('--enable-experiment=') || | |
| s.startsWith('--verbosity='); | |
| }, | |
| ), | |
| ); | |
| GenerateKernelArguments generateKernelArguments = | |
| GenerateKernelArguments.fromArgResults([vmArgsResult, args]); | |
| 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)) { | |
| // Nothing to do. | |
| } else if (!await isFileAppJitSnapshot(executableFile) && | |
| !await isFileAotSnapshot(executableFile)) { | |
| final compilationResult = await _compileToKernelUsingResidentCompiler( | |
| executable: executable, | |
| residentCompilerInfoFile: residentCompilerInfoFile, | |
| args: generateKernelArguments, | |
| shouldRetryOnFrontendCompilerException: true, | |
| nativeAssetsYaml: nativeAssets, | |
| ); | |
| if (compilationResult.goodResult != null) { | |
| executable = compilationResult.goodResult!; | |
| } else { | |
| switch (compilationResult.error!) { | |
| case CompilationIssue.serverError: | |
| return dartFrontendErrorExitCode; | |
| case CompilationIssue.serverErrorNoRestart: | |
| return dartFrontendErrorExitCode; | |
| case CompilationIssue.serverCreationError: | |
| return dartFrontendErrorExitCode; | |
| case CompilationIssue.compilationError: | |
| return compilationErrorExitCode; | |
| } | |
| } | |
| } | |
| } else if (nativeAssets != null) { | |
| final executableFile = File(executable.executable); | |
| if (await isFileKernelFile(executableFile)) { | |
| final concatenated = await concatenateNativeAssetsKernel( | |
| kernelFilePath: executable.executable, | |
| nativeAssetsYamlUri: Uri.file(nativeAssets), | |
| tempDirUri: builder!.tempDirUri!, | |
| ); | |
| if (concatenated == null) { | |
| return errorExitCode; | |
| } | |
| executable = DartExecutableWithPackageConfig( | |
| executable: concatenated, | |
| packageConfig: executable.packageConfig, | |
| ); | |
| } | |
| } | |
| VmInteropHandler.run( | |
| executable.executable, | |
| runArgs, | |
| packageConfigOverride: | |
| args.option('packages') ?? executable.packageConfig, | |
| useExecProcess: true, | |
| scriptUriOverride: executable.executable != sourceExecutable | |
| ? sourceExecutable | |
| : null, | |
| deleteTempDirOnShutdown: builder?.tempDirUri?.toFilePath(), | |
| ); | |
| return 0; | |
| } | |
| /// Installs (if needed) and runs the remote executable. | |
| /// | |
| /// Installs the app bundle at the same location as `dart install` but does | |
| /// not symlink the executable. | |
| Future<int> _runRemote( | |
| ArgResults args, | |
| String mainCommand, | |
| List<String> runArgs, | |
| ) async { | |
| for (final option in args.options) { | |
| if (args.wasParsed(option) && option != verbosityOption) { | |
| usageException( | |
| 'Option --$option cannot be used in remote runs. ' | |
| '`dart run <remote-executable>` uses `dart install` under the hood ' | |
| 'and compiles the app into a standalone executable.', | |
| ); | |
| } | |
| } | |
| final atIndex = mainCommand.indexOf('@'); | |
| assert(atIndex != -1); | |
| final command = mainCommand.substring(0, atIndex); | |
| final descriptorString = mainCommand.substring(atIndex + 1); | |
| final Object? descriptor; | |
| try { | |
| descriptor = loadYaml(descriptorString); | |
| } on FormatException catch (e) { | |
| usageException( | |
| 'Failed to parse remote executable descriptor "$descriptorString": $e', | |
| ); | |
| } | |
| final colonIndex = command.indexOf(':'); | |
| final String packageName; | |
| final String executable; | |
| if (colonIndex == -1) { | |
| packageName = command; | |
| executable = command; | |
| } else { | |
| packageName = command.substring(0, colonIndex); | |
| executable = command.substring(colonIndex + 1); | |
| } | |
| return await InstallCommand.inTempDir((tempDirectory) async { | |
| try { | |
| // Create a helper package for running a pub-resolve and pulling in the | |
| // wanted package and its dependencies. | |
| final helperPackageDirectory = Directory.fromUri( | |
| tempDirectory.uri.resolve('helperPackage/'), | |
| ); | |
| helperPackageDirectory.createSync(); | |
| InstallCommand.createHelperPackagePubspec( | |
| helperPackageDir: helperPackageDirectory, | |
| packageName: packageName, | |
| parsedArgs: DescriptorInstallCommandParsedArguments( | |
| packageName: packageName, | |
| descriptor: descriptor, | |
| overwrite: false, | |
| ), | |
| ); | |
| await InstallCommand.resolveHelperPackage(helperPackageDirectory); | |
| final helperPackageLockFile = File.fromUri( | |
| helperPackageDirectory.uri.resolve('pubspec.lock'), | |
| ); | |
| final appBundleDirectory = InstallCommand.selectAppBundleDirectory( | |
| packageName, | |
| helperPackageDirectory, | |
| helperPackageLockFile, | |
| ); | |
| // If the pubspec lock file changed, re-build the executable. | |
| if (!appBundleDirectory.pubspecLockIsIdenticalTo( | |
| helperPackageLockFile, | |
| )) { | |
| final helperPackageConfigFile = File.fromUri( | |
| helperPackageDirectory.uri.resolve( | |
| '.dart_tool/package_config.json', | |
| ), | |
| ); | |
| final sourcePackageRootDirectory = Directory( | |
| Uri.parse( | |
| PackageConfigFile.loadSync( | |
| helperPackageConfigFile, | |
| ).packages.firstWhere((e) => e.name == packageName).rootUri, | |
| ).toFilePath(), | |
| ).ensureEndWithSeparator; | |
| final sourcePackagePubspecFile = File.fromUri( | |
| sourcePackageRootDirectory.uri.resolve('pubspec.yaml'), | |
| ); | |
| final executables = InstallCommand.loadDeclaredExecutables( | |
| sourcePackagePubspecFile, | |
| sourcePackageRootDirectory, | |
| ); | |
| final buildDirectory = Directory.fromUri( | |
| tempDirectory.uri.resolve('build/'), | |
| ); | |
| final verbosity = args.option('verbosity')!; | |
| await InstallCommand.doBuild( | |
| executables, | |
| buildDirectory, | |
| helperPackageConfigFile, | |
| sourcePackagePubspecFile, | |
| verbose, | |
| verbosity, | |
| logGenerated: verbose, | |
| ); | |
| await InstallCommand.createAppBundleDirectory( | |
| appBundleDirectory, | |
| buildDirectory, | |
| helperPackageLockFile, | |
| sourcePackagePubspecFile, | |
| ); | |
| } | |
| final executableFile = appBundleDirectory.executable(executable).file; | |
| final arguments = args.rest.skip(1).toList(); | |
| // The app-bundle contains executables (not AOT snapshots) to make it | |
| // self-contained. So, spawn a process instead of loading a snapshot in | |
| // the VM. | |
| final process = await Process.start( | |
| executableFile.path, | |
| arguments, | |
| mode: ProcessStartMode.inheritStdio, // Enable using stdin etc. | |
| environment: VmInteropHandler.environmentOverrides, | |
| ); | |
| return await process.exitCode; | |
| } on InstallException catch (e) { | |
| log.stderr(e.message); | |
| return genericErrorExitCode; | |
| } | |
| }); | |
| } | |
| } | |
| /// Keep in sync with [getExecutableForCommand]. | |
| /// | |
| /// Returns `null` if the 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 (_) { | |
| // Follow the same fallback 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; | |
| } | |
| final descriptorUri = Uri.tryParse(descriptor); | |
| if (descriptorUri != null && descriptorUri.isPackage) { | |
| return descriptorUri.packageName; | |
| } | |
| final String package; | |
| if (descriptor.contains(':')) { | |
| final parts = descriptor.split(':'); | |
| if (parts.length > 2) { | |
| return null; | |
| } | |
| package = parts[0]; | |
| } else { | |
| package = descriptor; | |
| } | |
| if (package.isEmpty || package == 'test') { | |
| // Empty package or `dart run test` is expected to behave as `dart test`. | |
| return null; // Root package. | |
| } | |
| return package; | |
| } | |
| class _CompileToKernelResult { | |
| final DartExecutableWithPackageConfig? goodResult; | |
| final CompilationIssue? error; | |
| _CompileToKernelResult.good(DartExecutableWithPackageConfig this.goodResult) | |
| : error = null; | |
| _CompileToKernelResult.bad(CompilationIssue this.error) : goodResult = null; | |
| } |