Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
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,
	}
}