blob: a9678502da843437f85b949d9d897706d0669f9d [file] [log] [blame] [edit]
// 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');
}
}