compile.dart 29.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:typed_data';
7

8
import 'package:meta/meta.dart';
9
import 'package:package_config/package_config.dart';
10
import 'package:process/process.dart';
11 12 13
import 'package:usage/uuid/uuid.dart';

import 'artifacts.dart';
14
import 'base/common.dart';
15
import 'base/file_system.dart';
16
import 'base/io.dart';
17
import 'base/logger.dart';
18
import 'base/platform.dart';
19
import 'build_info.dart';
20
import 'convert.dart';
21

Chris Bracken's avatar
Chris Bracken committed
22
/// The target model describes the set of core libraries that are available within
23 24 25 26
/// the SDK.
class TargetModel {
  /// Parse a [TargetModel] from a raw string.
  ///
27 28
  /// Throws an exception if passed a value other than 'flutter',
  /// 'flutter_runner', 'vm', or 'dartdevc'.
29 30 31 32 33 34
  factory TargetModel(String rawValue) {
    switch (rawValue) {
      case 'flutter':
        return flutter;
      case 'flutter_runner':
        return flutterRunner;
35 36
      case 'vm':
        return vm;
37 38
      case 'dartdevc':
        return dartdevc;
39
    }
40
    throw Exception('Unexpected target model $rawValue');
41 42 43 44
  }

  const TargetModel._(this._value);

45
  /// The Flutter patched Dart SDK.
46 47
  static const TargetModel flutter = TargetModel._('flutter');

48
  /// The Fuchsia patched SDK.
49 50
  static const TargetModel flutterRunner = TargetModel._('flutter_runner');

51
  /// The Dart VM.
52 53
  static const TargetModel vm = TargetModel._('vm');

54 55 56
  /// The development compiler for JavaScript.
  static const TargetModel dartdevc = TargetModel._('dartdevc');

57 58 59 60 61 62
  final String _value;

  @override
  String toString() => _value;
}

63
class CompilerOutput {
64
  const CompilerOutput(this.outputFilename, this.errorCount, this.sources, {this.expressionData});
65

66 67
  final String outputFilename;
  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
}

74 75
enum StdoutState { CollectDiagnostic, CollectDependencies }

76 77
/// Handles stdin/stdout communication with the frontend server.
class StdoutHandler {
78
  StdoutHandler({
79 80
    required Logger logger,
    required FileSystem fileSystem,
81 82
  }) : _logger = logger,
       _fileSystem = fileSystem {
83 84 85
    reset();
  }

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 95 96
  bool _suppressCompilerMessages = false;
  bool _expectSources = true;
  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 103
      return;
    }
104 105
    final String? messageBoundaryKey = boundaryKey;
    if (messageBoundaryKey != null && message.startsWith(messageBoundaryKey)) {
106 107 108 109 110 111
      if (_expectSources) {
        if (state == StdoutState.CollectDiagnostic) {
          state = StdoutState.CollectDependencies;
          return;
        }
      }
112 113
      if (message.length <= messageBoundaryKey.length) {
        compilerOutput?.complete(null);
114 115
        return;
      }
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 121 122 123 124 125 126 127 128
      if (_readFile) {
        expressionData = _fileSystem.file(fileName).readAsBytesSync();
      }
      final CompilerOutput output = CompilerOutput(
        fileName,
        errorCount,
        sources,
        expressionData: expressionData,
      );
129
      compilerOutput?.complete(output);
130 131 132 133
      return;
    }
    if (state == StdoutState.CollectDiagnostic) {
      if (!_suppressCompilerMessages) {
134 135 136
        _logger.printError(message);
      } else {
        _logger.printTrace(message);
137 138 139 140 141 142 143 144 145 146 147
      }
    } else {
      assert(state == StdoutState.CollectDependencies);
      switch (message[0]) {
        case '+':
          sources.add(Uri.parse(message.substring(1)));
          break;
        case '-':
          sources.remove(Uri.parse(message.substring(1)));
          break;
        default:
148
          _logger.printTrace('Unexpected prefix for $message uri - ignoring');
149
      }
150
    }
151
  }
152 153 154

  // This is needed to get ready to process next compilation result output,
  // with its own boundary key and new completer.
155
  void reset({ bool suppressCompilerMessages = false, bool expectSources = true, bool readFile = false }) {
156
    boundaryKey = null;
157
    compilerOutput = Completer<CompilerOutput?>();
158
    _suppressCompilerMessages = suppressCompilerMessages;
159
    _expectSources = expectSources;
160
    _readFile = readFile;
161
    state = StdoutState.CollectDiagnostic;
162
  }
163 164
}

165
/// List the preconfigured build options for a given build mode.
166
List<String> buildModeOptions(BuildMode mode, List<String> dartDefines) {
167 168 169 170
  switch (mode) {
    case BuildMode.debug:
      return <String>[
        '-Ddart.vm.profile=false',
171 172 173 174
        // This allows the CLI to override the value of this define for unit
        // testing the framework.
        if (!dartDefines.any((String define) => define.startsWith('dart.vm.product')))
          '-Ddart.vm.product=false',
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        '--enable-asserts',
      ];
    case BuildMode.profile:
      return <String>[
        '-Ddart.vm.profile=true',
        '-Ddart.vm.product=false',
      ];
    case BuildMode.release:
      return <String>[
        '-Ddart.vm.profile=false',
        '-Ddart.vm.product=true',
      ];
  }
  throw Exception('Unknown BuildMode: $mode');
}

191
/// A compiler interface for producing single (non-incremental) kernel files.
192
class KernelCompiler {
193
  KernelCompiler({
194 195 196 197 198 199 200
    required FileSystem fileSystem,
    required Logger logger,
    required ProcessManager processManager,
    required Artifacts artifacts,
    required List<String> fileSystemRoots,
    required String fileSystemScheme,
    @visibleForTesting StdoutHandler? stdoutHandler,
201 202 203 204 205
  }) : _logger = logger,
       _fileSystem = fileSystem,
       _artifacts = artifacts,
       _processManager = processManager,
       _fileSystemScheme = fileSystemScheme,
206
       _fileSystemRoots = fileSystemRoots,
207
       _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem);
208 209 210 211 212

  final FileSystem _fileSystem;
  final Artifacts _artifacts;
  final ProcessManager _processManager;
  final Logger _logger;
213 214
  final String _fileSystemScheme;
  final List<String> _fileSystemRoots;
215
  final StdoutHandler _stdoutHandler;
216

217 218 219 220 221
  Future<CompilerOutput?> compile({
    required String sdkRoot,
    String? mainPath,
    String? outputFilePath,
    String? depFilePath,
222
    TargetModel targetModel = TargetModel.flutter,
223 224
    bool linkPlatformKernelIn = false,
    bool aot = false,
225 226 227 228 229 230
    List<String>? extraFrontEndOptions,
    List<String>? fileSystemRoots,
    String? fileSystemScheme,
    String? initializeFromDill,
    String? platformDill,
    Directory? buildDir,
231
    bool checkDartPluginRegistry = false,
232 233 234 235 236
    required String? packagesPath,
    required BuildMode buildMode,
    required bool trackWidgetCreation,
    required List<String> dartDefines,
    required PackageConfig packageConfig,
237
  }) async {
238
    final String frontendServer = _artifacts.getArtifactPath(
239 240 241
      Artifact.frontendServerSnapshotForEngineDartSdk
    );
    // This is a URI, not a file path, so the forward slash is correct even on Windows.
242
    if (!sdkRoot.endsWith('/')) {
243
      sdkRoot = '$sdkRoot/';
244
    }
245
    final String engineDartPath = _artifacts.getHostArtifact(HostArtifact.engineDartBinary).path;
246
    if (!_processManager.canRun(engineDartPath)) {
247
      throwToolExit('Unable to find Dart binary at $engineDartPath');
248
    }
249
    String? mainUri;
250 251
    final File mainFile = _fileSystem.file(mainPath);
    final Uri mainFileUri = mainFile.uri;
252
    if (packagesPath != null) {
253
      mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
254
    }
255
    mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\');
256 257
    if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
      _fileSystem.file(outputFilePath).createSync(recursive: true);
258
    }
259 260 261 262 263 264 265 266 267
    if (buildDir != null && checkDartPluginRegistry) {
      // Check if there's a Dart plugin registrant.
      // This is contained in the file `generated_main.dart` under `.dart_tools/flutter_build/`.
      final File newMainDart = buildDir.parent.childFile('generated_main.dart');
      if (newMainDart.existsSync()) {
        mainUri = newMainDart.path;
      }
    }

268 269
    final List<String> command = <String>[
      engineDartPath,
270
      '--disable-dart-dev',
271 272 273
      frontendServer,
      '--sdk-root',
      sdkRoot,
274
      '--target=$targetModel',
275
      '--no-print-incremental-dependencies',
276
      for (final Object dartDefine in dartDefines)
277
        '-D$dartDefine',
278
      ...buildModeOptions(buildMode, dartDefines),
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
      if (trackWidgetCreation) '--track-widget-creation',
      if (!linkPlatformKernelIn) '--no-link-platform',
      if (aot) ...<String>[
        '--aot',
        '--tfa',
      ],
      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)
298
        for (final String root in fileSystemRoots) ...<String>[
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
          '--filesystem-root',
          root,
        ],
      if (fileSystemScheme != null) ...<String>[
        '--filesystem-scheme',
        fileSystemScheme,
      ],
      if (initializeFromDill != null) ...<String>[
        '--initialize-from-dill',
        initializeFromDill,
      ],
      if (platformDill != null) ...<String>[
        '--platform',
        platformDill,
      ],
314
      ...?extraFrontEndOptions,
315
      mainUri,
316
    ];
317

318 319
    _logger.printTrace(command.join(' '));
    final Process server = await _processManager.start(command);
320 321

    server.stderr
322
      .transform<String>(utf8.decoder)
323
      .listen(_logger.printError);
324
    server.stdout
325 326
      .transform<String>(utf8.decoder)
      .transform<String>(const LineSplitter())
327
      .listen(_stdoutHandler.handler);
328
    final int exitCode = await server.exitCode;
329
    if (exitCode == 0) {
330
      return _stdoutHandler.compilerOutput?.future;
331 332
    }
    return null;
333
  }
334 335
}

336 337 338 339
/// Class that allows to serialize compilation requests to the compiler.
abstract class _CompilationRequest {
  _CompilationRequest(this.completer);

340
  Completer<CompilerOutput?> completer;
341

342
  Future<CompilerOutput?> _run(DefaultResidentCompiler compiler);
343

344
  Future<void> run(DefaultResidentCompiler compiler) async {
345 346 347 348 349
    completer.complete(await _run(compiler));
  }
}

class _RecompileRequest extends _CompilationRequest {
350
  _RecompileRequest(
351
    Completer<CompilerOutput?> completer,
352
    this.mainUri,
353 354
    this.invalidatedFiles,
    this.outputPath,
355
    this.packageConfig,
356
    this.suppressErrors,
357
  ) : super(completer);
358

359
  Uri mainUri;
360
  List<Uri>? invalidatedFiles;
361
  String outputPath;
362
  PackageConfig packageConfig;
363
  bool suppressErrors;
364 365

  @override
366
  Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
367 368 369 370
      compiler._recompile(this);
}

class _CompileExpressionRequest extends _CompilationRequest {
371
  _CompileExpressionRequest(
372
    Completer<CompilerOutput?> completer,
373 374 375 376 377
    this.expression,
    this.definitions,
    this.typeDefinitions,
    this.libraryUri,
    this.klass,
378
    this.isStatic,
379
  ) : super(completer);
380 381

  String expression;
382 383 384 385
  List<String>? definitions;
  List<String>? typeDefinitions;
  String? libraryUri;
  String? klass;
386 387 388
  bool isStatic;

  @override
389
  Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
390 391 392
      compiler._compileExpression(this);
}

393 394
class _CompileExpressionToJsRequest extends _CompilationRequest {
  _CompileExpressionToJsRequest(
395
    Completer<CompilerOutput?> completer,
396 397 398 399 400 401 402 403 404
    this.libraryUri,
    this.line,
    this.column,
    this.jsModules,
    this.jsFrameValues,
    this.moduleName,
    this.expression,
  ) : super(completer);

405
  final String? libraryUri;
406 407
  final int line;
  final int column;
408 409 410 411
  final Map<String, String>? jsModules;
  final Map<String, String>? jsFrameValues;
  final String? moduleName;
  final String? expression;
412 413

  @override
414
  Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
415 416 417
      compiler._compileExpressionToJs(this);
}

418
class _RejectRequest extends _CompilationRequest {
419
  _RejectRequest(Completer<CompilerOutput?> completer) : super(completer);
420 421

  @override
422
  Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async =>
423 424 425
      compiler._reject();
}

426 427 428 429 430
/// 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.
431 432
abstract class ResidentCompiler {
  factory ResidentCompiler(String sdkRoot, {
433 434 435 436 437 438
    required BuildMode buildMode,
    required Logger logger,
    required ProcessManager processManager,
    required Artifacts artifacts,
    required Platform platform,
    required FileSystem fileSystem,
439
    bool testCompilation,
440
    bool trackWidgetCreation,
441 442
    String packagesPath,
    List<String> fileSystemRoots,
443
    String? fileSystemScheme,
444
    String initializeFromDill,
445
    TargetModel targetModel,
446
    bool unsafePackageSerialization,
447
    List<String> extraFrontEndOptions,
448
    String platformDill,
449
    List<String>? dartDefines,
450
    String librariesSpec,
451
  }) = DefaultResidentCompiler;
452

453 454 455 456 457
  // TODO(jonahwilliams): 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);

458 459
  /// If invoked for the first time, it compiles Dart script identified by
  /// [mainPath], [invalidatedFiles] list is ignored.
460 461 462
  /// 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.
463
  /// Binary file name is returned if compilation was successful, otherwise
464
  /// null is returned.
465
  Future<CompilerOutput?> recompile(
466
    Uri mainUri,
467 468 469 470 471
    List<Uri>? invalidatedFiles, {
    required String outputPath,
    required PackageConfig packageConfig,
    required String projectRootPath,
    required FileSystem fs,
472
    bool suppressErrors = false,
473 474
  });

475
  Future<CompilerOutput?> compileExpression(
476
    String expression,
477 478 479 480
    List<String>? definitions,
    List<String>? typeDefinitions,
    String? libraryUri,
    String? klass,
481 482 483
    bool isStatic,
  );

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
  /// 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
503
  /// compilation result and a number of errors.
504
  Future<CompilerOutput?> compileExpressionToJs(
505 506 507 508 509 510 511 512 513
    String libraryUri,
    int line,
    int column,
    Map<String, String> jsModules,
    Map<String, String> jsFrameValues,
    String moduleName,
    String expression,
  );

514 515 516 517 518 519 520 521
  /// 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.
522
  Future<CompilerOutput?> reject();
523 524 525 526 527 528

  /// 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();

529
  Future<Object> shutdown();
530 531 532 533 534 535
}

@visibleForTesting
class DefaultResidentCompiler implements ResidentCompiler {
  DefaultResidentCompiler(
    String sdkRoot, {
536 537 538 539 540 541
    required this.buildMode,
    required Logger logger,
    required ProcessManager processManager,
    required Artifacts artifacts,
    required Platform platform,
    required FileSystem fileSystem,
542
    this.testCompilation = false,
543
    this.trackWidgetCreation = true,
544
    this.packagesPath,
545
    this.fileSystemRoots = const <String>[],
546 547 548
    this.fileSystemScheme,
    this.initializeFromDill,
    this.targetModel = TargetModel.flutter,
549
    this.unsafePackageSerialization = false,
550
    this.extraFrontEndOptions,
551
    this.platformDill,
552
    List<String>? dartDefines,
553
    this.librariesSpec,
554
    @visibleForTesting StdoutHandler? stdoutHandler,
555
  }) : assert(sdkRoot != null),
556 557 558
       _logger = logger,
       _processManager = processManager,
       _artifacts = artifacts,
559
       _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem),
560
       _platform = platform,
561
       dartDefines = dartDefines ?? const <String>[],
562 563 564
       // This is a URI, not a file path, so the forward slash is correct even on Windows.
       sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/';

565 566 567
  final Logger _logger;
  final ProcessManager _processManager;
  final Artifacts _artifacts;
568
  final Platform _platform;
569

570
  final bool testCompilation;
571 572
  final BuildMode buildMode;
  final bool trackWidgetCreation;
573
  final String? packagesPath;
574 575
  final TargetModel targetModel;
  final List<String> fileSystemRoots;
576 577
  final String? fileSystemScheme;
  final String? initializeFromDill;
578
  final bool unsafePackageSerialization;
579
  final List<String>? extraFrontEndOptions;
580
  final List<String> dartDefines;
581
  final String? librariesSpec;
582

583 584 585 586 587
  @override
  void addFileSystemRoot(String root) {
    fileSystemRoots.add(root);
  }

588 589 590 591 592 593 594 595
  /// 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.
596
  final String? platformDill;
597

598
  Process? _server;
599 600 601 602 603 604
  final StdoutHandler _stdoutHandler;
  bool _compileRequestNeedsConfirmation = false;

  final StreamController<_CompilationRequest> _controller = StreamController<_CompilationRequest>();

  @override
605
  Future<CompilerOutput?> recompile(
606
    Uri mainUri,
607 608 609
    List<Uri>? invalidatedFiles, {
    required String outputPath,
    required PackageConfig packageConfig,
610
    bool suppressErrors = false,
611 612
    String? projectRootPath,
    FileSystem? fs,
613
  }) async {
614
    assert(outputPath != null);
615 616 617
    if (!_controller.hasListener) {
      _controller.stream.listen(_handleCompilationRequest);
    }
618 619 620 621 622 623 624 625 626 627 628 629 630 631
    // `generated_main.dart` contains the Dart plugin registry.
    if (projectRootPath != null && fs != null) {
      final File generatedMainDart = fs.file(
        fs.path.join(
          projectRootPath,
          '.dart_tool',
          'flutter_build',
          'generated_main.dart',
        ),
      );
      if (generatedMainDart != null && generatedMainDart.existsSync()) {
        mainUri = generatedMainDart.uri;
      }
    }
632
    final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
633
    _controller.add(
634
      _RecompileRequest(completer, mainUri, invalidatedFiles, outputPath, packageConfig, suppressErrors)
635 636 637 638
    );
    return completer.future;
  }

639
  Future<CompilerOutput?> _recompile(_RecompileRequest request) async {
640
    _stdoutHandler.reset();
641
    _compileRequestNeedsConfirmation = true;
642
    _stdoutHandler._suppressCompilerMessages = request.suppressErrors;
643

644 645 646
    final String mainUri = request.packageConfig.toPackageUri(request.mainUri)?.toString() ??
      toMultiRootPath(request.mainUri, fileSystemScheme, fileSystemRoots, _platform.isWindows);

647 648
    final Process? server = _server;
    if (server == null) {
649
      return _compile(mainUri, request.outputPath);
650
    }
651
    final String inputKey = Uuid().generateV4();
652

653
    server.stdin.writeln('recompile $mainUri $inputKey');
654
    _logger.printTrace('<- recompile $mainUri $inputKey');
655 656 657 658 659 660 661 662 663 664 665 666
    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);
667
      }
668
    }
669
    server.stdin.writeln(inputKey);
670
    _logger.printTrace('<- $inputKey');
671

672
    return _stdoutHandler.compilerOutput?.future;
673 674
  }

675
  final List<_CompilationRequest> _compilationQueue = <_CompilationRequest>[];
676

677
  Future<void> _handleCompilationRequest(_CompilationRequest request) async {
678 679
    final bool isEmpty = _compilationQueue.isEmpty;
    _compilationQueue.add(request);
680 681 682 683
    // 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) {
684 685
      while (_compilationQueue.isNotEmpty) {
        final _CompilationRequest request = _compilationQueue.first;
686
        await request.run(this);
687
        _compilationQueue.removeAt(0);
688 689 690 691
      }
    }
  }

692
  Future<CompilerOutput?> _compile(
693
    String scriptUri,
694
    String? outputPath,
695
  ) async {
696
    final String frontendServer = _artifacts.getArtifactPath(
697 698
      Artifact.frontendServerSnapshotForEngineDartSdk
    );
699
    final List<String> command = <String>[
700
      _artifacts.getHostArtifact(HostArtifact.engineDartBinary).path,
701
      '--disable-dart-dev',
702 703
      frontendServer,
      '--sdk-root',
704
      sdkRoot,
705
      '--incremental',
706 707
      if (testCompilation)
        '--no-print-incremental-dependencies',
708
      '--target=$targetModel',
709 710 711 712
      // TODO(jonahwilliams): remove once this becomes the default behavior
      // in the frontend_server.
      // https://github.com/flutter/flutter/issues/52693
      '--debugger-module-names',
713 714 715 716
      // TODO(annagrin): remove once this becomes the default behavior
      // in the frontend_server.
      // https://github.com/flutter/flutter/issues/59902
      '--experimental-emit-debug-metadata',
717
      for (final Object dartDefine in dartDefines)
718
        '-D$dartDefine',
719 720 721 722
      if (outputPath != null) ...<String>[
        '--output-dill',
        outputPath,
      ],
723 724
      if (librariesSpec != null) ...<String>[
        '--libraries-spec',
725
        librariesSpec!,
726
      ],
727
      if (packagesPath != null) ...<String>[
728
        '--packages',
729
        packagesPath!,
730
      ],
731
      ...buildModeOptions(buildMode, dartDefines),
732 733
      if (trackWidgetCreation) '--track-widget-creation',
      if (fileSystemRoots != null)
734
        for (final String root in fileSystemRoots) ...<String>[
735 736 737
          '--filesystem-root',
          root,
        ],
738
      if (fileSystemScheme != null) ...<String>[
739
        '--filesystem-scheme',
740
        fileSystemScheme!,
741
      ],
742
      if (initializeFromDill != null) ...<String>[
743
        '--initialize-from-dill',
744
        initializeFromDill!,
745
      ],
746 747
      if (platformDill != null) ...<String>[
        '--platform',
748
        platformDill!,
749 750
      ],
      if (unsafePackageSerialization == true) '--unsafe-package-serialization',
751
      ...?extraFrontEndOptions,
752
    ];
753 754
    _logger.printTrace(command.join(' '));
    _server = await _processManager.start(command);
755
    _server?.stdout
756 757
      .transform<String>(utf8.decoder)
      .transform<String>(const LineSplitter())
758
      .listen(
759
        _stdoutHandler.handler,
760 761 762
        onDone: () {
          // when outputFilename future is not completed, but stdout is closed
          // process has died unexpectedly.
763 764
          if (_stdoutHandler.compilerOutput?.isCompleted == false) {
            _stdoutHandler.compilerOutput?.complete(null);
765
            throwToolExit('the Dart compiler exited unexpectedly.');
766 767 768
          }
        });

769
    _server?.stderr
770 771
      .transform<String>(utf8.decoder)
      .transform<String>(const LineSplitter())
772
      .listen(_logger.printError);
773

774
    unawaited(_server?.exitCode.then((int code) {
775 776 777 778 779
      if (code != 0) {
        throwToolExit('the Dart compiler exited unexpectedly.');
      }
    }));

780
    _server?.stdin.writeln('compile $scriptUri');
781
    _logger.printTrace('<- compile $scriptUri');
782

783
    return _stdoutHandler.compilerOutput?.future;
784 785
  }

786
  @override
787
  Future<CompilerOutput?> compileExpression(
788
    String expression,
789 790 791 792
    List<String>? definitions,
    List<String>? typeDefinitions,
    String? libraryUri,
    String? klass,
793
    bool isStatic,
794
  ) async {
795 796 797 798
    if (!_controller.hasListener) {
      _controller.stream.listen(_handleCompilationRequest);
    }

799
    final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
800 801 802
    final _CompileExpressionRequest request =  _CompileExpressionRequest(
        completer, expression, definitions, typeDefinitions, libraryUri, klass, isStatic);
    _controller.add(request);
803 804 805
    return completer.future;
  }

806
  Future<CompilerOutput?> _compileExpression(_CompileExpressionRequest request) async {
807
    _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false, readFile: true);
808 809 810

    // 'compile-expression' should be invoked after compiler has been started,
    // program was compiled.
811 812
    final Process? server = _server;
    if (server == null) {
813
      return null;
814
    }
815

816
    final String inputKey = Uuid().generateV4();
817
    server.stdin
818 819
      ..writeln('compile-expression $inputKey')
      ..writeln(request.expression);
820 821 822 823
    request.definitions?.forEach(server.stdin.writeln);
    server.stdin.writeln(inputKey);
    request.typeDefinitions?.forEach(server.stdin.writeln);
    server.stdin
824 825 826
      ..writeln(inputKey)
      ..writeln(request.libraryUri ?? '')
      ..writeln(request.klass ?? '')
827
      ..writeln(request.isStatic);
828

829
    return _stdoutHandler.compilerOutput?.future;
830
  }
831

832
  @override
833
  Future<CompilerOutput?> compileExpressionToJs(
834 835 836 837 838 839 840 841 842 843 844 845
    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);
    }

846
    final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
847 848 849 850 851 852 853
    _controller.add(
        _CompileExpressionToJsRequest(
            completer, libraryUri, line, column, jsModules, jsFrameValues, moduleName, expression)
    );
    return completer.future;
  }

854
  Future<CompilerOutput?> _compileExpressionToJs(_CompileExpressionToJsRequest request) async {
855
    _stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false);
856 857 858

    // 'compile-expression-to-js' should be invoked after compiler has been started,
    // program was compiled.
859 860
    final Process? server = _server;
    if (server == null) {
861 862 863 864
      return null;
    }

    final String inputKey = Uuid().generateV4();
865
    server.stdin
866 867 868 869
      ..writeln('compile-expression-to-js $inputKey')
      ..writeln(request.libraryUri ?? '')
      ..writeln(request.line)
      ..writeln(request.column);
870 871 872 873
    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
874 875 876
      ..writeln(inputKey)
      ..writeln(request.moduleName ?? '')
      ..writeln(request.expression ?? '');
877

878
    return _stdoutHandler.compilerOutput?.future;
879 880
  }

881
  @override
882
  void accept() {
883
    if (_compileRequestNeedsConfirmation) {
884
      _server?.stdin.writeln('accept');
885
      _logger.printTrace('<- accept');
886 887
    }
    _compileRequestNeedsConfirmation = false;
888 889
  }

890
  @override
891
  Future<CompilerOutput?> reject() {
892 893 894 895
    if (!_controller.hasListener) {
      _controller.stream.listen(_handleCompilationRequest);
    }

896
    final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>();
897 898 899 900
    _controller.add(_RejectRequest(completer));
    return completer.future;
  }

901
  Future<CompilerOutput?> _reject() async {
902
    if (!_compileRequestNeedsConfirmation) {
903
      return Future<CompilerOutput?>.value(null);
904
    }
905
    _stdoutHandler.reset(expectSources: false);
906
    _server?.stdin.writeln('reject');
907
    _logger.printTrace('<- reject');
908
    _compileRequestNeedsConfirmation = false;
909
    return _stdoutHandler.compilerOutput?.future;
910
  }
911

912
  @override
913
  void reset() {
914
    _server?.stdin.writeln('reset');
915
    _logger.printTrace('<- reset');
916
  }
917

918
  @override
919
  Future<Object> shutdown() async {
Chris Bracken's avatar
Chris Bracken committed
920
    // Server was never successfully created.
921 922
    final Process? server = _server;
    if (server == null) {
923 924
      return 0;
    }
925 926 927
    _logger.printTrace('killing pid ${server.pid}');
    server.kill();
    return server.exitCode;
928
  }
929
}
930

931
/// Convert a file URI into a multi-root scheme URI if provided, otherwise
932 933
/// return unmodified.
@visibleForTesting
934
String toMultiRootPath(Uri fileUri, String? scheme, List<String> fileSystemRoots, bool windows) {
935 936 937 938 939 940
  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)) {
941
      return '$scheme://${filePath.substring(fileSystemRoot.length)}';
942 943 944 945
    }
  }
  return fileUri.toString();
}