1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:typed_data';
7
8import 'package:meta/meta.dart';
9import 'package:package_config/package_config.dart';
10import 'package:process/process.dart';
11import 'package:usage/uuid/uuid.dart';
12
13import 'artifacts.dart';
14import 'base/common.dart';
15import 'base/file_system.dart';
16import 'base/io.dart';
17import 'base/logger.dart';
18import 'base/platform.dart';
19import 'build_info.dart';
20import 'convert.dart';
21
22/// Opt-in changes to the dart compilers.
23const List<String> kDartCompilerExperiments = <String>[
24];
25
26/// The target model describes the set of core libraries that are available within
27/// the SDK.
28class TargetModel {
29 /// Parse a [TargetModel] from a raw string.
30 ///
31 /// Throws an exception if passed a value other than 'flutter',
32 /// 'flutter_runner', 'vm', or 'dartdevc'.
33 factory TargetModel(String rawValue) {
34 return switch (rawValue) {
35 'flutter' => flutter,
36 'flutter_runner' => flutterRunner,
37 'vm' => vm,
38 'dartdevc' => dartdevc,
39 _ => throw Exception('Unexpected target model $rawValue'),
40 };
41 }
42
43 const TargetModel._(this._value);
44
45 /// The Flutter patched Dart SDK.
46 static const TargetModel flutter = TargetModel._('flutter');
47
48 /// The Fuchsia patched SDK.
49 static const TargetModel flutterRunner = TargetModel._('flutter_runner');
50
51 /// The Dart VM.
52 static const TargetModel vm = TargetModel._('vm');
53
54 /// The development compiler for JavaScript.
55 static const TargetModel dartdevc = TargetModel._('dartdevc');
56
57 final String _value;
58
59 @override
60 String toString() => _value;
61}
62
63class CompilerOutput {
64 const CompilerOutput(this.outputFilename, this.errorCount, this.sources, {this.expressionData});
65
66 final String outputFilename;
67 final int errorCount;
68 final List<Uri> sources;
69
70 /// This field is only non-null for expression compilation requests.
71 final Uint8List? expressionData;
72}
73
74enum StdoutState { CollectDiagnostic, CollectDependencies }
75
76/// Handles stdin/stdout communication with the frontend server.
77class StdoutHandler {
78 StdoutHandler({
79 required Logger logger,
80 required FileSystem fileSystem,
81 }) : _logger = logger,
82 _fileSystem = fileSystem {
83 reset();
84 }
85
86 final Logger _logger;
87 final FileSystem _fileSystem;
88
89 String? boundaryKey;
90 StdoutState state = StdoutState.CollectDiagnostic;
91 Completer<CompilerOutput?>? compilerOutput;
92 final List<Uri> sources = <Uri>[];
93
94 bool _suppressCompilerMessages = false;
95 bool _expectSources = true;
96 bool _readFile = false;
97
98 void handler(String message) {
99 const String kResultPrefix = 'result ';
100 if (boundaryKey == null && message.startsWith(kResultPrefix)) {
101 boundaryKey = message.substring(kResultPrefix.length);
102 return;
103 }
104 final String? messageBoundaryKey = boundaryKey;
105 if (messageBoundaryKey != null && message.startsWith(messageBoundaryKey)) {
106 if (_expectSources) {
107 if (state == StdoutState.CollectDiagnostic) {
108 state = StdoutState.CollectDependencies;
109 return;
110 }
111 }
112 if (message.length <= messageBoundaryKey.length) {
113 compilerOutput?.complete();
114 return;
115 }
116 final int spaceDelimiter = message.lastIndexOf(' ');
117 final String fileName = message.substring(messageBoundaryKey.length + 1, spaceDelimiter);
118 final int errorCount = int.parse(message.substring(spaceDelimiter + 1).trim());
119 Uint8List? expressionData;
120 if (_readFile) {
121 expressionData = _fileSystem.file(fileName).readAsBytesSync();
122 }
123 final CompilerOutput output = CompilerOutput(
124 fileName,
125 errorCount,
126 sources,
127 expressionData: expressionData,
128 );
129 compilerOutput?.complete(output);
130 return;
131 }
132 switch (state) {
133 case StdoutState.CollectDiagnostic when _suppressCompilerMessages:
134 _logger.printTrace(message);
135 case StdoutState.CollectDiagnostic:
136 _logger.printError(message);
137 case StdoutState.CollectDependencies:
138 switch (message[0]) {
139 case '+':
140 sources.add(Uri.parse(message.substring(1)));
141 case '-':
142 sources.remove(Uri.parse(message.substring(1)));
143 default:
144 _logger.printTrace('Unexpected prefix for $message uri - ignoring');
145 }
146 }
147 }
148
149 // This is needed to get ready to process next compilation result output,
150 // with its own boundary key and new completer.
151 void reset({ bool suppressCompilerMessages = false, bool expectSources = true, bool readFile = false }) {
152 boundaryKey = null;
153 compilerOutput = Completer<CompilerOutput?>();
154 _suppressCompilerMessages = suppressCompilerMessages;
155 _expectSources = expectSources;
156 _readFile = readFile;
157 state = StdoutState.CollectDiagnostic;
158 }
159}
160
161/// List the preconfigured build options for a given build mode.
162List<String> buildModeOptions(BuildMode mode, List<String> dartDefines) =>
163 switch (mode) {
164 BuildMode.debug => <String>[
165 // These checks allow the CLI to override the value of this define for unit
166 // testing the framework.
167 if (!dartDefines.any((String define) => define.startsWith('dart.vm.profile')))
168 '-Ddart.vm.profile=false',
169 if (!dartDefines.any((String define) => define.startsWith('dart.vm.product')))
170 '-Ddart.vm.product=false',
171 '--enable-asserts',
172 ],
173 BuildMode.profile => <String>[
174 // These checks allow the CLI to override the value of this define for
175 // benchmarks with most timeline traces disabled.
176 if (!dartDefines.any((String define) => define.startsWith('dart.vm.profile')))
177 '-Ddart.vm.profile=true',
178 if (!dartDefines.any((String define) => define.startsWith('dart.vm.product')))
179 '-Ddart.vm.product=false',
180 '--delete-tostring-package-uri=dart:ui',
181 '--delete-tostring-package-uri=package:flutter',
182 ...kDartCompilerExperiments,
183 ],
184 BuildMode.release => <String>[
185 '-Ddart.vm.profile=false',
186 '-Ddart.vm.product=true',
187 '--delete-tostring-package-uri=dart:ui',
188 '--delete-tostring-package-uri=package:flutter',
189 ...kDartCompilerExperiments,
190 ],
191 _ => throw Exception('Unknown BuildMode: $mode')
192 };
193
194/// A compiler interface for producing single (non-incremental) kernel files.
195class KernelCompiler {
196 KernelCompiler({
197 required FileSystem fileSystem,
198 required Logger logger,
199 required ProcessManager processManager,
200 required Artifacts artifacts,
201 required List<String> fileSystemRoots,
202 String? fileSystemScheme,
203 @visibleForTesting StdoutHandler? stdoutHandler,
204 }) : _logger = logger,
205 _fileSystem = fileSystem,
206 _artifacts = artifacts,
207 _processManager = processManager,
208 _fileSystemScheme = fileSystemScheme,
209 _fileSystemRoots = fileSystemRoots,
210 _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem);
211
212 final FileSystem _fileSystem;
213 final Artifacts _artifacts;
214 final ProcessManager _processManager;
215 final Logger _logger;
216 final String? _fileSystemScheme;
217 final List<String> _fileSystemRoots;
218 final StdoutHandler _stdoutHandler;
219
220 Future<CompilerOutput?> compile({
221 required String sdkRoot,
222 String? mainPath,
223 String? outputFilePath,
224 String? depFilePath,
225 TargetModel targetModel = TargetModel.flutter,
226 bool linkPlatformKernelIn = false,
227 bool aot = false,
228 String? frontendServerStarterPath,
229 List<String>? extraFrontEndOptions,
230 List<String>? fileSystemRoots,
231 String? fileSystemScheme,
232 String? initializeFromDill,
233 String? platformDill,
234 Directory? buildDir,
235 String? targetOS,
236 bool checkDartPluginRegistry = false,
237 required String? packagesPath,
238 required BuildMode buildMode,
239 required bool trackWidgetCreation,
240 required List<String> dartDefines,
241 required PackageConfig packageConfig,
242 String? nativeAssets,
243 }) async {
244 final TargetPlatform? platform = targetModel == TargetModel.dartdevc ? TargetPlatform.web_javascript : null;
245 // This is a URI, not a file path, so the forward slash is correct even on Windows.
246 if (!sdkRoot.endsWith('/')) {
247 sdkRoot = '$sdkRoot/';
248 }
249 String? mainUri;
250 if (mainPath != null) {
251 final File mainFile = _fileSystem.file(mainPath);
252 final Uri mainFileUri = mainFile.uri;
253 if (packagesPath != null) {
254 mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
255 }
256 mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
257 }
258 if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
259 _fileSystem.file(outputFilePath).createSync(recursive: true);
260 }
261
262 // Check if there's a Dart plugin registrant.
263 // This is contained in the file `dart_plugin_registrant.dart` under `.dart_tools/flutter_build/`.
264 final File? dartPluginRegistrant = checkDartPluginRegistry
265 ? buildDir?.parent.childFile('dart_plugin_registrant.dart')
266 : null;
267
268 String? dartPluginRegistrantUri;
269 if (dartPluginRegistrant != null && dartPluginRegistrant.existsSync()) {
270 final Uri dartPluginRegistrantFileUri = dartPluginRegistrant.uri;
271 dartPluginRegistrantUri = packageConfig.toPackageUri(dartPluginRegistrantFileUri)?.toString() ??
272 toMultiRootPath(dartPluginRegistrantFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
273 }
274
275 final List<String> commandToStartFrontendServer;
276 if (frontendServerStarterPath != null && frontendServerStarterPath.isNotEmpty) {
277 final String engineDartPath = _artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform);
278 if (!_processManager.canRun(engineDartPath)) {
279 throwToolExit('Unable to find Dart binary at $engineDartPath');
280 }
281 commandToStartFrontendServer = <String>[
282 engineDartPath,
283 frontendServerStarterPath,
284 ];
285 } else {
286 final String engineDartAotRuntimePath = _artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: platform);
287 if (!_processManager.canRun(engineDartAotRuntimePath)) {
288 throwToolExit('Unable to find dartaotruntime binary at $engineDartAotRuntimePath');
289 }
290 commandToStartFrontendServer = <String>[
291 engineDartAotRuntimePath,
292 _artifacts.getArtifactPath(
293 Artifact.frontendServerSnapshotForEngineDartSdk,
294 platform: platform,
295 ),
296 ];
297 }
298
299 final List<String> command = commandToStartFrontendServer + <String>[
300 '--sdk-root',
301 sdkRoot,
302 '--target=$targetModel',
303 '--no-print-incremental-dependencies',
304 for (final Object dartDefine in dartDefines)
305 '-D$dartDefine',
306 ...buildModeOptions(buildMode, dartDefines),
307 if (trackWidgetCreation) '--track-widget-creation',
308 if (!linkPlatformKernelIn) '--no-link-platform',
309 if (aot) ...<String>[
310 '--aot',
311 '--tfa',
312 // The --target-os flag only makes sense for whole program compilation.
313 if (targetOS != null) ...<String>[
314 '--target-os',
315 targetOS,
316 ],
317 ],
318 if (packagesPath != null) ...<String>[
319 '--packages',
320 packagesPath,
321 ],
322 if (outputFilePath != null) ...<String>[
323 '--output-dill',
324 outputFilePath,
325 ],
326 if (depFilePath != null && (fileSystemRoots == null || fileSystemRoots.isEmpty)) ...<String>[
327 '--depfile',
328 depFilePath,
329 ],
330 if (fileSystemRoots != null)
331 for (final String root in fileSystemRoots) ...<String>[
332 '--filesystem-root',
333 root,
334 ],
335 if (fileSystemScheme != null) ...<String>[
336 '--filesystem-scheme',
337 fileSystemScheme,
338 ],
339 if (initializeFromDill != null) ...<String>[
340 '--incremental',
341 '--initialize-from-dill',
342 initializeFromDill,
343 ],
344 if (platformDill != null) ...<String>[
345 '--platform',
346 platformDill,
347 ],
348 if (dartPluginRegistrantUri != null) ...<String>[
349 '--source',
350 dartPluginRegistrantUri,
351 '--source',
352 'package:flutter/src/dart_plugin_registrant.dart',
353 '-Dflutter.dart_plugin_registrant=$dartPluginRegistrantUri',
354 ],
355 if (nativeAssets != null) ...<String>[
356 '--native-assets',
357 nativeAssets,
358 ],
359 // See: https://github.com/flutter/flutter/issues/103994
360 '--verbosity=error',
361 ...?extraFrontEndOptions,
362 if (mainUri != null) mainUri
363 else '--native-assets-only',
364 ];
365
366 _logger.printTrace(command.join(' '));
367 final Process server = await _processManager.start(command);
368
369 server.stderr
370 .transform<String>(utf8.decoder)
371 .listen(_logger.printError);
372 server.stdout
373 .transform<String>(utf8.decoder)
374 .transform<String>(const LineSplitter())
375 .listen(_stdoutHandler.handler);
376 final int exitCode = await server.exitCode;
377 if (exitCode == 0) {
378 return _stdoutHandler.compilerOutput?.future;
379 }
380 return null;
381 }
382}
383
384/// Class that allows to serialize compilation requests to the compiler.
385abstract class _CompilationRequest {
386 _CompilationRequest(this.completer);
387
388 Completer<CompilerOutput?> completer;
389
390 Future<CompilerOutput?> _run(DefaultResidentCompiler compiler);
391
392 Future<void> run(DefaultResidentCompiler compiler) async {
393 completer.complete(await _run(compiler));
394 }
395}
396
397class _RecompileRequest extends _CompilationRequest {
398 _RecompileRequest(
399 super.completer,
400 this.mainUri,
401 this.invalidatedFiles,
402 this.outputPath,
403 this.packageConfig,
404 this.suppressErrors, {
405 this.additionalSourceUri,
406 this.nativeAssetsYamlUri,
407 });
408
409 Uri mainUri;
410 List<Uri>? invalidatedFiles;
411 String outputPath;
412 PackageConfig packageConfig;
413 bool suppressErrors;
414 final Uri? additionalSourceUri;
415 final Uri? nativeAssetsYamlUri;
416
417 @override
418 Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
419 compiler._recompile(this);
420}
421
422class _CompileExpressionRequest extends _CompilationRequest {
423 _CompileExpressionRequest(
424 super.completer,
425 this.expression,
426 this.definitions,
427 this.definitionTypes,
428 this.typeDefinitions,
429 this.typeBounds,
430 this.typeDefaults,
431 this.libraryUri,
432 this.klass,
433 this.method,
434 this.isStatic,
435 );
436
437 String expression;
438 List<String>? definitions;
439 List<String>? definitionTypes;
440 List<String>? typeDefinitions;
441 List<String>? typeBounds;
442 List<String>? typeDefaults;
443 String? libraryUri;
444 String? klass;
445 String? method;
446 bool isStatic;
447
448 @override
449 Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
450 compiler._compileExpression(this);
451}
452
453class _CompileExpressionToJsRequest extends _CompilationRequest {
454 _CompileExpressionToJsRequest(
455 super.completer,
456 this.libraryUri,
457 this.line,
458 this.column,
459 this.jsModules,
460 this.jsFrameValues,
461 this.moduleName,
462 this.expression,
463 );
464
465 final String? libraryUri;
466 final int line;
467 final int column;
468 final Map<String, String>? jsModules;
469 final Map<String, String>? jsFrameValues;
470 final String? moduleName;
471 final String? expression;
472
473 @override
474 Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
475 compiler._compileExpressionToJs(this);
476}
477
478class _RejectRequest extends _CompilationRequest {
479 _RejectRequest(super.completer);
480
481 @override
482 Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
483 compiler._reject();
484}
485
486/// Wrapper around incremental frontend server compiler, that communicates with
487/// server via stdin/stdout.
488///
489/// The wrapper is intended to stay resident in memory as user changes, reloads,
490/// restarts the Flutter app.
491abstract class ResidentCompiler {
492 factory ResidentCompiler(String sdkRoot, {
493 required BuildMode buildMode,
494 required Logger logger,
495 required ProcessManager processManager,
496 required Artifacts artifacts,
497 required Platform platform,
498 required FileSystem fileSystem,
499 bool testCompilation,
500 bool trackWidgetCreation,
501 String packagesPath,
502 List<String> fileSystemRoots,
503 String? fileSystemScheme,
504 String initializeFromDill,
505 bool assumeInitializeFromDillUpToDate,
506 TargetModel targetModel,
507 bool unsafePackageSerialization,
508 String? frontendServerStarterPath,
509 List<String> extraFrontEndOptions,
510 String platformDill,
511 List<String>? dartDefines,
512 String librariesSpec,
513 }) = DefaultResidentCompiler;
514
515 // TODO(zanderso): find a better way to configure additional file system
516 // roots from the runner.
517 // See: https://github.com/flutter/flutter/issues/50494
518 void addFileSystemRoot(String root);
519
520 /// If invoked for the first time, it compiles Dart script identified by
521 /// [mainPath], [invalidatedFiles] list is ignored.
522 /// On successive runs [invalidatedFiles] indicates which files need to be
523 /// recompiled. If [mainPath] is [null], previously used [mainPath] entry
524 /// point that is used for recompilation.
525 /// Binary file name is returned if compilation was successful, otherwise
526 /// null is returned.
527 ///
528 /// If [checkDartPluginRegistry] is true, it is the caller's responsibility
529 /// to ensure that the generated registrant file has been updated such that
530 /// it is wrapping [mainUri].
531 Future<CompilerOutput?> recompile(
532 Uri mainUri,
533 List<Uri>? invalidatedFiles, {
534 required String outputPath,
535 required PackageConfig packageConfig,
536 required FileSystem fs,
537 String? projectRootPath,
538 bool suppressErrors = false,
539 bool checkDartPluginRegistry = false,
540 File? dartPluginRegistrant,
541 Uri? nativeAssetsYaml,
542 });
543
544 Future<CompilerOutput?> compileExpression(
545 String expression,
546 List<String>? definitions,
547 List<String>? definitionTypes,
548 List<String>? typeDefinitions,
549 List<String>? typeBounds,
550 List<String>? typeDefaults,
551 String? libraryUri,
552 String? klass,
553 String? method,
554 bool isStatic,
555 );
556
557 /// Compiles [expression] in [libraryUri] at [line]:[column] to JavaScript
558 /// in [moduleName].
559 ///
560 /// Values listed in [jsFrameValues] are substituted for their names in the
561 /// [expression].
562 ///
563 /// Ensures that all [jsModules] are loaded and accessible inside the
564 /// expression.
565 ///
566 /// Example values of parameters:
567 /// [moduleName] is of the form '/packages/hello_world_main.dart'
568 /// [jsFrameValues] is a map from js variable name to its primitive value
569 /// or another variable name, for example
570 /// { 'x': '1', 'y': 'y', 'o': 'null' }
571 /// [jsModules] is a map from variable name to the module name, where
572 /// variable name is the name originally used in JavaScript to contain the
573 /// module object, for example:
574 /// { 'dart':'dart_sdk', 'main': '/packages/hello_world_main.dart' }
575 /// Returns a [CompilerOutput] including the name of the file containing the
576 /// compilation result and a number of errors.
577 Future<CompilerOutput?> compileExpressionToJs(
578 String libraryUri,
579 int line,
580 int column,
581 Map<String, String> jsModules,
582 Map<String, String> jsFrameValues,
583 String moduleName,
584 String expression,
585 );
586
587 /// Should be invoked when results of compilation are accepted by the client.
588 ///
589 /// Either [accept] or [reject] should be called after every [recompile] call.
590 void accept();
591
592 /// Should be invoked when results of compilation are rejected by the client.
593 ///
594 /// Either [accept] or [reject] should be called after every [recompile] call.
595 Future<CompilerOutput?> reject();
596
597 /// Should be invoked when frontend server compiler should forget what was
598 /// accepted previously so that next call to [recompile] produces complete
599 /// kernel file.
600 void reset();
601
602 Future<Object> shutdown();
603}
604
605@visibleForTesting
606class DefaultResidentCompiler implements ResidentCompiler {
607 DefaultResidentCompiler(
608 String sdkRoot, {
609 required this.buildMode,
610 required Logger logger,
611 required ProcessManager processManager,
612 required this.artifacts,
613 required Platform platform,
614 required FileSystem fileSystem,
615 this.testCompilation = false,
616 this.trackWidgetCreation = true,
617 this.packagesPath,
618 List<String> fileSystemRoots = const <String>[],
619 this.fileSystemScheme,
620 this.initializeFromDill,
621 this.assumeInitializeFromDillUpToDate = false,
622 this.targetModel = TargetModel.flutter,
623 this.unsafePackageSerialization = false,
624 this.frontendServerStarterPath,
625 this.extraFrontEndOptions,
626 this.platformDill,
627 List<String>? dartDefines,
628 this.librariesSpec,
629 @visibleForTesting StdoutHandler? stdoutHandler,
630 }) : _logger = logger,
631 _processManager = processManager,
632 _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem),
633 _platform = platform,
634 dartDefines = dartDefines ?? const <String>[],
635 // This is a URI, not a file path, so the forward slash is correct even on Windows.
636 sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/',
637 // Make a copy, we might need to modify it later.
638 fileSystemRoots = List<String>.from(fileSystemRoots);
639
640 final Logger _logger;
641 final ProcessManager _processManager;
642 final Artifacts artifacts;
643 final Platform _platform;
644
645 final bool testCompilation;
646 final BuildMode buildMode;
647 final bool trackWidgetCreation;
648 final String? packagesPath;
649 final TargetModel targetModel;
650 final List<String> fileSystemRoots;
651 final String? fileSystemScheme;
652 final String? initializeFromDill;
653 final bool assumeInitializeFromDillUpToDate;
654 final bool unsafePackageSerialization;
655 final String? frontendServerStarterPath;
656 final List<String>? extraFrontEndOptions;
657 final List<String> dartDefines;
658 final String? librariesSpec;
659
660 @override
661 void addFileSystemRoot(String root) {
662 fileSystemRoots.add(root);
663 }
664
665 /// The path to the root of the Dart SDK used to compile.
666 ///
667 /// This is used to resolve the [platformDill].
668 final String sdkRoot;
669
670 /// The path to the platform dill file.
671 ///
672 /// This does not need to be provided for the normal Flutter workflow.
673 final String? platformDill;
674
675 Process? _server;
676 final StdoutHandler _stdoutHandler;
677 bool _compileRequestNeedsConfirmation = false;
678
679 final StreamController<_CompilationRequest> _controller = StreamController<_CompilationRequest>();
680
681 @override
682 Future<CompilerOutput?> recompile(
683 Uri mainUri,
684 List<Uri>? invalidatedFiles, {
685 required String outputPath,
686 required PackageConfig packageConfig,
687 bool suppressErrors = false,
688 bool checkDartPluginRegistry = false,
689 File? dartPluginRegistrant,
690 String? projectRootPath,
691 FileSystem? fs,
692 Uri? nativeAssetsYaml,
693 }) async {
694 if (!_controller.hasListener) {
695 _controller.stream.listen(_handleCompilationRequest);
696 }
697 Uri? additionalSourceUri;
698 // `dart_plugin_registrant.dart` contains the Dart plugin registry.
699 if (checkDartPluginRegistry && dartPluginRegistrant != null && dartPluginRegistrant.existsSync()) {
700 additionalSourceUri = dartPluginRegistrant.uri;
701 }
702 final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
703 _controller.add(_RecompileRequest(
704 completer,
705 mainUri,
706 invalidatedFiles,
707 outputPath,
708 packageConfig,
709 suppressErrors,
710 additionalSourceUri: additionalSourceUri,
711 nativeAssetsYamlUri: nativeAssetsYaml,
712 ));
713 return completer.future;
714 }
715
716 Future<CompilerOutput?> _recompile(_RecompileRequest request) async {
717 _stdoutHandler.reset();
718 _compileRequestNeedsConfirmation = true;
719 _stdoutHandler._suppressCompilerMessages = request.suppressErrors;
720
721 final String mainUri = request.packageConfig.toPackageUri(request.mainUri)?.toString() ??
722 toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
723
724 String? additionalSourceUri;
725 if (request.additionalSourceUri != null) {
726 additionalSourceUri = request.packageConfig.toPackageUri(request.additionalSourceUri!)?.toString() ??
727 toMultiRootPath(request.additionalSourceUri!, fileSystemScheme, fileSystemRoots, _platform.isWindows);
728 }
729
730 final String? nativeAssets = request.nativeAssetsYamlUri?.toString();
731 final Process? server = _server;
732 if (server == null) {
733 return _compile(
734 mainUri,
735 request.outputPath,
736 additionalSourceUri: additionalSourceUri,
737 nativeAssetsUri: nativeAssets,
738 );
739 }
740 final String inputKey = Uuid().generateV4();
741
742 if (nativeAssets != null && nativeAssets.isNotEmpty) {
743 server.stdin.writeln('native-assets $nativeAssets');
744 _logger.printTrace('<- native-assets $nativeAssets');
745 }
746 server.stdin.writeln('recompile $mainUri $inputKey');
747 _logger.printTrace('<- recompile $mainUri $inputKey');
748 final List<Uri>? invalidatedFiles = request.invalidatedFiles;
749 if (invalidatedFiles != null) {
750 for (final Uri fileUri in invalidatedFiles) {
751 String message;
752 if (fileUri.scheme == 'package') {
753 message = fileUri.toString();
754 } else {
755 message = request.packageConfig.toPackageUri(fileUri)?.toString() ??
756 toMultiRootPath(fileUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);
757 }
758 server.stdin.writeln(message);
759 _logger.printTrace(message);
760 }
761 }
762 server.stdin.writeln(inputKey);
763 _logger.printTrace('<- $inputKey');
764
765 return _stdoutHandler.compilerOutput?.future;
766 }
767
768 final List<_CompilationRequest> _compilationQueue = <_CompilationRequest>[];
769
770 Future<void> _handleCompilationRequest(_CompilationRequest request) async {
771 final bool isEmpty = _compilationQueue.isEmpty;
772 _compilationQueue.add(request);
773 // Only trigger processing if queue was empty - i.e. no other requests
774 // are currently being processed. This effectively enforces "one
775 // compilation request at a time".
776 if (isEmpty) {
777 while (_compilationQueue.isNotEmpty) {
778 final _CompilationRequest request = _compilationQueue.first;
779 await request.run(this);
780 _compilationQueue.removeAt(0);
781 }
782 }
783 }
784
785 Future<CompilerOutput?> _compile(
786 String scriptUri,
787 String? outputPath, {
788 String? additionalSourceUri,
789 String? nativeAssetsUri,
790 }) async {
791 final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) ? TargetPlatform.web_javascript : null;
792 late final List<String> commandToStartFrontendServer;
793 if (frontendServerStarterPath != null && frontendServerStarterPath!.isNotEmpty) {
794 commandToStartFrontendServer = <String>[
795 artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform),
796 frontendServerStarterPath!,
797 ];
798 } else {
799 commandToStartFrontendServer = <String>[
800 artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: platform),
801 artifacts.getArtifactPath(
802 Artifact.frontendServerSnapshotForEngineDartSdk,
803 platform: platform,
804 ),
805 ];
806 }
807
808 final List<String> command = commandToStartFrontendServer + <String>[
809 '--sdk-root',
810 sdkRoot,
811 '--incremental',
812 if (testCompilation)
813 '--no-print-incremental-dependencies',
814 '--target=$targetModel',
815 // TODO(annagrin): remove once this becomes the default behavior
816 // in the frontend_server.
817 // https://github.com/flutter/flutter/issues/59902
818 '--experimental-emit-debug-metadata',
819 for (final Object dartDefine in dartDefines)
820 '-D$dartDefine',
821 if (outputPath != null) ...<String>[
822 '--output-dill',
823 outputPath,
824 ],
825 // If we have a platform dill, we don't need to pass the libraries spec,
826 // since the information is embedded in the .dill file.
827 if (librariesSpec != null && platformDill == null) ...<String>[
828 '--libraries-spec',
829 librariesSpec!,
830 ],
831 if (packagesPath != null) ...<String>[
832 '--packages',
833 packagesPath!,
834 ],
835 ...buildModeOptions(buildMode, dartDefines),
836 if (trackWidgetCreation) '--track-widget-creation',
837 for (final String root in fileSystemRoots) ...<String>[
838 '--filesystem-root',
839 root,
840 ],
841 if (fileSystemScheme != null) ...<String>[
842 '--filesystem-scheme',
843 fileSystemScheme!,
844 ],
845 if (initializeFromDill != null) ...<String>[
846 '--initialize-from-dill',
847 initializeFromDill!,
848 ],
849 if (assumeInitializeFromDillUpToDate) '--assume-initialize-from-dill-up-to-date',
850 if (additionalSourceUri != null) ...<String>[
851 '--source',
852 additionalSourceUri,
853 '--source',
854 'package:flutter/src/dart_plugin_registrant.dart',
855 '-Dflutter.dart_plugin_registrant=$additionalSourceUri',
856 ],
857 if (nativeAssetsUri != null) ...<String>[
858 '--native-assets',
859 nativeAssetsUri,
860 ],
861 if (platformDill != null) ...<String>[
862 '--platform',
863 platformDill!,
864 ],
865 if (unsafePackageSerialization) '--unsafe-package-serialization',
866 // See: https://github.com/flutter/flutter/issues/103994
867 '--verbosity=error',
868 ...?extraFrontEndOptions,
869 ];
870 _logger.printTrace(command.join(' '));
871 _server = await _processManager.start(command);
872 _server?.stdout
873 .transform<String>(utf8.decoder)
874 .transform<String>(const LineSplitter())
875 .listen(
876 _stdoutHandler.handler,
877 onDone: () {
878 // when outputFilename future is not completed, but stdout is closed
879 // process has died unexpectedly.
880 if (_stdoutHandler.compilerOutput?.isCompleted == false) {
881 _stdoutHandler.compilerOutput?.complete();
882 throwToolExit('the Dart compiler exited unexpectedly.');
883 }
884 });
885
886 _server?.stderr
887 .transform<String>(utf8.decoder)
888 .transform<String>(const LineSplitter())
889 .listen(_logger.printError);
890
891 unawaited(_server?.exitCode.then((int code) {
892 if (code != 0) {
893 throwToolExit('the Dart compiler exited unexpectedly.');
894 }
895 }));
896
897 if (nativeAssetsUri != null && nativeAssetsUri.isNotEmpty) {
898 _server?.stdin.writeln('native-assets $nativeAssetsUri');
899 _logger.printTrace('<- native-assets $nativeAssetsUri');
900 }
901
902 _server?.stdin.writeln('compile $scriptUri');
903 _logger.printTrace('<- compile $scriptUri');
904
905 return _stdoutHandler.compilerOutput?.future;
906 }
907
908 @override
909 Future<CompilerOutput?> compileExpression(
910 String expression,
911 List<String>? definitions,
912 List<String>? definitionTypes,
913 List<String>? typeDefinitions,
914 List<String>? typeBounds,
915 List<String>? typeDefaults,
916 String? libraryUri,
917 String? klass,
918 String? method,
919 bool isStatic,
920 ) async {
921 if (!_controller.hasListener) {
922 _controller.stream.listen(_handleCompilationRequest);
923 }
924
925 final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
926 final _CompileExpressionRequest request = _CompileExpressionRequest(
927 completer, expression, definitions, definitionTypes, typeDefinitions,
928 typeBounds, typeDefaults, libraryUri, klass, method, isStatic);
929 _controller.add(request);
930 return completer.future;
931 }
932
933 Future<CompilerOutput?> _compileExpression(_CompileExpressionRequest request) async {
934 _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false, readFile: true);
935
936 // 'compile-expression' should be invoked after compiler has been started,
937 // program was compiled.
938 final Process? server = _server;
939 if (server == null) {
940 return null;
941 }
942
943 final String inputKey = Uuid().generateV4();
944 server.stdin
945 ..writeln('compile-expression $inputKey')
946 ..writeln(request.expression);
947 request.definitions?.forEach(server.stdin.writeln);
948 server.stdin.writeln(inputKey);
949 request.definitionTypes?.forEach(server.stdin.writeln);
950 server.stdin.writeln(inputKey);
951 request.typeDefinitions?.forEach(server.stdin.writeln);
952 server.stdin.writeln(inputKey);
953 request.typeBounds?.forEach(server.stdin.writeln);
954 server.stdin.writeln(inputKey);
955 request.typeDefaults?.forEach(server.stdin.writeln);
956 server.stdin
957 ..writeln(inputKey)
958 ..writeln(request.libraryUri ?? '')
959 ..writeln(request.klass ?? '')
960 ..writeln(request.method ?? '')
961 ..writeln(request.isStatic);
962
963 return _stdoutHandler.compilerOutput?.future;
964 }
965
966 @override
967 Future<CompilerOutput?> compileExpressionToJs(
968 String libraryUri,
969 int line,
970 int column,
971 Map<String, String> jsModules,
972 Map<String, String> jsFrameValues,
973 String moduleName,
974 String expression,
975 ) {
976 if (!_controller.hasListener) {
977 _controller.stream.listen(_handleCompilationRequest);
978 }
979
980 final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
981 _controller.add(
982 _CompileExpressionToJsRequest(
983 completer, libraryUri, line, column, jsModules, jsFrameValues, moduleName, expression)
984 );
985 return completer.future;
986 }
987
988 Future<CompilerOutput?> _compileExpressionToJs(_CompileExpressionToJsRequest request) async {
989 _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
990
991 // 'compile-expression-to-js' should be invoked after compiler has been started,
992 // program was compiled.
993 final Process? server = _server;
994 if (server == null) {
995 return null;
996 }
997
998 final String inputKey = Uuid().generateV4();
999 server.stdin
1000 ..writeln('compile-expression-to-js $inputKey')
1001 ..writeln(request.libraryUri ?? '')
1002 ..writeln(request.line)
1003 ..writeln(request.column);
1004 request.jsModules?.forEach((String k, String v) { server.stdin.writeln('$k:$v'); });
1005 server.stdin.writeln(inputKey);
1006 request.jsFrameValues?.forEach((String k, String v) { server.stdin.writeln('$k:$v'); });
1007 server.stdin
1008 ..writeln(inputKey)
1009 ..writeln(request.moduleName ?? '')
1010 ..writeln(request.expression ?? '');
1011
1012 return _stdoutHandler.compilerOutput?.future;
1013 }
1014
1015 @override
1016 void accept() {
1017 if (_compileRequestNeedsConfirmation) {
1018 _server?.stdin.writeln('accept');
1019 _logger.printTrace('<- accept');
1020 }
1021 _compileRequestNeedsConfirmation = false;
1022 }
1023
1024 @override
1025 Future<CompilerOutput?> reject() {
1026 if (!_controller.hasListener) {
1027 _controller.stream.listen(_handleCompilationRequest);
1028 }
1029
1030 final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
1031 _controller.add(_RejectRequest(completer));
1032 return completer.future;
1033 }
1034
1035 Future<CompilerOutput?> _reject() async {
1036 if (!_compileRequestNeedsConfirmation) {
1037 return Future<CompilerOutput?>.value();
1038 }
1039 _stdoutHandler.reset(expectSources: false);
1040 _server?.stdin.writeln('reject');
1041 _logger.printTrace('<- reject');
1042 _compileRequestNeedsConfirmation = false;
1043 return _stdoutHandler.compilerOutput?.future;
1044 }
1045
1046 @override
1047 void reset() {
1048 _server?.stdin.writeln('reset');
1049 _logger.printTrace('<- reset');
1050 }
1051
1052 @override
1053 Future<Object> shutdown() async {
1054 // Server was never successfully created.
1055 final Process? server = _server;
1056 if (server == null) {
1057 return 0;
1058 }
1059 _logger.printTrace('killing pid ${server.pid}');
1060 server.kill();
1061 return server.exitCode;
1062 }
1063}
1064
1065/// Convert a file URI into a multi-root scheme URI if provided, otherwise
1066/// return unmodified.
1067@visibleForTesting
1068String toMultiRootPath(Uri fileUri, String? scheme, List<String> fileSystemRoots, bool windows) {
1069 if (scheme == null || fileSystemRoots.isEmpty || fileUri.scheme != 'file') {
1070 return fileUri.toString();
1071 }
1072 final String filePath = fileUri.toFilePath(windows: windows);
1073 for (final String fileSystemRoot in fileSystemRoots) {
1074 if (filePath.startsWith(fileSystemRoot)) {
1075 return '$scheme://${filePath.substring(fileSystemRoot.length)}';
1076 }
1077 }
1078 return fileUri.toString();
1079}
1080

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com