using System; using System.Threading; using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; using System.Runtime.CompilerServices; using static VNLib.Tools.Build.Executor.Constants.Config; namespace VNLib.Tools.Build.Executor.Constants { internal static class Utils { /// /// Runs a process by its name/exe file path, and writes its stdout/stderr to /// the default build log /// /// The name of the process to run /// CLI arguments to pass to the process /// The process exit code public static async Task RunProcessAsync( string process, string? workingDir, string[] args, IReadOnlyDictionary? env = null ) { //Init new console cancellation token using ConsoleCancelToken ctToken = new(); ProcessStartInfo psi = new(process) { //Redirect streams RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true, //Create a child process, not shell UseShellExecute = false, WorkingDirectory = workingDir ?? string.Empty, }; if (env != null) { //Add all env variables to process foreach (KeyValuePair kv in env) { psi.Environment.Add(kv.Key, kv.Value); } } //Add arguments foreach (string arg in args) { psi.ArgumentList.Add(arg); } using Process proc = new(); proc.StartInfo = psi; //Start the process proc.Start(); Log.Debug("Starting process {proc}, with args {args}", proc.ProcessName, args); Console.WriteLine(); //Log std out Task stdout = LogStdOutAsync(proc, ctToken.Token); Task stdErr = LogStdErrAsync(proc, ctToken.Token); //Wait for the process to exit Task wfe = proc.WaitForExitAsync(ctToken.Token); //Wait for stderr/out/proc to exit await Task.WhenAll(stdout, stdErr, wfe); Console.WriteLine(); Log.Debug("[CHILD]:{id}:{p} exited w/ code {code}", proc.ProcessName, proc.Id, proc.ExitCode); //Return status code return proc.ExitCode; } private static async Task LogStdOutAsync(Process psi, CancellationToken cancellation) { try { string procName = psi.ProcessName; int id = psi.Id; do { //Read lines from the process string? line = await psi.StandardOutput.ReadLineAsync(cancellation); if (line == null) { break; } //Print to log file Console.WriteLine(line); } while (!psi.HasExited); } catch (Exception ex) { Log.Error(ex, "An exception was raised while reading the process standard output"); } } private static async Task LogStdErrAsync(Process psi, CancellationToken cancellation) { try { string procName = psi.ProcessName; int id = psi.Id; do { //Read lines from the process string? line = await psi.StandardError.ReadLineAsync(cancellation); if (line == null) { break; } //Print to log file Console.WriteLine(line); } while (!psi.HasExited); } catch (Exception ex) { Log.Error(ex, "An exception was raised while reading the process standard output"); } } /// /// Throws a if the value /// of is false /// /// If false throws exception /// The message to display [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ThrowIfStepFailed(bool status, string message, string artifactName) { if (!status) { throw new BuildStepFailedException(message, artifactName); } } } }