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

  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;

  _printBanner(configParser);

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

  for (final task in configParser.tasks) {
    final jobs = _resolveJobs(task);
    if (jobs.isEmpty) {
      logger.logWarning("${task.name} has no job to run, skipping");
      continue;
    }

    logger.logGroup(task.name);
    if (task.description != null) logger.logDetail(task.description!);

    var taskFailed = false;
    await ColorizeLogger.group(() async {
      for (final job in jobs) {
        logger.logEmpty();
        final result = await _runJob(task, job);
        results.add(result);

        if (result.isFatal) {
          taskFailed = true;
          final remaining = jobs.length - jobs.indexOf(job) - 1;
          if (remaining > 0) {
            logger.logWarning(
              "skipped $remaining remaining job(s) after ${job.label} failed",
            );
          }
          break;
        }
      }
    });

    logger.logEmpty();
    if (taskFailed && failFast) {
      logger.logWarning("stopping early (--fail-fast)");
      break;
    }
  }

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

  // A run that executed nothing is not a success. Reporting 0 here let a
  // pipeline "pass" while shipping nothing, which is the worst possible way
  // to find out a job was excluded by its task's `workflows`.
  final failed = results.isEmpty || results.any((result) => result.isFatal);

  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 failed ? 1 : 0;
}