run method
Executes the run command to process distribution tasks.
This method performs the following operations:
- Parses the configuration file specified by
--configoption - Filters tasks and jobs based on the
--operationkey - 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');
}
}
}