// 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/config.dart'; import 'base/context.dart'; import 'base/file_system.dart'; import 'base/io.dart'; import 'base/logger.dart'; import 'base/platform.dart'; import 'base/process.dart'; import 'base/utils.dart'; import 'build_info.dart'; import 'bundle.dart'; import 'convert.dart'; /// Opt-in changes to the dart compilers. const kDartCompilerExperiments = []; /// 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) { return switch (rawValue) { 'flutter' => flutter, 'flutter_runner' => flutterRunner, 'vm' => vm, 'dartdevc' => dartdevc, _ => throw Exception('Unexpected target model $rawValue'), }; } const TargetModel._(this._value); /// The Flutter patched Dart SDK. static const flutter = TargetModel._('flutter'); /// The Fuchsia patched SDK. static const flutterRunner = TargetModel._('flutter_runner'); /// The Dart VM. static const vm = TargetModel._('vm'); /// The development compiler for JavaScript. static const dartdevc = TargetModel._('dartdevc'); final String _value; @override String toString() => _value; } class CompilerOutput { const CompilerOutput( this.outputFilename, this.errorCount, this.sources, { this.expressionData, this.errorMessage, }); final String outputFilename; final int errorCount; final List sources; /// This field is only non-null for expression compilation requests. final Uint8List? expressionData; /// This field is only non-null when a compilation error was encountered. final String? errorMessage; } 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; final sources = []; var _suppressCompilerMessages = false; var _expectSources = true; var _readFile = false; var _errorBuffer = StringBuffer(); void handler(String message) { const 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 output = CompilerOutput( fileName, errorCount, sources, expressionData: expressionData, errorMessage: _errorBuffer.isNotEmpty ? _errorBuffer.toString() : null, ); compilerOutput?.complete(output); return; } switch (state) { case StdoutState.CollectDiagnostic when _suppressCompilerMessages: _logger.printTrace(message); _errorBuffer.writeln(message); case StdoutState.CollectDiagnostic: _logger.printError(message); _errorBuffer.writeln(message); case 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(); _suppressCompilerMessages = suppressCompilerMessages; _expectSources = expectSources; _readFile = readFile; state = StdoutState.CollectDiagnostic; _errorBuffer = StringBuffer(); } } /// List the preconfigured build options for a given build mode. List buildModeOptions(BuildMode mode, List dartDefines) => switch (mode) { BuildMode.debug => [ // 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 => [ // 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', '--delete-tostring-package-uri=dart:ui', '--delete-tostring-package-uri=package:flutter', ...kDartCompilerExperiments, ], BuildMode.release => [ '-Ddart.vm.profile=false', '-Ddart.vm.product=true', '--delete-tostring-package-uri=dart:ui', '--delete-tostring-package-uri=package:flutter', ...kDartCompilerExperiments, ], _ => throw Exception('Unknown BuildMode: $mode'), }; final _extraFrontEndOptionsFilterSet = { // Don't allow for users to pass --include-unsupported-platform-library-stubs to bypass // unsupported library compile time errors as this flag is only safe for use by developer // tooling. '--include-unsupported-platform-library-stubs', }; Iterable? _filterExtraFrontEndOptions(List? options) => options?.where((e) => !_extraFrontEndOptionsFilterSet.contains(e)); /// 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 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 _fileSystemRoots; final StdoutHandler _stdoutHandler; Future compile({ required String sdkRoot, String? mainPath, String? outputFilePath, String? depFilePath, TargetModel targetModel = TargetModel.flutter, bool linkPlatformKernelIn = false, bool aot = false, String? frontendServerStarterPath, List? extraFrontEndOptions, List? 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 dartDefines, required PackageConfig packageConfig, String? nativeAssets, }) async { final TargetPlatform? platform = targetModel == TargetModel.dartdevc ? TargetPlatform.web_javascript : null; // This is a URI, not a file path, so the forward slash is correct even on Windows. if (!sdkRoot.endsWith('/')) { sdkRoot = '$sdkRoot/'; } String? mainUri; if (mainPath != null) { 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 commandToStartFrontendServer; if (frontendServerStarterPath != null && frontendServerStarterPath.isNotEmpty) { final String engineDartPath = _artifacts.getArtifactPath( Artifact.engineDartBinary, platform: platform, ); if (!_processManager.canRun(engineDartPath)) { throwToolExit('Unable to find Dart binary at $engineDartPath'); } commandToStartFrontendServer = [engineDartPath, frontendServerStarterPath]; } else { final String engineDartAotRuntimePath = _artifacts.getArtifactPath( Artifact.engineDartAotRuntime, platform: platform, ); if (!_processManager.canRun(engineDartAotRuntimePath)) { throwToolExit('Unable to find dartaotruntime binary at $engineDartAotRuntimePath'); } commandToStartFrontendServer = [ engineDartAotRuntimePath, _artifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, platform: platform, ), ]; } final List command = commandToStartFrontendServer + [ '--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) ...[ '--aot', '--tfa', // The --target-os flag only makes sense for whole program compilation. if (targetOS != null) ...['--target-os', targetOS], ], if (packagesPath != null) ...['--packages', packagesPath], if (outputFilePath != null) ...['--output-dill', outputFilePath], if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) ...[ '--depfile', depFilePath, ], if (fileSystemRoots != null) for (final String root in fileSystemRoots) ...['--filesystem-root', root], if (fileSystemScheme != null) ...['--filesystem-scheme', fileSystemScheme], if (initializeFromDill != null) ...[ '--incremental', '--initialize-from-dill', initializeFromDill, ], if (platformDill != null) ...['--platform', platformDill], if (dartPluginRegistrantUri != null) ...[ '--source', dartPluginRegistrantUri, '--source', 'package:flutter/src/dart_plugin_registrant.dart', '-Dflutter.dart_plugin_registrant=$dartPluginRegistrantUri', ], if (nativeAssets != null) ...['--native-assets', nativeAssets], // See: https://github.com/flutter/flutter/issues/103994 '--verbosity=error', ...?_filterExtraFrontEndOptions(extraFrontEndOptions), mainUri ?? '--native-assets-only', ]; _logger.printTrace(command.join(' ')); final Process server = await _processManager.start(command); server.stderr.transform(utf8.decoder).listen(_logger.printError); server.stdout.transform(utf8LineDecoder).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 completer; Future _run(DefaultResidentCompiler compiler); Future 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, this.nativeAssetsYamlUri, required this.recompileRestart, }); Uri mainUri; List? invalidatedFiles; String outputPath; PackageConfig packageConfig; bool suppressErrors; final Uri? additionalSourceUri; final Uri? nativeAssetsYamlUri; final bool recompileRestart; @override Future _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? definitions; List? definitionTypes; List? typeDefinitions; List? typeBounds; List? typeDefaults; String? libraryUri; String? klass; String? method; bool isStatic; @override Future _run(DefaultResidentCompiler compiler) async => compiler._compileExpression(this); } class _CompileExpressionToJsRequest extends _CompilationRequest { _CompileExpressionToJsRequest( super.completer, this.libraryUri, this.scriptUri, this.line, this.column, this.jsModules, this.jsFrameValues, this.moduleName, this.expression, ); final String? libraryUri; final String? scriptUri; final int line; final int column; final Map? jsModules; final Map? jsFrameValues; final String? moduleName; final String? expression; @override Future _run(DefaultResidentCompiler compiler) async => compiler._compileExpressionToJs(this); } class _RejectRequest extends _CompilationRequest { _RejectRequest(super.completer); @override Future _run(DefaultResidentCompiler compiler) async => compiler._reject(); } ResidentCompilerFactory get residentCompilerFactory => context.get() ?? const ResidentCompilerFactory(); /// A factory for generating [ResidentCompiler] instances. class ResidentCompilerFactory { const ResidentCompilerFactory(); ResidentCompiler create({ required TargetPlatform targetPlatform, required BuildInfo buildInfo, required Logger logger, required ProcessManager processManager, required Artifacts artifacts, required Platform platform, required FileSystem fileSystem, required ShutdownHooks shutdownHooks, required Config config, bool testCompilation = false, TargetModel? targetModelOverride, }) { String sdkRoot = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, platform: targetPlatform, mode: buildInfo.mode, ); String? platformDillPath; String? librariesSpec; TargetModel targetModel = targetModelOverride ?? .flutter; // Configure the compiler to target the DDC runtime. if (targetPlatform case .web_javascript) { sdkRoot = artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path; targetModel = .dartdevc; const platformDillName = 'ddc_outline.dill'; platformDillPath = fileSystem .file( fileSystem.path.join( artifacts.getHostArtifact(.webPlatformKernelFolder).path, platformDillName, ), ) .absolute .uri .toString(); librariesSpec = fileSystem .file(artifacts.getHostArtifact(.flutterWebLibrariesJson)) .uri .toString(); buildInfo = buildInfo.copyWith( // Override the filesystem scheme so that the frontend_server can find // the generated entrypoint code. fileSystemScheme: 'org-dartlang-app', extraFrontEndOptions: [ ...buildInfo.extraFrontEndOptions, if (buildInfo.webEnableHotReload) // These flags are only valid to be passed when compiling with DDC. ...['--dartdevc-canary', '--dartdevc-module-format=ddc'], ], ); } else { if (targetPlatform case .fuchsia_arm64 || .fuchsia_x64) { targetModel = .flutterRunner; } buildInfo = buildInfo.copyWith( extraFrontEndOptions: [ ...buildInfo.extraFrontEndOptions, '--enable-experiment=alternative-invalidation-strategy', ], ); } return DefaultResidentCompiler( sdkRoot, buildInfo: buildInfo, logger: logger, processManager: processManager, artifacts: artifacts, platform: platform, fileSystem: fileSystem, shutdownHooks: shutdownHooks, config: config, targetModel: targetModel, platformDill: platformDillPath, librariesSpec: librariesSpec, testCompilation: testCompilation, ); } } /// 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 interface class ResidentCompiler { // 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 the Dart script identified by /// [mainUri], [invalidatedFiles] list is ignored. /// On successive runs [invalidatedFiles] indicates which files need to be /// recompiled. /// /// 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]. /// /// If [recompileRestart] is true, uses the `recompile-restart` instruction /// intended for a hot restart instead. Future recompile( Uri mainUri, List? invalidatedFiles, { required String outputPath, required PackageConfig packageConfig, required FileSystem fs, String? projectRootPath, bool suppressErrors = false, bool checkDartPluginRegistry = false, File? dartPluginRegistrant, Uri? nativeAssetsYaml, bool recompileRestart = false, }); Future compileExpression( String expression, List? definitions, List? definitionTypes, List? typeDefinitions, List? typeBounds, List? 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 compileExpressionToJs( String libraryUri, String scriptUri, int line, int column, Map jsModules, Map 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 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 shutdown(); } @visibleForTesting class DefaultResidentCompiler implements ResidentCompiler { DefaultResidentCompiler( String sdkRoot, { required BuildInfo buildInfo, required Logger logger, required ProcessManager processManager, required this.artifacts, required Platform platform, required FileSystem fileSystem, required ShutdownHooks shutdownHooks, required Config config, this.testCompilation = false, this.targetModel = TargetModel.flutter, this.platformDill, this.librariesSpec, @visibleForTesting StdoutHandler? stdoutHandler, }) : buildMode = buildInfo.mode, trackWidgetCreation = buildInfo.trackWidgetCreation, includeUnsupportedPlatformLibraryStubs = buildInfo.includeUnsupportedPlatformLibraryStubs, packagesPath = buildInfo.packageConfigPath, initializeFromDill = buildInfo.initializeFromDill ?? getDefaultCachedKernelPath( config: config, fileSystem: fileSystem, trackWidgetCreation: buildInfo.trackWidgetCreation, dartDefines: buildInfo.dartDefines, extraFrontEndOptions: buildInfo.extraFrontEndOptions, ), extraFrontEndOptions = buildInfo.extraFrontEndOptions, fileSystemScheme = buildInfo.fileSystemScheme, assumeInitializeFromDillUpToDate = buildInfo.assumeInitializeFromDillUpToDate, frontendServerStarterPath = buildInfo.frontendServerStarterPath, _logger = logger, _processManager = processManager, _shutdownHooks = shutdownHooks, _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem), _platform = platform, dartDefines = buildInfo.dartDefines, // 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.from(buildInfo.fileSystemRoots) { // Currently, the only developer tooling that requires support for importing unsupported // platform libraries at compile time is the widget previewer. Only developer tooling use cases // should support this, so this is restricted to the widget previewer's runtime target for now. if (includeUnsupportedPlatformLibraryStubs && targetModel != TargetModel.dartdevc) { throw StateError( 'includeUnsupportedPlatformLibraryStubs should only be used by the widget-preview ' 'command.', ); } } final Logger _logger; final ProcessManager _processManager; final Artifacts artifacts; final Platform _platform; final ShutdownHooks _shutdownHooks; final bool testCompilation; final BuildMode buildMode; final bool trackWidgetCreation; final bool includeUnsupportedPlatformLibraryStubs; final String? packagesPath; final TargetModel targetModel; final List fileSystemRoots; final String? fileSystemScheme; final String? initializeFromDill; final bool assumeInitializeFromDillUpToDate; final String? frontendServerStarterPath; final List? extraFrontEndOptions; final List 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; var _compileRequestNeedsConfirmation = false; final _controller = StreamController<_CompilationRequest>(); @override Future recompile( Uri mainUri, List? invalidatedFiles, { required String outputPath, required PackageConfig packageConfig, bool suppressErrors = false, bool checkDartPluginRegistry = false, File? dartPluginRegistrant, String? projectRootPath, FileSystem? fs, Uri? nativeAssetsYaml, bool recompileRestart = false, }) 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 = Completer(); _controller.add( _RecompileRequest( completer, mainUri, invalidatedFiles, outputPath, packageConfig, suppressErrors, additionalSourceUri: additionalSourceUri, nativeAssetsYamlUri: nativeAssetsYaml, recompileRestart: recompileRestart, ), ); return completer.future; } Future _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 nativeAssets = request.nativeAssetsYamlUri?.toString(); final Process? server = _server; if (server == null) { return _compile( mainUri, request.outputPath, additionalSourceUri: additionalSourceUri, nativeAssetsUri: nativeAssets, ); } final String inputKey = Uuid().generateV4(); if (nativeAssets != null && nativeAssets.isNotEmpty) { server.stdin.writeln('native-assets $nativeAssets'); _logger.printTrace('<- native-assets $nativeAssets'); } if (request.recompileRestart) { server.stdin.writeln('recompile-restart $mainUri $inputKey'); } else { server.stdin.writeln('recompile $mainUri $inputKey'); } _logger.printTrace('<- recompile $mainUri $inputKey'); final List? 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 _compilationQueue = <_CompilationRequest>[]; Future _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 _compile( String scriptUri, String? outputPath, { String? additionalSourceUri, String? nativeAssetsUri, }) async { final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) ? TargetPlatform.web_javascript : null; late final List commandToStartFrontendServer; if (frontendServerStarterPath != null && frontendServerStarterPath!.isNotEmpty) { commandToStartFrontendServer = [ artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform), frontendServerStarterPath!, ]; } else { commandToStartFrontendServer = [ artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: platform), artifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, platform: platform, ), ]; } final command = [ ...commandToStartFrontendServer, '--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) ...['--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) ...[ '--libraries-spec', librariesSpec!, ], if (packagesPath != null) ...['--packages', packagesPath!], ...buildModeOptions(buildMode, dartDefines), if (trackWidgetCreation) '--track-widget-creation', if (includeUnsupportedPlatformLibraryStubs) '--include-unsupported-platform-library-stubs', for (final String root in fileSystemRoots) ...['--filesystem-root', root], if (fileSystemScheme != null) ...['--filesystem-scheme', fileSystemScheme!], if (initializeFromDill != null) ...['--initialize-from-dill', initializeFromDill!], if (assumeInitializeFromDillUpToDate) '--assume-initialize-from-dill-up-to-date', if (additionalSourceUri != null) ...[ '--source', additionalSourceUri, '--source', 'package:flutter/src/dart_plugin_registrant.dart', '-Dflutter.dart_plugin_registrant=$additionalSourceUri', ], if (nativeAssetsUri != null) ...['--native-assets', nativeAssetsUri], if (platformDill != null) ...['--platform', platformDill!], // See: https://github.com/flutter/flutter/issues/103994 '--verbosity=error', ...?_filterExtraFrontEndOptions(extraFrontEndOptions), ]; _logger.printTrace(command.join(' ')); _server = await _processManager.start(command); _server?.stdout .transform(utf8LineDecoder) .listen( _stdoutHandler.handler, onDone: () { // when outputFilename future is not completed, but stdout is closed // process has died unexpectedly. if (_stdoutHandler.compilerOutput?.isCompleted == false && !_shutdownHooks.isShuttingDown) { _stdoutHandler.compilerOutput?.complete(); throwToolExit('The Dart compiler exited unexpectedly.'); } }, ); _server?.stderr.transform(utf8LineDecoder).listen(_logger.printError); unawaited( _server?.exitCode.then((int code) { // The frontend server exits with a 253 error code when we shutdown due to a signal. // Don't treat this as an error if we're in the middle of the shutdown sequence. if (code != 0 && !_shutdownHooks.isShuttingDown) { throwToolExit('The Dart compiler exited unexpectedly.'); } }), ); if (nativeAssetsUri != null && nativeAssetsUri.isNotEmpty) { _server?.stdin.writeln('native-assets $nativeAssetsUri'); _logger.printTrace('<- native-assets $nativeAssetsUri'); } _server?.stdin.writeln('compile $scriptUri'); _logger.printTrace('<- compile $scriptUri'); return _stdoutHandler.compilerOutput?.future; } @override Future compileExpression( String expression, List? definitions, List? definitionTypes, List? typeDefinitions, List? typeBounds, List? typeDefaults, String? libraryUri, String? klass, String? method, bool isStatic, ) async { if (!_controller.hasListener) { _controller.stream.listen(_handleCompilationRequest); } final completer = Completer(); final request = _CompileExpressionRequest( completer, expression, definitions, definitionTypes, typeDefinitions, typeBounds, typeDefaults, libraryUri, klass, method, isStatic, ); _controller.add(request); return completer.future; } Future _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 compileExpressionToJs( String libraryUri, String scriptUri, int line, int column, Map jsModules, Map jsFrameValues, String moduleName, String expression, ) { if (!_controller.hasListener) { _controller.stream.listen(_handleCompilationRequest); } final completer = Completer(); _controller.add( _CompileExpressionToJsRequest( completer, libraryUri, scriptUri, line, column, jsModules, jsFrameValues, moduleName, expression, ), ); return completer.future; } Future _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; } server.stdin ..writeln('JSON_INPUT') ..writeln( json.encode({ 'type': 'COMPILE_EXPRESSION_JS', 'data': { 'expression': request.expression, 'libraryUri': request.libraryUri, 'scriptUri': request.scriptUri, 'line': request.line, 'column': request.column, 'jsModules': request.jsModules, 'jsFrameValues': request.jsFrameValues, 'moduleName': request.moduleName, }, }), ); return _stdoutHandler.compilerOutput?.future; } @override void accept() { if (_compileRequestNeedsConfirmation) { _server?.stdin.writeln('accept'); _logger.printTrace('<- accept'); } _compileRequestNeedsConfirmation = false; } @override Future reject() { if (!_controller.hasListener) { _controller.stream.listen(_handleCompilationRequest); } final completer = Completer(); _controller.add(_RejectRequest(completer)); return completer.future; } Future _reject() async { if (!_compileRequestNeedsConfirmation) { return Future.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 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 fileSystemRoots, bool windows) { if (scheme == null || fileSystemRoots.isEmpty || fileUri.scheme != 'file') { return fileUri.toString(); } final String filePath = fileUri.toFilePath(windows: windows); for (final fileSystemRoot in fileSystemRoots) { if (filePath.startsWith(fileSystemRoot)) { return '$scheme://${filePath.substring(fileSystemRoot.length)}'; } } return fileUri.toString(); }