| // 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:io'; | |
| /// Stores the result of preprocessing `dartdevc` command line arguments. | |
| /// | |
| /// `dartdevc` preprocesses arguments to support some features that | |
| /// `package:args` does not handle (training `@` to reference arguments in a | |
| /// file). | |
| /// | |
| /// [isBatch]/[isWorker] mode are preprocessed because they can combine | |
| /// argument lists from the initial invocation and from batch/worker jobs. | |
| class ParsedArguments { | |
| /// The user's arguments to the compiler for this compilation. | |
| final List<String> rest; | |
| /// Whether to run in `--batch` mode, e.g the Dart SDK and Language tests. | |
| /// | |
| /// Similar to [isWorker] but with a different protocol. | |
| /// See also [isBatchOrWorker]. | |
| final bool isBatch; | |
| /// Whether to run in `--experimental-expression-compiler` mode. | |
| /// | |
| /// This is a special mode that is optimized for only compiling expressions. | |
| /// | |
| /// All dependencies must come from precompiled dill files, and those must | |
| /// be explicitly invalidated as needed between expression compile requests. | |
| /// Invalidation of dill is performed using [updateDeps] from the client (i.e. | |
| /// debugger) and should be done every time a dill file changes, for example, | |
| /// on hot reload or rebuild. | |
| final bool isExpressionCompiler; | |
| /// Whether to run in `--bazel_worker` mode, e.g. for Bazel builds. | |
| /// | |
| /// Similar to [isBatch] but with a different protocol. | |
| /// See also [isBatchOrWorker]. | |
| final bool isWorker; | |
| /// Whether to re-use the last compiler result when in a worker. | |
| /// | |
| /// This is useful if we are repeatedly compiling things in the same context, | |
| /// e.g. in a debugger REPL. | |
| final bool reuseResult; | |
| /// Whether to use the incremental compiler for compiling. | |
| /// | |
| /// Note that this only makes sense when also reusing results. | |
| final bool useIncrementalCompiler; | |
| ParsedArguments._( | |
| this.rest, { | |
| this.isBatch = false, | |
| this.isWorker = false, | |
| this.reuseResult = false, | |
| this.useIncrementalCompiler = false, | |
| this.isExpressionCompiler = false, | |
| }); | |
| /// Preprocess arguments to determine whether DDK is used in batch mode or as | |
| /// a persistent worker. | |
| /// | |
| /// When used in batch mode, we expect a `--batch` parameter. | |
| /// | |
| /// When used as a persistent bazel worker, the `--persistent_worker` might be | |
| /// present, and an argument of the form `@path/to/file` might be provided. | |
| /// The `@latter needs to be replaced by reading all the contents of the file | |
| /// and expanding them into the resulting argument list. | |
| factory ParsedArguments.from(List<String> args) { | |
| if (args.isEmpty) return ParsedArguments._(args); | |
| var newArgs = <String>[]; | |
| var isWorker = false; | |
| var isBatch = false; | |
| var reuseResult = false; | |
| var useIncrementalCompiler = false; | |
| var isExpressionCompiler = false; | |
| Iterable<String> argsToParse = args; | |
| // Expand `@path/to/file` | |
| if (args.last.startsWith('@')) { | |
| var extra = _readLines(args.last.substring(1)); | |
| argsToParse = args.take(args.length - 1).followedBy(extra); | |
| } | |
| for (var arg in argsToParse) { | |
| if (arg == '--persistent_worker') { | |
| isWorker = true; | |
| } else if (arg == '--batch') { | |
| isBatch = true; | |
| } else if (arg == '--reuse-compiler-result') { | |
| reuseResult = true; | |
| } else if (arg == '--use-incremental-compiler') { | |
| useIncrementalCompiler = true; | |
| } else if (arg == '--experimental-expression-compiler') { | |
| isExpressionCompiler = true; | |
| } else { | |
| newArgs.add(arg); | |
| } | |
| } | |
| return ParsedArguments._( | |
| newArgs, | |
| isWorker: isWorker, | |
| isBatch: isBatch, | |
| reuseResult: reuseResult, | |
| useIncrementalCompiler: useIncrementalCompiler, | |
| isExpressionCompiler: isExpressionCompiler, | |
| ); | |
| } | |
| /// Whether the compiler is running in [isBatch] or [isWorker] mode. | |
| /// | |
| /// Both modes are generally equivalent from the compiler's perspective, | |
| /// the main difference is that they use distinct protocols to communicate | |
| /// jobs to the compiler. | |
| bool get isBatchOrWorker => isBatch || isWorker; | |
| /// Merge [args] and return the new parsed arguments. | |
| /// | |
| /// Typically used when [isBatchOrWorker] is set to merge the compilation's | |
| /// arguments with any global ones that were provided when the worker started. | |
| ParsedArguments merge(List<String> arguments) { | |
| // Parse the arguments again so `--kernel` can be passed. This provides | |
| // added safety that we are really compiling in Kernel mode, if somehow the | |
| // worker was not initialized correctly. | |
| var newArgs = ParsedArguments.from(arguments); | |
| if (newArgs.isBatchOrWorker) { | |
| throw ArgumentError('cannot change batch or worker mode after startup.'); | |
| } | |
| return ParsedArguments._( | |
| rest.toList()..addAll(newArgs.rest), | |
| isWorker: isWorker, | |
| isBatch: isBatch, | |
| reuseResult: reuseResult || newArgs.reuseResult, | |
| useIncrementalCompiler: | |
| useIncrementalCompiler || newArgs.useIncrementalCompiler, | |
| ); | |
| } | |
| } | |
| /// Return all lines in a file found at [path]. | |
| Iterable<String> _readLines(String path) { | |
| try { | |
| return File(path).readAsLinesSync().where((String line) => line.isNotEmpty); | |
| } on FileSystemException catch (e) { | |
| throw Exception('Failed to read $path: $e'); | |
| } | |
| } |