aboutsummaryrefslogtreecommitdiff
path: root/src/Constants/Utils.cs
blob: 6047c3528b7a41a6debcd59ead043269e6b744ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
    {

        /// <summary>
        /// Runs a process by its name/exe file path, and writes its stdout/stderr to 
        /// the default build log
        /// </summary>
        /// <param name="process">The name of the process to run</param>
        /// <param name="args">CLI arguments to pass to the process</param>
        /// <returns>The process exit code</returns>
        public static async Task<int> RunProcessAsync(string process, string? workingDir, string[] args, IReadOnlyDictionary<string, string>? 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<string, string> 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");
            }
        }


        /// <summary>
        /// Throws a <see cref="BuildStepFailedException"/> if the value
        /// of <paramref name="status"/> is false
        /// </summary>
        /// <param name="status">If false throws exception</param>
        /// <param name="message">The message to display</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ThrowIfStepFailed(bool status, string message, string artifactName)
        {
            if (!status)
            {
                throw new BuildStepFailedException(message, artifactName);
            }
        }
    
    }
}