| File: Command.cs | Web Access |
| Project: src\arcade\src\Common\Microsoft.Arcade.Common\Microsoft.Arcade.Common.csproj (Microsoft.Arcade.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Microsoft.Arcade.Common { public class Command : ICommand { public static readonly string[] RunnableSuffixes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new string[] { ".exe", ".cmd", ".bat" } : new string[] { string.Empty }; private readonly Process _process; private Action<string> _statusForward; private StringWriter _stdOutCapture; private StringWriter _stdErrCapture; private Action<string> _stdOutForward; private Action<string> _stdErrForward; private Action<string> _stdOutHandler; private Action<string> _stdErrHandler; private bool _running = false; private bool _quietBuildReporter = false; internal Command(string executable, string args) { // Set the things we need var psi = new ProcessStartInfo() { FileName = executable, Arguments = args }; _process = new Process() { StartInfo = psi }; } public ICommand QuietBuildReporter() { _quietBuildReporter = true; return this; } public CommandResult Execute() { ThrowIfRunning(); _running = true; if (_process.StartInfo.RedirectStandardOutput) { _process.OutputDataReceived += (sender, args) => { ProcessData(args.Data, _stdOutCapture, _stdOutForward, _stdOutHandler); }; } if (_process.StartInfo.RedirectStandardError) { _process.ErrorDataReceived += (sender, args) => { ProcessData(args.Data, _stdErrCapture, _stdErrForward, _stdErrHandler); }; } _process.EnableRaisingEvents = true; if (_process.StartInfo.RedirectStandardOutput || _process.StartInfo.RedirectStandardInput || _process.StartInfo.RedirectStandardError) { _process.StartInfo.UseShellExecute = false; } var sw = Stopwatch.StartNew(); ReportExecBegin(); _process.Start(); if (_process.StartInfo.RedirectStandardOutput) { _process.BeginOutputReadLine(); } if (_process.StartInfo.RedirectStandardError) { _process.BeginErrorReadLine(); } _process.WaitForExit(); var exitCode = _process.ExitCode; ReportExecEnd(exitCode); return new CommandResult( _process.StartInfo, exitCode, _stdOutCapture?.GetStringBuilder()?.ToString(), _stdErrCapture?.GetStringBuilder()?.ToString()); } public ICommand WorkingDirectory(string projectDirectory) { _process.StartInfo.WorkingDirectory = projectDirectory; return this; } public ICommand EnvironmentVariable(string name, string value) { _process.StartInfo.Environment[name] = value; _process.StartInfo.UseShellExecute = false; return this; } public ICommand ForwardStatus(TextWriter to = null) { ThrowIfRunning(); if (to == null) { _statusForward = Console.WriteLine; } else { _statusForward = to.WriteLine; } return this; } public ICommand CaptureStdOut() { ThrowIfRunning(); _process.StartInfo.RedirectStandardOutput = true; _stdOutCapture = new StringWriter(); return this; } public ICommand CaptureStdErr() { ThrowIfRunning(); _process.StartInfo.RedirectStandardError = true; _stdErrCapture = new StringWriter(); return this; } public ICommand ForwardStdOut(TextWriter to = null) { ThrowIfRunning(); _process.StartInfo.RedirectStandardOutput = true; if (to == null) { _stdOutForward = Console.WriteLine; } else { _stdOutForward = to.WriteLine; } return this; } public ICommand ForwardStdErr(TextWriter to = null) { ThrowIfRunning(); _process.StartInfo.RedirectStandardError = true; if (to == null) { _stdErrForward = Console.WriteLine; } else { _stdErrForward = to.WriteLine; } return this; } public ICommand OnOutputLine(Action<string> handler) { ThrowIfRunning(); _process.StartInfo.RedirectStandardOutput = true; if (_stdOutHandler != null) { throw new InvalidOperationException("Already handling stdout!"); } _stdOutHandler = handler; return this; } public ICommand OnErrorLine(Action<string> handler) { ThrowIfRunning(); _process.StartInfo.RedirectStandardError = true; if (_stdErrHandler != null) { throw new InvalidOperationException("Already handling stderr!"); } _stdErrHandler = handler; return this; } private string FormatProcessInfo(ProcessStartInfo info, bool includeWorkingDirectory) { string prefix = includeWorkingDirectory ? $"{info.WorkingDirectory}> {info.FileName}" : info.FileName; if (string.IsNullOrWhiteSpace(info.Arguments)) { return prefix; } return prefix + " " + info.Arguments; } private void ReportExecBegin() { if (!_quietBuildReporter && _statusForward != null) { _statusForward($"[EXEC Begin] {FormatProcessInfo(_process.StartInfo, includeWorkingDirectory: false)}"); } } private void ReportExecEnd(int exitCode) { if (!_quietBuildReporter && _statusForward != null) { bool success = exitCode == 0; var message = $"{FormatProcessInfo(_process.StartInfo, includeWorkingDirectory: !success)} exited with {exitCode}"; _statusForward($"[EXEC End] {message}"); } } private void ThrowIfRunning([CallerMemberName] string memberName = null) { if (_running) { throw new InvalidOperationException($"Unable to invoke {memberName} after the command has been run"); } } private void ProcessData(string data, StringWriter capture, Action<string> forward, Action<string> handler) { if (data == null) { return; } if (capture != null) { capture.WriteLine(data); } if (forward != null) { forward(data); } if (handler != null) { handler(data); } } } }