Repository URL to install this package:
|
Version:
1.0.0 ▾
|
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using JetBrains.Annotations;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Fluctio.FluctioSim.EditorUtils.OperatingSystem
{
public static class ProcessUtil
{
public static Process StartProcess(ProcessStartInfo processStartInfo)
{
var process = Process.Start(processStartInfo);
if (process == null)
{
throw new ProcessStartException(processStartInfo);
}
process.EnableRaisingEvents = true;
return process;
}
public static Process StartProcess(string filename, string arguments = "")
{
return StartProcess(new ProcessStartInfo
{
FileName = filename,
Arguments = arguments,
});
}
public static Process OpenTerminalWindow(string command)
{
// TODO: test on MacOS and Linux
var processStartInfo = new ProcessStartInfo();
switch (Platform.Type)
{
case PlatformType.Windows:
processStartInfo.FileName = "cmd";
processStartInfo.Arguments = $"/u /s /k {InQuotes(command)}";
break;
case PlatformType.Linux:
processStartInfo.FileName = "bash";
var bashrcCommand = $"source {InQuotes("$HOME/.bashrc")}";
var fullInitCommand = $"{bashrcCommand}; {command}";
var initFileString = $"<(echo {InQuotes(fullInitCommand)})";
processStartInfo.Arguments = $"--init-file {initFileString}";
break;
case PlatformType.MacOS:
processStartInfo.FileName = "osascript";
var osascriptString = $"tell app \"Terminal\" to do script {InQuotes(command)}";
processStartInfo.Arguments = $"-e {InQuotes(osascriptString)}";
break;
case PlatformType.Other:
default:
throw Platform.OSNotSupportedException;
}
var process = StartProcess(processStartInfo);
return process;
}
public static Process StartShellProcess(string command)
{
var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
switch (Platform.Type)
{
case PlatformType.Windows:
processStartInfo.FileName = "cmd";
processStartInfo.Arguments = $"/u /s /c {InQuotes(command)}";
break;
case PlatformType.Linux:
processStartInfo.FileName = "sh";
processStartInfo.Arguments = $"-c {InQuotes(command)}";
break;
case PlatformType.MacOS:
processStartInfo.FileName = "zsh";
processStartInfo.Arguments = $"-c {InQuotes(command)}";
break;
case PlatformType.Other:
default:
throw Platform.OSNotSupportedException;
}
var process = StartProcess(processStartInfo);
return process;
}
public static async Task KillTree([NotNull] this Process process)
{
// TODO: separate function for sending Ctrl+C/SIGINT/SIGTERM/p.StandardInput.Close()
var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
};
switch (Platform.Type)
{
case PlatformType.Windows:
processStartInfo.FileName = "taskkill";
processStartInfo.Arguments = $"/PID {process.Id} /T /F";
break;
case PlatformType.Linux:
case PlatformType.MacOS:
processStartInfo.FileName = "pkill";
processStartInfo.Arguments = $"-P {process.Id}";
break;
case PlatformType.Other:
default:
throw Platform.OSNotSupportedException;
}
var killingProcess = StartProcess(processStartInfo);
await killingProcess.WaitForExitAsync();
}
public static async Task<string> RunAndGet(string command)
{
var process = StartShellProcess(command);
var stderrResult = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrEmpty(stderrResult))
{
if (process.ExitCode == 0)
{
Debug.LogWarning(stderrResult);
}
else
{
throw new ProcessRuntimeException($"The following shell command failed:\n{command}\nReason:\n{stderrResult}");
}
}
var stdoutResult = await process.StandardOutput.ReadToEndAsync();
return stdoutResult;
}
public static Process RunInBackground(string command, [CanBeNull] Action<string, ProcessStreamType> outputCallback = null)
{
var process = StartShellProcess(command);
process.OutputDataReceived += (_, args) => MaybeExecuteCallback(args, ProcessStreamType.StdOut);
process.ErrorDataReceived += (_, args) => MaybeExecuteCallback(args, ProcessStreamType.StdErr);
process.Exited += delegate
{
if (process.ExitCode != 0)
{
Debug.LogError($"The following shell command failed:\n{command}\nExit code:\n{process.ExitCode}");
}
};
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return process;
void MaybeExecuteCallback(DataReceivedEventArgs args, ProcessStreamType streamType)
{
if (args.Data != null)
{
outputCallback?.Invoke(args.Data, streamType);
}
}
}
public static Process RunInUnityConsole(string command, [CanBeNull] Func<string, ProcessStreamType, LogType?> logTyper = null)
{
return RunInBackground(command, (line, streamType) =>
{
logTyper ??= DefaultLogTyper;
var logType = logTyper(line, streamType);
if (logType != null)
{
Debug.unityLogger.Log(logType.Value, line);
}
});
}
private static LogType? DefaultLogTyper(string line, ProcessStreamType streamType)
{
return streamType switch
{
ProcessStreamType.StdIn => null,
ProcessStreamType.StdOut => LogType.Log,
ProcessStreamType.StdErr => LogType.Warning,
_ => throw new InvalidEnumArgumentException(nameof(streamType), (int)streamType, streamType.GetType())
};
}
public static Task WaitForExitAsync(this Process process)
{
var completionSource = new TaskCompletionSource<object>();
process.Exited += (_, _) =>
{
if (process.ExitCode == 0)
{
completionSource.SetResult(null);
}
else
{
completionSource.SetException(new ProcessRuntimeException($"Process {process.Id} exited with code {process.ExitCode}"));
}
};
return completionSource.Task;
}
public static string InQuotes(string argument, bool wrapEmpty = true)
{
if (!wrapEmpty && string.IsNullOrWhiteSpace(argument))
{
return "";
}
const char doubleQuote = '"';
const char slash = '\\';
if (Platform.Type != PlatformType.Windows)
{
argument = argument
.Replace($"{slash}", $"{slash}{slash}")
.Replace($"{doubleQuote}", $"{slash}{doubleQuote}");
}
return $"{doubleQuote}{argument}{doubleQuote}";
}
}
[Serializable]
public class ProcessStartException : Exception
{
public ProcessStartException(ProcessStartInfo processStartInfo) : base($"Could not start the following process:\n{processStartInfo.FileName} {processStartInfo.Arguments}") {}
}
[Serializable]
public class ProcessRuntimeException : Exception
{
public ProcessRuntimeException(string message) : base(message) {}
}
public enum ProcessStreamType
{
StdIn = 0,
StdOut = 1,
StdErr = 2,
}
}