generateAndRun function

Future<int> generateAndRun(
  1. List<String> args, {
  2. List<String>? experiments,
  3. Logger? logger,
  4. Future<String> generateBuildScript() = generateBuildScript,
  5. void handleUncaughtError(
    1. Object error,
    2. StackTrace stackTrace
    ) = _defaultHandleUncaughtError,
})

Generates the build script, precompiles it if needed, and runs it.

The handleUncaughtError function will be invoked when the build script terminates with an uncaught error.

Will retry once on IsolateSpawnExceptions to handle SDK updates.

Returns the exit code from running the build script.

If an exit code of 75 is returned, this function should be re-ran.

Implementation

Future<int> generateAndRun(
  List<String> args, {
  List<String>? experiments,
  Logger? logger,
  Future<String> Function() generateBuildScript = generateBuildScript,
  void Function(Object error, StackTrace stackTrace) handleUncaughtError =
      _defaultHandleUncaughtError,
}) async {
  experiments ??= [];
  logger ??= _logger;
  ReceivePort? exitPort;
  ReceivePort? errorPort;
  ReceivePort? messagePort;
  StreamSubscription? errorListener;
  int? scriptExitCode;

  var tryCount = 0;
  var succeeded = false;
  while (tryCount < 2 && !succeeded) {
    tryCount++;
    exitPort?.close();
    errorPort?.close();
    messagePort?.close();
    await errorListener?.cancel();

    try {
      var buildScript = File(scriptLocation);
      var oldContents = '';
      if (buildScript.existsSync()) {
        oldContents = buildScript.readAsStringSync();
      }
      var newContents = await generateBuildScript();
      // Only trigger a build script update if necessary.
      if (newContents != oldContents) {
        buildScript
          ..createSync(recursive: true)
          ..writeAsStringSync(newContents);
      }
    } on CannotBuildException {
      return ExitCode.config.code;
    }

    scriptExitCode = await _createKernelIfNeeded(logger, experiments);
    if (scriptExitCode != 0) return scriptExitCode!;

    exitPort = ReceivePort();
    errorPort = ReceivePort();
    messagePort = ReceivePort();
    errorListener = errorPort.listen((e) {
      e = e as List<Object?>;
      final error = e[0] ?? TypeError();
      final trace = Trace.parse(e[1] as String? ?? '').terse;

      handleUncaughtError(error, trace);
      if (scriptExitCode == 0) scriptExitCode = 1;
    });
    try {
      await Isolate.spawnUri(Uri.file(p.absolute(scriptKernelLocation)), args,
          messagePort.sendPort,
          errorsAreFatal: true,
          onExit: exitPort.sendPort,
          onError: errorPort.sendPort);
      succeeded = true;
    } on IsolateSpawnException catch (e) {
      if (tryCount > 1) {
        logger.severe(
            'Failed to spawn build script after retry. '
            'This is likely due to a misconfigured builder definition. '
            'See the generated script at $scriptLocation to find errors.',
            e);
        messagePort.sendPort.send(ExitCode.config.code);
        exitPort.sendPort.send(null);
      } else {
        logger.warning(
            'Error spawning build script isolate, this is likely due to a Dart '
            'SDK update. Deleting precompiled script and retrying...');
      }
      await File(scriptKernelLocation).rename(scriptKernelCachedLocation);
    }
  }

  StreamSubscription? exitCodeListener;
  exitCodeListener = messagePort!.listen((isolateExitCode) {
    if (isolateExitCode is int) {
      scriptExitCode = isolateExitCode;
    } else {
      throw StateError(
          'Bad response from isolate, expected an exit code but got '
          '$isolateExitCode');
    }
    exitCodeListener!.cancel();
    exitCodeListener = null;
  });
  await exitPort?.first;
  await errorListener?.cancel();
  await exitCodeListener?.cancel();

  return scriptExitCode ?? 1;
}