77 lines
2.2 KiB
C#
77 lines
2.2 KiB
C#
using System.Diagnostics;
|
|
|
|
namespace Sdt.Runner;
|
|
|
|
public sealed record RunResult(int ExitCode, TimeSpan Elapsed)
|
|
{
|
|
public bool Success => ExitCode == 0;
|
|
}
|
|
|
|
public static class ProcessRunner
|
|
{
|
|
/// <summary>
|
|
/// Runs a command with the given args, streaming stdout/stderr via <paramref name="onOutput"/>.
|
|
/// onOutput receives (line, isStderr).
|
|
/// </summary>
|
|
public static async Task<RunResult> RunAsync(
|
|
string command,
|
|
IEnumerable<string> args,
|
|
string workingDir,
|
|
Action<string, bool> onOutput,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = command,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
WorkingDirectory = workingDir,
|
|
};
|
|
|
|
foreach (var arg in args)
|
|
psi.ArgumentList.Add(arg);
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
using var process = new Process { StartInfo = psi };
|
|
|
|
void OnCancel(object? sender, ConsoleCancelEventArgs e)
|
|
{
|
|
e.Cancel = true; // Prevent SDT from exiting immediately
|
|
try { process.Kill(entireProcessTree: true); } catch { }
|
|
}
|
|
|
|
Console.CancelKeyPress += OnCancel;
|
|
|
|
try
|
|
{
|
|
process.Start();
|
|
|
|
var stdoutTask = DrainAsync(process.StandardOutput, line => onOutput(line, false), cancellationToken);
|
|
var stderrTask = DrainAsync(process.StandardError, line => onOutput(line, true), cancellationToken);
|
|
|
|
await Task.WhenAll(stdoutTask, stderrTask).ConfigureAwait(false);
|
|
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
finally
|
|
{
|
|
Console.CancelKeyPress -= OnCancel;
|
|
}
|
|
|
|
sw.Stop();
|
|
return new RunResult(process.ExitCode, sw.Elapsed);
|
|
}
|
|
|
|
private static async Task DrainAsync(StreamReader reader, Action<string> emit, CancellationToken ct)
|
|
{
|
|
string? line;
|
|
while ((line = await reader.ReadLineAsync(ct).ConfigureAwait(false)) is not null
|
|
&& !ct.IsCancellationRequested)
|
|
{
|
|
emit(line);
|
|
}
|
|
}
|
|
}
|