162 private static readonly
object _lock =
new();
164 private readonly
string _toolPath;
168 _toolPath = toolPath;
171 public IReadOnlyCollection<Output>? Execute(
174 string? workingDirectory =
null,
175 IReadOnlyDictionary<string, string>? environmentVariables =
null,
177 bool? logOutput =
null,
178 bool? logInvocation =
null,
179 Action<OutputType, string>? logger =
null,
180 Action<IProcess>? exitHandler =
null,
183 Action<StreamWriter>? input =
null,
184 Encoding? standardOutputEncoding =
null,
185 Encoding? standardInputEncoding =
null
188 workingDirectory ??= EnvironmentInfo.WorkingDirectory;
189 logInvocation ??=
true;
191 logger ??= ProcessTasks.DefaultLogger;
192 standardOutputEncoding ??= Encoding.UTF8;
193 standardInputEncoding ??=
new UTF8Encoding(
false);
194 var outputFilter = arguments.GetFilter();
196 var toolPath = _toolPath;
197 var args = arguments.ToStringAndClear();
199 if (!Path.IsPathRooted(_toolPath) && !_toolPath.Contains(Path.DirectorySeparatorChar))
200 toolPath = ToolPathResolver.GetPathExecutable(_toolPath);
202 var toolPathOverride = GetToolPathOverride(toolPath);
203 if (!
string.IsNullOrEmpty(toolPathOverride))
205 args = $
"{toolPath.DoubleQuoteIfNeeded()} {args}".TrimEnd();
206 toolPath = toolPathOverride;
209 Assert.FileExists(toolPath);
210 Assert.DirectoryExists(workingDirectory);
212 var startInfo =
new ProcessStartInfo
216 WorkingDirectory = workingDirectory,
217 RedirectStandardOutput =
true,
218 RedirectStandardError =
true,
219 RedirectStandardInput = input !=
null,
220 UseShellExecute =
false,
221 StandardErrorEncoding = standardOutputEncoding,
222 StandardOutputEncoding = standardOutputEncoding,
225 startInfo.StandardInputEncoding = standardInputEncoding;
227 if (environmentVariables !=
null)
229 startInfo.Environment.Clear();
230 foreach (var (key, value) in environmentVariables)
231 startInfo.Environment[key] = value;
234 if (logInvocation.Value)
235 LogInvocation(startInfo, outputFilter);
237 var process = Process.Start(startInfo);
241 input?.Invoke(process.StandardInput);
243 var output = GetOutputCollection(process, logger, outputFilter);
244 var proc2 =
new Process2(process, outputFilter, timeout, output);
246 (exitHandler ?? (p => p.AssertZeroExitCode())).Invoke(proc2.AssertWaitForExit());
250 private static string? GetToolPathOverride(
string toolPath)
252 if (toolPath.EndsWithOrdinalIgnoreCase(
".dll"))
254 return ToolPathResolver.TryGetEnvironmentExecutable(
"DOTNET_EXE") ??
255 ToolPathResolver.GetPathExecutable(
"dotnet");
258 if (EnvironmentInfo.IsUnix &&
259 toolPath.EndsWithOrdinalIgnoreCase(
".exe") &&
260 !EnvironmentInfo.IsWsl)
261 return ToolPathResolver.GetPathExecutable(
"mono");
266 private static BlockingCollection<Output> GetOutputCollection(
268 Action<OutputType, string>? logger,
269 Func<string, string> outputFilter)
271 var output =
new BlockingCollection<Output>();
273 process.OutputDataReceived += (_, e) =>
278 var filteredOutput = outputFilter(e.Data);
279 output.Add(
new Output { Text = filteredOutput, Type = OutputType.Std });
280 logger?.Invoke(OutputType.Std, filteredOutput);
282 process.ErrorDataReceived += (_, e) =>
287 var filteredOutput = outputFilter(e.Data);
288 output.Add(
new Output { Text = filteredOutput, Type = OutputType.Err });
289 logger?.Invoke(OutputType.Err, filteredOutput);
292 process.BeginOutputReadLine();
293 process.BeginErrorReadLine();
298 private static void LogInvocation(ProcessStartInfo startInfo, Func<string, string> outputFilter)
302 Log.Information(
"> {ToolPath} {Arguments}", startInfo.FileName.DoubleQuoteIfNeeded(), outputFilter(startInfo.Arguments));
304 startInfo.WorkingDirectory != EnvironmentInfo.WorkingDirectory
305 ? LogEventLevel.Information
306 : LogEventLevel.Verbose,
307 "@ {WorkingDirectory}",
308 startInfo.WorkingDirectory);