From 1d0995d4cce1f09640387f30d1b340c8f52ec7d8 Mon Sep 17 00:00:00 2001 From: Mark Lechtermann Date: Mon, 8 May 2023 08:52:47 +0200 Subject: [PATCH] build: debug --- .github/workflows/code-style.yaml | 3 + test/ProcessRunner.cs | 194 ++++++++++++++++++++++++++++++ test/Program.cs | 22 ++++ test/test.csproj | 10 ++ 4 files changed, 229 insertions(+) create mode 100644 test/ProcessRunner.cs create mode 100644 test/Program.cs create mode 100644 test/test.csproj diff --git a/.github/workflows/code-style.yaml b/.github/workflows/code-style.yaml index 966297d..9e64740 100644 --- a/.github/workflows/code-style.yaml +++ b/.github/workflows/code-style.yaml @@ -24,5 +24,8 @@ jobs: - name: Check dotnet version run: dotnet --version + - name: Check code format (editorconfig) + run: dotnet run --project .\test\test.csproj + - name: Check code format (editorconfig) run: dotnet format --verify-no-changes diff --git a/test/ProcessRunner.cs b/test/ProcessRunner.cs new file mode 100644 index 0000000..73ce174 --- /dev/null +++ b/test/ProcessRunner.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + public readonly struct ProcessResult + { + public Process Process { get; } + public int ExitCode { get; } + public ReadOnlyCollection OutputLines { get; } + public ReadOnlyCollection ErrorLines { get; } + + public ProcessResult(Process process, int exitCode, ReadOnlyCollection outputLines, ReadOnlyCollection errorLines) + { + Process = process; + ExitCode = exitCode; + OutputLines = outputLines; + ErrorLines = errorLines; + } + } + + public readonly struct ProcessInfo + { + public Process Process { get; } + public ProcessStartInfo StartInfo { get; } + public Task Result { get; } + + public int Id => Process.Id; + + public ProcessInfo(Process process, ProcessStartInfo startInfo, Task result) + { + Process = process; + StartInfo = startInfo; + Result = result; + } + } + + public static class ProcessRunner + { + public static void OpenFile(string file) + { + if (File.Exists(file)) + { + Process.Start(file); + } + } + + public static ProcessInfo CreateProcess( + string executable, + string arguments, + bool lowPriority = false, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) => + CreateProcess( + CreateProcessStartInfo(executable, arguments, workingDirectory, captureOutput, displayWindow, environmentVariables), + lowPriority: lowPriority, + onProcessStartHandler: onProcessStartHandler, + cancellationToken: cancellationToken); + + public static ProcessInfo CreateProcess( + ProcessStartInfo processStartInfo, + bool lowPriority = false, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) + { + var errorLines = new List(); + var outputLines = new List(); + var process = new Process(); + var tcs = new TaskCompletionSource(); + + process.EnableRaisingEvents = true; + process.StartInfo = processStartInfo; + + process.OutputDataReceived += (s, e) => + { + if (e.Data != null) + { + outputLines.Add(e.Data); + } + }; + + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) + { + errorLines.Add(e.Data); + } + }; + + process.Exited += (s, e) => + { + // We must call WaitForExit to make sure we've received all OutputDataReceived/ErrorDataReceived calls + // or else we'll be returning a list we're still modifying. For paranoia, we'll start a task here rather + // than enter right back into the Process type and start a wait which isn't guaranteed to be safe. + Task.Run(() => + { + process.WaitForExit(); + var result = new ProcessResult( + process, + process.ExitCode, + new ReadOnlyCollection(outputLines), + new ReadOnlyCollection(errorLines)); + tcs.TrySetResult(result); + }); + }; + + _ = cancellationToken.Register(() => + { + if (tcs.TrySetCanceled()) + { + // If the underlying process is still running, we should kill it + if (!process.HasExited) + { + try + { + process.Kill(); + } + catch (InvalidOperationException) + { + // Ignore, since the process is already dead + } + } + } + }); + + process.Start(); + onProcessStartHandler?.Invoke(process); + + if (lowPriority) + { + process.PriorityClass = ProcessPriorityClass.BelowNormal; + } + + if (processStartInfo.RedirectStandardOutput) + { + process.BeginOutputReadLine(); + } + + if (processStartInfo.RedirectStandardError) + { + process.BeginErrorReadLine(); + } + + return new ProcessInfo(process, processStartInfo, tcs.Task); + } + + public static ProcessStartInfo CreateProcessStartInfo( + string executable, + string arguments, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null) + { + var processStartInfo = new ProcessStartInfo(executable, arguments); + + if (!string.IsNullOrEmpty(workingDirectory)) + { + processStartInfo.WorkingDirectory = workingDirectory; + } + + if (environmentVariables != null) + { + foreach (var pair in environmentVariables) + { + processStartInfo.EnvironmentVariables[pair.Key] = pair.Value; + } + } + + if (captureOutput) + { + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + } + else + { + processStartInfo.CreateNoWindow = !displayWindow; + processStartInfo.UseShellExecute = displayWindow; + } + + return processStartInfo; + } + } +} diff --git a/test/Program.cs b/test/Program.cs new file mode 100644 index 0000000..7f3a9b3 --- /dev/null +++ b/test/Program.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis.Tools.Utilities; + +internal class Program +{ + private static int Main(string[] args) + { + TryGetDotNetCliVersion(out string a); + Console.WriteLine(a); + + return 0; + } + + + internal static bool TryGetDotNetCliVersion(out string dotnetVersion) + { + var processInfo = ProcessRunner.CreateProcess("dotnet", "--version", captureOutput: true, displayWindow: false); + var versionResult = processInfo.Result.GetAwaiter().GetResult(); + + dotnetVersion = versionResult.OutputLines[0].Trim(); + return true; + } +} diff --git a/test/test.csproj b/test/test.csproj new file mode 100644 index 0000000..74abf5c --- /dev/null +++ b/test/test.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + +