| import 'dart:io'; |
| |
| const ansiRed = '\x1b[31m'; |
| const ansiDefault = '\x1b[39;49m'; |
| |
| void printError(Object? message) { |
| if (stderr.supportsAnsiEscapes) { |
| message = '$ansiRed$message$ansiDefault'; |
| } |
| stderr.writeln(message); |
| } |
| |
| class StepFailure implements Exception { |
| StepFailure(this.name); |
| String name; |
| @override |
| String toString() => 'step failed: $name'; |
| } |
| |
| abstract class Step { |
| /// Runs this step, raises an exception if something fails. |
| Future<void> run(); |
| } |
| |
| class Callback implements Step { |
| Callback(this.name, this.function); |
| String name; |
| Future<void> Function() function; |
| @override |
| Future<void> run() => function(); |
| } |
| |
| class Command implements Step { |
| Command(this.exec, this.args, this.workingDirectory); |
| final String exec; |
| final List<String> args; |
| final Uri workingDirectory; |
| |
| @override |
| Future<void> run() async { |
| final result = await Process.run( |
| exec, |
| args, |
| workingDirectory: workingDirectory.toFilePath(), |
| runInShell: true, |
| ); |
| if (result.exitCode != 0) { |
| printError(result.stdout); |
| printError(result.stderr); |
| final commandString = "$exec ${args.join(" ")}"; |
| stderr.writeln('failure executing command: $commandString'); |
| throw StepFailure(commandString); |
| } |
| } |
| } |
| |
| class Runner { |
| static final gitRoot = getRepositoryRoot(); |
| Runner(this.name, this.defaultWorkingDir); |
| String name; |
| Uri defaultWorkingDir; |
| final steps = <Step>[]; |
| final cleanupSteps = <Step>[]; |
| |
| void chainCommand(String exec, List<String> args, {Uri? workingDirectory}) => |
| _addCommand(steps, exec, args, workingDirectory: workingDirectory); |
| |
| void chainCleanupCommand(String exec, List<String> args, |
| {Uri? workingDirectory}) => |
| _addCommand(cleanupSteps, exec, args, workingDirectory: workingDirectory); |
| |
| void _addCommand(List<Step> list, String exec, List<String> args, |
| {Uri? workingDirectory}) { |
| list.add(Command(exec, args, workingDirectory ?? defaultWorkingDir)); |
| } |
| |
| void chainCallback(String name, Future<void> Function() callback) { |
| steps.add(Callback(name, callback)); |
| } |
| |
| Future<void> run() async { |
| stderr.writeln('started: $name'); |
| var error = false; |
| for (var step in steps) { |
| try { |
| await step.run(); |
| } on StepFailure catch (e) { |
| stderr.writeln(e); |
| error = true; |
| exitCode = 1; |
| break; |
| } |
| } |
| stderr.writeln('${error ? "failed" : "complete"}: $name'); |
| for (var step in cleanupSteps) { |
| try { |
| await step.run(); |
| } on Exception catch (e) { |
| printError('ERROR: $e'); |
| } |
| } |
| } |
| } |
| |
| Uri getRepositoryRoot() { |
| final gitCommand = Process.runSync('git', ['rev-parse', '--show-toplevel']); |
| final output = gitCommand.stdout as String; |
| return Uri.directory(output.trim()); |
| } |