runCommand function
Run a command and capture output.
Implementation
Future<ProcessOutput> runCommand(
String command,
List<String> args, {
String? workingDirectory,
Map<String, String>? environment,
Duration? timeout,
int? maxOutputBytes,
}) async {
final sw = Stopwatch()..start();
try {
final process = await Process.start(
command,
args,
workingDirectory: workingDirectory,
environment: environment,
);
final stdoutBuf = StringBuffer();
final stderrBuf = StringBuffer();
var stdoutBytes = 0;
var stderrBytes = 0;
final stdoutSub = process.stdout.transform(utf8.decoder).listen((chunk) {
stdoutBytes += chunk.length;
if (maxOutputBytes == null || stdoutBytes <= maxOutputBytes) {
stdoutBuf.write(chunk);
}
});
final stderrSub = process.stderr.transform(utf8.decoder).listen((chunk) {
stderrBytes += chunk.length;
if (maxOutputBytes == null || stderrBytes <= maxOutputBytes) {
stderrBuf.write(chunk);
}
});
int exitCode;
bool timedOut = false;
if (timeout != null) {
final exitFuture = process.exitCode;
final result = await Future.any([
exitFuture,
Future.delayed(timeout, () => -1),
]);
if (result == -1) {
timedOut = true;
process.kill(ProcessSignal.sigterm);
await Future.delayed(const Duration(seconds: 2));
process.kill(ProcessSignal.sigkill);
exitCode = await exitFuture.timeout(
const Duration(seconds: 3),
onTimeout: () => -1,
);
} else {
exitCode = result;
}
} else {
exitCode = await process.exitCode;
}
await stdoutSub.cancel();
await stderrSub.cancel();
sw.stop();
var stdout = stdoutBuf.toString();
var stderr = stderrBuf.toString();
if (maxOutputBytes != null && stdoutBytes > maxOutputBytes) {
stdout += '\n[... output truncated at $maxOutputBytes bytes]';
}
if (maxOutputBytes != null && stderrBytes > maxOutputBytes) {
stderr += '\n[... output truncated at $maxOutputBytes bytes]';
}
return ProcessOutput(
exitCode: exitCode,
stdout: stdout,
stderr: stderr,
elapsed: sw.elapsed,
timedOut: timedOut,
);
} catch (e) {
sw.stop();
return ProcessOutput(
exitCode: -1,
stdout: '',
stderr: e.toString(),
elapsed: sw.elapsed,
);
}
}