flutter/packages/flutter_tools/lib/src/compile.dart
Tess Strickland 840d7dd68a
Use --target-os for appropriate precompiled targets. (#127567)
This PR adds uses of the `--target-os` command line argument when
building kernel sources for precompiled applications for supported
target operating systems. The Dart CFE then:

* treats `Platform.operatingSystem` as if it were defined as the
constant string provided as an argument to the flag,
* treats `Platform.pathSeparator` as the appropriate separator for that
operating system,
* attempts to constant evaluate the initializer for any field annotated
with the `vm:platform-const` pragma, and
* attempts to constant evaluate all calls to a method annotated with the
`vm:platform-const` pragma.

The `vm:platform-const` pragma can appear in either library or user
code. If the attempt to constant evaluate the field initializer or
method call fails, then an error is thrown at kernel compilation time.

Addresses #14233.

The tests in
`packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart`
have been adjusted properly to account for the new passed command line
arguments.
2023-06-08 13:17:18 +02:00

1024 lines
33 KiB
Dart

// Copyright 2014 The Flutter Authors. 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:typed_data';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:process/process.dart';
import 'package:usage/uuid/uuid.dart';
import 'artifacts.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/platform.dart';
import 'build_info.dart';
import 'convert.dart';
/// Opt-in changes to the dart compilers.
const List<String> kDartCompilerExperiments = <String>[
];
/// The target model describes the set of core libraries that are available within
/// the SDK.
class TargetModel {
/// Parse a [TargetModel] from a raw string.
///
/// Throws an exception if passed a value other than 'flutter',
/// 'flutter_runner', 'vm', or 'dartdevc'.
factory TargetModel(String rawValue) {
switch (rawValue) {
case 'flutter':
return flutter;
case 'flutter_runner':
return flutterRunner;
case 'vm':
return vm;
case 'dartdevc':
return dartdevc;
}
throw Exception('Unexpected target model $rawValue');
}
const TargetModel._(this._value);
/// The Flutter patched Dart SDK.
static const TargetModel flutter = TargetModel._('flutter');
/// The Fuchsia patched SDK.
static const TargetModel flutterRunner = TargetModel._('flutter_runner');
/// The Dart VM.
static const TargetModel vm = TargetModel._('vm');
/// The development compiler for JavaScript.
static const TargetModel dartdevc = TargetModel._('dartdevc');
final String _value;
@override
String toString() => _value;
}
class CompilerOutput {
const CompilerOutput(this.outputFilename, this.errorCount, this.sources, {this.expressionData});
final String outputFilename;
final int errorCount;
final List<Uri> sources;
/// This field is only non-null for expression compilation requests.
final Uint8List? expressionData;
}
enum StdoutState { CollectDiagnostic, CollectDependencies }
/// Handles stdin/stdout communication with the frontend server.
class StdoutHandler {
StdoutHandler({
required Logger logger,
required FileSystem fileSystem,
}) : _logger = logger,
_fileSystem = fileSystem {
reset();
}
final Logger _logger;
final FileSystem _fileSystem;
String? boundaryKey;
StdoutState state = StdoutState.CollectDiagnostic;
Completer<CompilerOutput?>? compilerOutput;
final List<Uri> sources = <Uri>[];
bool _suppressCompilerMessages = false;
bool _expectSources = true;
bool _readFile = false;
void handler(String message) {
const String kResultPrefix = 'result ';
if (boundaryKey == null && message.startsWith(kResultPrefix)) {
boundaryKey = message.substring(kResultPrefix.length);
return;
}
final String? messageBoundaryKey = boundaryKey;
if (messageBoundaryKey != null && message.startsWith(messageBoundaryKey)) {
if (_expectSources) {
if (state == StdoutState.CollectDiagnostic) {
state = StdoutState.CollectDependencies;
return;
}
}
if (message.length <= messageBoundaryKey.length) {
compilerOutput?.complete();
return;
}
final int spaceDelimiter = message.lastIndexOf(' ');
final String fileName = message.substring(messageBoundaryKey.length + 1, spaceDelimiter);
final int errorCount = int.parse(message.substring(spaceDelimiter + 1).trim());
Uint8List? expressionData;
if (_readFile) {
expressionData = _fileSystem.file(fileName).readAsBytesSync();
}
final CompilerOutput output = CompilerOutput(
fileName,
errorCount,
sources,
expressionData: expressionData,
);
compilerOutput?.complete(output);
return;
}
if (state == StdoutState.CollectDiagnostic) {
if (!_suppressCompilerMessages) {
_logger.printError(message);
} else {
_logger.printTrace(message);
}
} else {
assert(state == StdoutState.CollectDependencies);
switch (message[0]) {
case '+':
sources.add(Uri.parse(message.substring(1)));
case '-':
sources.remove(Uri.parse(message.substring(1)));
default:
_logger.printTrace('Unexpected prefix for $message uri - ignoring');
}
}
}
// This is needed to get ready to process next compilation result output,
// with its own boundary key and new completer.
void reset({ bool suppressCompilerMessages = false, bool expectSources = true, bool readFile = false }) {
boundaryKey = null;
compilerOutput = Completer<CompilerOutput?>();
_suppressCompilerMessages = suppressCompilerMessages;
_expectSources = expectSources;
_readFile = readFile;
state = StdoutState.CollectDiagnostic;
}
}
/// List the preconfigured build options for a given build mode.
List<String> buildModeOptions(BuildMode mode, List<String> dartDefines) =>
switch (mode) {
BuildMode.debug => <String>[
// These checks allow the CLI to override the value of this define for unit
// testing the framework.
if (!dartDefines.any((String define) => define.startsWith('dart.vm.profile')))
'-Ddart.vm.profile=false',
if (!dartDefines.any((String define) => define.startsWith('dart.vm.product')))
'-Ddart.vm.product=false',
'--enable-asserts',
],
BuildMode.profile => <String>[
// These checks allow the CLI to override the value of this define for
// benchmarks with most timeline traces disabled.
if (!dartDefines.any((String define) => define.startsWith('dart.vm.profile')))
'-Ddart.vm.profile=true',
if (!dartDefines.any((String define) => define.startsWith('dart.vm.product')))
'-Ddart.vm.product=false',
...kDartCompilerExperiments,
],
BuildMode.release => <String>[
'-Ddart.vm.profile=false',
'-Ddart.vm.product=true',
...kDartCompilerExperiments,
],
_ => throw Exception('Unknown BuildMode: $mode')
};
/// A compiler interface for producing single (non-incremental) kernel files.
class KernelCompiler {
KernelCompiler({
required FileSystem fileSystem,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required List<String> fileSystemRoots,
String? fileSystemScheme,
@visibleForTesting StdoutHandler? stdoutHandler,
}) : _logger = logger,
_fileSystem = fileSystem,
_artifacts = artifacts,
_processManager = processManager,
_fileSystemScheme = fileSystemScheme,
_fileSystemRoots = fileSystemRoots,
_stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem);
final FileSystem _fileSystem;
final Artifacts _artifacts;
final ProcessManager _processManager;
final Logger _logger;
final String? _fileSystemScheme;
final List<String> _fileSystemRoots;
final StdoutHandler _stdoutHandler;
Future<CompilerOutput?> compile({
required String sdkRoot,
String? mainPath,
String? outputFilePath,
String? depFilePath,
TargetModel targetModel = TargetModel.flutter,
bool linkPlatformKernelIn = false,
bool aot = false,
List<String>? extraFrontEndOptions,
List<String>? fileSystemRoots,
String? fileSystemScheme,
String? initializeFromDill,
String? platformDill,
Directory? buildDir,
String? targetOS,
bool checkDartPluginRegistry = false,
required String? packagesPath,
required BuildMode buildMode,
required bool trackWidgetCreation,
required List<String> dartDefines,
required PackageConfig packageConfig,
}) async {
final TargetPlatform? platform = targetModel == TargetModel.dartdevc ? TargetPlatform.web_javascript : null;
final String frontendServer = _artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk,
platform: platform,
);
// This is a URI, not a file path, so the forward slash is correct even on Windows.
if (!sdkRoot.endsWith('/')) {
sdkRoot = '$sdkRoot/';
}
final String engineDartPath = _artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform);
if (!_processManager.canRun(engineDartPath)) {
throwToolExit('Unable to find Dart binary at $engineDartPath');
}
String? mainUri;
final File mainFile = _fileSystem.file(mainPath);
final Uri mainFileUri = mainFile.uri;
if (packagesPath != null) {
mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
}
mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
_fileSystem.file(outputFilePath).createSync(recursive: true);
}
// Check if there's a Dart plugin registrant.
// This is contained in the file `dart_plugin_registrant.dart` under `.dart_tools/flutter_build/`.
final File? dartPluginRegistrant = checkDartPluginRegistry
? buildDir?.parent.childFile('dart_plugin_registrant.dart')
: null;
String? dartPluginRegistrantUri;
if (dartPluginRegistrant != null && dartPluginRegistrant.existsSync()) {
final Uri dartPluginRegistrantFileUri = dartPluginRegistrant.uri;
dartPluginRegistrantUri = packageConfig.toPackageUri(dartPluginRegistrantFileUri)?.toString() ??
toMultiRootPath(dartPluginRegistrantFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
}
final List<String> command = <String>[
engineDartPath,
'--disable-dart-dev',
frontendServer,
'--sdk-root',
sdkRoot,
'--target=$targetModel',
'--no-print-incremental-dependencies',
for (final Object dartDefine in dartDefines)
'-D$dartDefine',
...buildModeOptions(buildMode, dartDefines),
if (trackWidgetCreation) '--track-widget-creation',
if (!linkPlatformKernelIn) '--no-link-platform',
if (aot) ...<String>[
'--aot',
'--tfa',
// The --target-os flag only makes sense for whole program compilation.
if (targetOS != null) ...<String>[
'--target-os',
targetOS,
],
],
if (packagesPath != null) ...<String>[
'--packages',
packagesPath,
],
if (outputFilePath != null) ...<String>[
'--output-dill',
outputFilePath,
],
if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) ...<String>[
'--depfile',
depFilePath,
],
if (fileSystemRoots != null)
for (final String root in fileSystemRoots) ...<String>[
'--filesystem-root',
root,
],
if (fileSystemScheme != null) ...<String>[
'--filesystem-scheme',
fileSystemScheme,
],
if (initializeFromDill != null) ...<String>[
'--incremental',
'--initialize-from-dill',
initializeFromDill,
],
if (platformDill != null) ...<String>[
'--platform',
platformDill,
],
if (dartPluginRegistrantUri != null) ...<String>[
'--source',
dartPluginRegistrantUri,
'--source',
'package:flutter/src/dart_plugin_registrant.dart',
'-Dflutter.dart_plugin_registrant=$dartPluginRegistrantUri',
],
// See: https://github.com/flutter/flutter/issues/103994
'--verbosity=error',
...?extraFrontEndOptions,
mainUri,
];
_logger.printTrace(command.join(' '));
final Process server = await _processManager.start(command);
server.stderr
.transform<String>(utf8.decoder)
.listen(_logger.printError);
server.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(_stdoutHandler.handler);
final int exitCode = await server.exitCode;
if (exitCode == 0) {
return _stdoutHandler.compilerOutput?.future;
}
return null;
}
}
/// Class that allows to serialize compilation requests to the compiler.
abstract class _CompilationRequest {
_CompilationRequest(this.completer);
Completer<CompilerOutput?> completer;
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler);
Future<void> run(DefaultResidentCompiler compiler) async {
completer.complete(await _run(compiler));
}
}
class _RecompileRequest extends _CompilationRequest {
_RecompileRequest(
super.completer,
this.mainUri,
this.invalidatedFiles,
this.outputPath,
this.packageConfig,
this.suppressErrors,
{this.additionalSourceUri}
);
Uri mainUri;
List<Uri>? invalidatedFiles;
String outputPath;
PackageConfig packageConfig;
bool suppressErrors;
final Uri? additionalSourceUri;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._recompile(this);
}
class _CompileExpressionRequest extends _CompilationRequest {
_CompileExpressionRequest(
super.completer,
this.expression,
this.definitions,
this.definitionTypes,
this.typeDefinitions,
this.typeBounds,
this.typeDefaults,
this.libraryUri,
this.klass,
this.method,
this.isStatic,
);
String expression;
List<String>? definitions;
List<String>? definitionTypes;
List<String>? typeDefinitions;
List<String>? typeBounds;
List<String>? typeDefaults;
String? libraryUri;
String? klass;
String? method;
bool isStatic;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._compileExpression(this);
}
class _CompileExpressionToJsRequest extends _CompilationRequest {
_CompileExpressionToJsRequest(
super.completer,
this.libraryUri,
this.line,
this.column,
this.jsModules,
this.jsFrameValues,
this.moduleName,
this.expression,
);
final String? libraryUri;
final int line;
final int column;
final Map<String, String>? jsModules;
final Map<String, String>? jsFrameValues;
final String? moduleName;
final String? expression;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._compileExpressionToJs(this);
}
class _RejectRequest extends _CompilationRequest {
_RejectRequest(super.completer);
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
compiler._reject();
}
/// Wrapper around incremental frontend server compiler, that communicates with
/// server via stdin/stdout.
///
/// The wrapper is intended to stay resident in memory as user changes, reloads,
/// restarts the Flutter app.
abstract class ResidentCompiler {
factory ResidentCompiler(String sdkRoot, {
required BuildMode buildMode,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required Platform platform,
required FileSystem fileSystem,
bool testCompilation,
bool trackWidgetCreation,
String packagesPath,
List<String> fileSystemRoots,
String? fileSystemScheme,
String initializeFromDill,
bool assumeInitializeFromDillUpToDate,
TargetModel targetModel,
bool unsafePackageSerialization,
List<String> extraFrontEndOptions,
String platformDill,
List<String>? dartDefines,
String librariesSpec,
}) = DefaultResidentCompiler;
// TODO(zanderso): find a better way to configure additional file system
// roots from the runner.
// See: https://github.com/flutter/flutter/issues/50494
void addFileSystemRoot(String root);
/// If invoked for the first time, it compiles Dart script identified by
/// [mainPath], [invalidatedFiles] list is ignored.
/// On successive runs [invalidatedFiles] indicates which files need to be
/// recompiled. If [mainPath] is [null], previously used [mainPath] entry
/// point that is used for recompilation.
/// Binary file name is returned if compilation was successful, otherwise
/// null is returned.
///
/// If [checkDartPluginRegistry] is true, it is the caller's responsibility
/// to ensure that the generated registrant file has been updated such that
/// it is wrapping [mainUri].
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? invalidatedFiles, {
required String outputPath,
required PackageConfig packageConfig,
required FileSystem fs,
String? projectRootPath,
bool suppressErrors = false,
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
});
Future<CompilerOutput?> compileExpression(
String expression,
List<String>? definitions,
List<String>? definitionTypes,
List<String>? typeDefinitions,
List<String>? typeBounds,
List<String>? typeDefaults,
String? libraryUri,
String? klass,
String? method,
bool isStatic,
);
/// Compiles [expression] in [libraryUri] at [line]:[column] to JavaScript
/// in [moduleName].
///
/// Values listed in [jsFrameValues] are substituted for their names in the
/// [expression].
///
/// Ensures that all [jsModules] are loaded and accessible inside the
/// expression.
///
/// Example values of parameters:
/// [moduleName] is of the form '/packages/hello_world_main.dart'
/// [jsFrameValues] is a map from js variable name to its primitive value
/// or another variable name, for example
/// { 'x': '1', 'y': 'y', 'o': 'null' }
/// [jsModules] is a map from variable name to the module name, where
/// variable name is the name originally used in JavaScript to contain the
/// module object, for example:
/// { 'dart':'dart_sdk', 'main': '/packages/hello_world_main.dart' }
/// Returns a [CompilerOutput] including the name of the file containing the
/// compilation result and a number of errors.
Future<CompilerOutput?> compileExpressionToJs(
String libraryUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression,
);
/// Should be invoked when results of compilation are accepted by the client.
///
/// Either [accept] or [reject] should be called after every [recompile] call.
void accept();
/// Should be invoked when results of compilation are rejected by the client.
///
/// Either [accept] or [reject] should be called after every [recompile] call.
Future<CompilerOutput?> reject();
/// Should be invoked when frontend server compiler should forget what was
/// accepted previously so that next call to [recompile] produces complete
/// kernel file.
void reset();
Future<Object> shutdown();
}
@visibleForTesting
class DefaultResidentCompiler implements ResidentCompiler {
DefaultResidentCompiler(
String sdkRoot, {
required this.buildMode,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required Platform platform,
required FileSystem fileSystem,
this.testCompilation = false,
this.trackWidgetCreation = true,
this.packagesPath,
List<String> fileSystemRoots = const <String>[],
this.fileSystemScheme,
this.initializeFromDill,
this.assumeInitializeFromDillUpToDate = false,
this.targetModel = TargetModel.flutter,
this.unsafePackageSerialization = false,
this.extraFrontEndOptions,
this.platformDill,
List<String>? dartDefines,
this.librariesSpec,
@visibleForTesting StdoutHandler? stdoutHandler,
}) : _logger = logger,
_processManager = processManager,
_artifacts = artifacts,
_stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem),
_platform = platform,
dartDefines = dartDefines ?? const <String>[],
// This is a URI, not a file path, so the forward slash is correct even on Windows.
sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/',
// Make a copy, we might need to modify it later.
fileSystemRoots = List<String>.from(fileSystemRoots);
final Logger _logger;
final ProcessManager _processManager;
final Artifacts _artifacts;
final Platform _platform;
final bool testCompilation;
final BuildMode buildMode;
final bool trackWidgetCreation;
final String? packagesPath;
final TargetModel targetModel;
final List<String> fileSystemRoots;
final String? fileSystemScheme;
final String? initializeFromDill;
final bool assumeInitializeFromDillUpToDate;
final bool unsafePackageSerialization;
final List<String>? extraFrontEndOptions;
final List<String> dartDefines;
final String? librariesSpec;
@override
void addFileSystemRoot(String root) {
fileSystemRoots.add(root);
}
/// The path to the root of the Dart SDK used to compile.
///
/// This is used to resolve the [platformDill].
final String sdkRoot;
/// The path to the platform dill file.
///
/// This does not need to be provided for the normal Flutter workflow.
final String? platformDill;
Process? _server;
final StdoutHandler _stdoutHandler;
bool _compileRequestNeedsConfirmation = false;
final StreamController<_CompilationRequest> _controller = StreamController<_CompilationRequest>();
@override
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? invalidatedFiles, {
required String outputPath,
required PackageConfig packageConfig,
bool suppressErrors = false,
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
String? projectRootPath,
FileSystem? fs,
}) async {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
Uri? additionalSourceUri;
// `dart_plugin_registrant.dart` contains the Dart plugin registry.
if (checkDartPluginRegistry && dartPluginRegistrant != null && dartPluginRegistrant.existsSync()) {
additionalSourceUri = dartPluginRegistrant.uri;
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(_RecompileRequest(
completer,
mainUri,
invalidatedFiles,
outputPath,
packageConfig,
suppressErrors,
additionalSourceUri: additionalSourceUri,
));
return completer.future;
}
Future<CompilerOutput?> _recompile(_RecompileRequest request) async {
_stdoutHandler.reset();
_compileRequestNeedsConfirmation = true;
_stdoutHandler._suppressCompilerMessages = request.suppressErrors;
final String mainUri = request.packageConfig.toPackageUri(request.mainUri)?.toString() ??
toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
String? additionalSourceUri;
if (request.additionalSourceUri != null) {
additionalSourceUri = request.packageConfig.toPackageUri(request.additionalSourceUri!)?.toString() ??
toMultiRootPath(request.additionalSourceUri!, fileSystemScheme, fileSystemRoots, _platform.isWindows);
}
final Process? server = _server;
if (server == null) {
return _compile(mainUri, request.outputPath, additionalSourceUri: additionalSourceUri);
}
final String inputKey = Uuid().generateV4();
server.stdin.writeln('recompile $mainUri $inputKey');
_logger.printTrace('<- recompile $mainUri $inputKey');
final List<Uri>? invalidatedFiles = request.invalidatedFiles;
if (invalidatedFiles != null) {
for (final Uri fileUri in invalidatedFiles) {
String message;
if (fileUri.scheme == 'package') {
message = fileUri.toString();
} else {
message = request.packageConfig.toPackageUri(fileUri)?.toString() ??
toMultiRootPath(fileUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
}
server.stdin.writeln(message);
_logger.printTrace(message);
}
}
server.stdin.writeln(inputKey);
_logger.printTrace('<- $inputKey');
return _stdoutHandler.compilerOutput?.future;
}
final List<_CompilationRequest> _compilationQueue = <_CompilationRequest>[];
Future<void> _handleCompilationRequest(_CompilationRequest request) async {
final bool isEmpty = _compilationQueue.isEmpty;
_compilationQueue.add(request);
// Only trigger processing if queue was empty - i.e. no other requests
// are currently being processed. This effectively enforces "one
// compilation request at a time".
if (isEmpty) {
while (_compilationQueue.isNotEmpty) {
final _CompilationRequest request = _compilationQueue.first;
await request.run(this);
_compilationQueue.removeAt(0);
}
}
}
Future<CompilerOutput?> _compile(
String scriptUri,
String? outputPath,
{String? additionalSourceUri}
) async {
final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) ? TargetPlatform.web_javascript : null;
final String frontendServer = _artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk,
platform: platform,
);
final List<String> command = <String>[
_artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform),
'--disable-dart-dev',
frontendServer,
'--sdk-root',
sdkRoot,
'--incremental',
if (testCompilation)
'--no-print-incremental-dependencies',
'--target=$targetModel',
// TODO(annagrin): remove once this becomes the default behavior
// in the frontend_server.
// https://github.com/flutter/flutter/issues/59902
'--experimental-emit-debug-metadata',
for (final Object dartDefine in dartDefines)
'-D$dartDefine',
if (outputPath != null) ...<String>[
'--output-dill',
outputPath,
],
// If we have a platform dill, we don't need to pass the libraries spec,
// since the information is embedded in the .dill file.
if (librariesSpec != null && platformDill == null) ...<String>[
'--libraries-spec',
librariesSpec!,
],
if (packagesPath != null) ...<String>[
'--packages',
packagesPath!,
],
...buildModeOptions(buildMode, dartDefines),
if (trackWidgetCreation) '--track-widget-creation',
for (final String root in fileSystemRoots) ...<String>[
'--filesystem-root',
root,
],
if (fileSystemScheme != null) ...<String>[
'--filesystem-scheme',
fileSystemScheme!,
],
if (initializeFromDill != null) ...<String>[
'--initialize-from-dill',
initializeFromDill!,
],
if (assumeInitializeFromDillUpToDate) '--assume-initialize-from-dill-up-to-date',
if (additionalSourceUri != null) ...<String>[
'--source',
additionalSourceUri,
'--source',
'package:flutter/src/dart_plugin_registrant.dart',
'-Dflutter.dart_plugin_registrant=$additionalSourceUri',
],
if (platformDill != null) ...<String>[
'--platform',
platformDill!,
],
if (unsafePackageSerialization) '--unsafe-package-serialization',
// See: https://github.com/flutter/flutter/issues/103994
'--verbosity=error',
...?extraFrontEndOptions,
];
_logger.printTrace(command.join(' '));
_server = await _processManager.start(command);
_server?.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(
_stdoutHandler.handler,
onDone: () {
// when outputFilename future is not completed, but stdout is closed
// process has died unexpectedly.
if (_stdoutHandler.compilerOutput?.isCompleted == false) {
_stdoutHandler.compilerOutput?.complete();
throwToolExit('the Dart compiler exited unexpectedly.');
}
});
_server?.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen(_logger.printError);
unawaited(_server?.exitCode.then((int code) {
if (code != 0) {
throwToolExit('the Dart compiler exited unexpectedly.');
}
}));
_server?.stdin.writeln('compile $scriptUri');
_logger.printTrace('<- compile $scriptUri');
return _stdoutHandler.compilerOutput?.future;
}
@override
Future<CompilerOutput?> compileExpression(
String expression,
List<String>? definitions,
List<String>? definitionTypes,
List<String>? typeDefinitions,
List<String>? typeBounds,
List<String>? typeDefaults,
String? libraryUri,
String? klass,
String? method,
bool isStatic,
) async {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
final _CompileExpressionRequest request = _CompileExpressionRequest(
completer, expression, definitions, definitionTypes, typeDefinitions,
typeBounds, typeDefaults, libraryUri, klass, method, isStatic);
_controller.add(request);
return completer.future;
}
Future<CompilerOutput?> _compileExpression(_CompileExpressionRequest request) async {
_stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false, readFile: true);
// 'compile-expression' should be invoked after compiler has been started,
// program was compiled.
final Process? server = _server;
if (server == null) {
return null;
}
final String inputKey = Uuid().generateV4();
server.stdin
..writeln('compile-expression $inputKey')
..writeln(request.expression);
request.definitions?.forEach(server.stdin.writeln);
server.stdin.writeln(inputKey);
request.definitionTypes?.forEach(server.stdin.writeln);
server.stdin.writeln(inputKey);
request.typeDefinitions?.forEach(server.stdin.writeln);
server.stdin.writeln(inputKey);
request.typeBounds?.forEach(server.stdin.writeln);
server.stdin.writeln(inputKey);
request.typeDefaults?.forEach(server.stdin.writeln);
server.stdin
..writeln(inputKey)
..writeln(request.libraryUri ?? '')
..writeln(request.klass ?? '')
..writeln(request.method ?? '')
..writeln(request.isStatic);
return _stdoutHandler.compilerOutput?.future;
}
@override
Future<CompilerOutput?> compileExpressionToJs(
String libraryUri,
int line,
int column,
Map<String, String> jsModules,
Map<String, String> jsFrameValues,
String moduleName,
String expression,
) {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(
_CompileExpressionToJsRequest(
completer, libraryUri, line, column, jsModules, jsFrameValues, moduleName, expression)
);
return completer.future;
}
Future<CompilerOutput?> _compileExpressionToJs(_CompileExpressionToJsRequest request) async {
_stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
// 'compile-expression-to-js' should be invoked after compiler has been started,
// program was compiled.
final Process? server = _server;
if (server == null) {
return null;
}
final String inputKey = Uuid().generateV4();
server.stdin
..writeln('compile-expression-to-js $inputKey')
..writeln(request.libraryUri ?? '')
..writeln(request.line)
..writeln(request.column);
request.jsModules?.forEach((String k, String v) { server.stdin.writeln('$k:$v'); });
server.stdin.writeln(inputKey);
request.jsFrameValues?.forEach((String k, String v) { server.stdin.writeln('$k:$v'); });
server.stdin
..writeln(inputKey)
..writeln(request.moduleName ?? '')
..writeln(request.expression ?? '');
return _stdoutHandler.compilerOutput?.future;
}
@override
void accept() {
if (_compileRequestNeedsConfirmation) {
_server?.stdin.writeln('accept');
_logger.printTrace('<- accept');
}
_compileRequestNeedsConfirmation = false;
}
@override
Future<CompilerOutput?> reject() {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
}
final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
_controller.add(_RejectRequest(completer));
return completer.future;
}
Future<CompilerOutput?> _reject() async {
if (!_compileRequestNeedsConfirmation) {
return Future<CompilerOutput?>.value();
}
_stdoutHandler.reset(expectSources: false);
_server?.stdin.writeln('reject');
_logger.printTrace('<- reject');
_compileRequestNeedsConfirmation = false;
return _stdoutHandler.compilerOutput?.future;
}
@override
void reset() {
_server?.stdin.writeln('reset');
_logger.printTrace('<- reset');
}
@override
Future<Object> shutdown() async {
// Server was never successfully created.
final Process? server = _server;
if (server == null) {
return 0;
}
_logger.printTrace('killing pid ${server.pid}');
server.kill();
return server.exitCode;
}
}
/// Convert a file URI into a multi-root scheme URI if provided, otherwise
/// return unmodified.
@visibleForTesting
String toMultiRootPath(Uri fileUri, String? scheme, List<String> fileSystemRoots, bool windows) {
if (scheme == null || fileSystemRoots.isEmpty || fileUri.scheme != 'file') {
return fileUri.toString();
}
final String filePath = fileUri.toFilePath(windows: windows);
for (final String fileSystemRoot in fileSystemRoots) {
if (filePath.startsWith(fileSystemRoot)) {
return '$scheme://${filePath.substring(fileSystemRoot.length)}';
}
}
return fileUri.toString();
}