run method

  1. @override
Future<int> run()
override

Executes the run command to process distribution tasks.

This method performs the following operations:

  • Parses the configuration file specified by --config option
  • Filters tasks and jobs based on the --operation key
  • Executes build and publish operations for each selected job
  • Prints a summary and returns a matching exit code

A job that fails aborts the remaining jobs of its task, because a publish step must never run on the artifact of a build that did not succeed.

Implementation

@override
Future<int> run() async {
  // Claim stdout for the report before anything is printed, so even a
  // configuration error lands on stderr and leaves stdout parseable.
  if (argResults!['json'] as bool) {
    ColorizeLogger.reserveStdout = true;
    ColorizeLogger.retargetColors();
  }

  if (argResults!['status'] as bool) return _printSavedStatus();

  final ConfigParser configParser;
  try {
    configParser = await _buildConfigParser();
  } on ConfigException catch (e) {
    logger.logError(e.message);
    return 1;
  }

  if (argResults!['list'] as bool) {
    _printCatalog(configParser);
    return 0;
  }

  JobArguments.dryRun = isDryRun;

  try {
    _configureRunControl(configParser);
    await _prepareState(configParser);
    await _prepareVersion(configParser);
  } on Object catch (error) {
    logger.logError(error.toString());
    return 1;
  }

  if (!isDryRun) {
    _signalSubscription = ProcessSignal.sigint.watch().listen((_) async {
      if (_interrupted) return;
      _interrupted = true;
      if (!_interruptSignal.isCompleted) _interruptSignal.complete();
      logger.logWarning('interrupt received; saving state and stopping work');
      await _stateStore?.flush();
      await JobArguments.terminateAllProcesses();
    });
  }

  try {
    _printBanner(configParser);

    final results = <JobResult>[];
    final stopwatch = Stopwatch()..start();

    final hooksRunner = HooksRunner(
      logger,
      configParser.variables,
      dryRun: isDryRun,
    );
    final runPre = await hooksRunner.run(
      configParser.hooks.pre,
      phase: 'pre',
      scopeSucceeded: true,
      context: _hookContext(status: 'running'),
    );
    _lifecycleFailed = _hookFailed(runPre);

    if (!_lifecycleFailed && !_interrupted) {
      final concurrency = _concurrency(configParser);
      if (concurrency > 1) {
        results.addAll(await _runParallel(configParser, concurrency));
      } else {
        results.addAll(await _runSequential(configParser));
      }
    }

    final jobsSucceeded =
        results.isNotEmpty && !results.any((result) => result.isFatal);
    final runPost = await hooksRunner.run(
      configParser.hooks.post,
      phase: 'post',
      scopeSucceeded: jobsSucceeded && !_lifecycleFailed && !_interrupted,
      context: _hookContext(
        status: jobsSucceeded ? 'success' : 'failure',
        results: results,
      ),
      variableContext: _hookArtifactVariables(results: results),
    );
    if (_hookFailed(runPost)) _lifecycleFailed = true;

    var failed = results.isEmpty ||
        results.any((result) => result.isFatal) ||
        _lifecycleFailed ||
        _interrupted;
    if (!isDryRun && configParser.clean?.shouldRun(!failed) == true) {
      final cleanFailure = await _autoClean(configParser);
      if (cleanFailure != 0) {
        _lifecycleFailed = true;
        failed = true;
      }
    }

    stopwatch.stop();
    final summary = _renderSummary(results, stopwatch.elapsed);
    _printSummary(results, stopwatch.elapsed);

    await _writeJsonReport(results, stopwatch.elapsed, succeeded: !failed);

    if (!(argResults!['no-notify'] as bool) && !isDryRun) {
      await Notifier(logger, configParser.variables).dispatch(
        configParser.notifications,
        succeeded: !failed,
        summary: summary,
      );
    }

    return _interrupted
        ? 130
        : failed
            ? 1
            : 0;
  } on Object catch (error, stack) {
    logger.logError('Run aborted: $error');
    logger.logDebug(stack.toString());
    return _interrupted ? 130 : 1;
  } finally {
    try {
      await _stateStore?.flush();
    } on Object catch (error) {
      logger.logWarning('Could not save run state: $error');
    }
    try {
      await _signalSubscription?.cancel();
    } on Object catch (error) {
      logger.logDebug('Could not close signal listener: $error');
    }
  }
}