// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using EpicGames.Core; namespace UnrealBuildTool { /// /// The type of shell supported by this platform. Used to configure command line arguments. /// public enum ShellType { /// /// The Bourne shell /// Sh, /// /// Windows command interpreter /// Cmd, } /// /// Host platform abstraction /// public abstract class BuildHostPlatform { private static BuildHostPlatform? CurrentPlatform; /// /// Host platform singleton. /// public static BuildHostPlatform Current { get { if (CurrentPlatform == null) { if (OperatingSystem.IsWindows()) { CurrentPlatform = new WindowsBuildHostPlatform(); } else if (OperatingSystem.IsMacOS()) { CurrentPlatform = new MacBuildHostPlatform(); } else if (OperatingSystem.IsLinux()) { CurrentPlatform = new LinuxBuildHostPlatform(); } else { throw new NotImplementedException(); } } return CurrentPlatform; } } /// /// Gets the current host platform type. /// public abstract UnrealTargetPlatform Platform { get; } /// /// Gets the path to the shell for this platform /// public abstract FileReference Shell { get; } /// /// The type of shell returned by the Shell parameter /// public abstract ShellType ShellType { get; } /// /// The executable binary suffix for this platform /// public abstract string BinarySuffix { get; } /// /// Class that holds information about a running process /// public class ProcessInfo { /// /// Process ID /// public int PID; /// /// Name of the process /// public string Name; /// /// Filename of the process binary /// public string Filename; /// /// Constructor /// /// The process ID /// The process name /// The process filename public ProcessInfo(int InPID, string InName, string InFilename) { PID = InPID; Name = InName; Filename = InFilename; } /// /// Constructor /// /// Process to take information from public ProcessInfo(Process Proc) { PID = Proc.Id; Name = Proc.ProcessName; Filename = Proc.MainModule?.FileName != null ? Path.GetFullPath(Proc.MainModule.FileName) : String.Empty; } /// /// Format as a string for debugging /// /// String containing process info public override string ToString() { return String.Format("{0}, {1}", Name, Filename); } } /// /// Gets all currently running processes. /// /// public virtual ProcessInfo[] GetProcesses() { Process[] AllProcesses = Process.GetProcesses(); List Result = new List(AllProcesses.Length); foreach (Process Proc in AllProcesses) { try { if (!Proc.HasExited) { Result.Add(new ProcessInfo(Proc)); } } catch { } } return Result.ToArray(); } /// /// Gets a process by name. /// /// Name of the process to get information for. /// public virtual ProcessInfo? GetProcessByName(string Name) { ProcessInfo[] AllProcess = GetProcesses(); foreach (ProcessInfo Info in AllProcess) { if (Info.Name == Name) { return Info; } } return null; } /// /// Gets processes by name. /// /// Name of the process to get information for. /// public virtual ProcessInfo[] GetProcessesByName(string Name) { ProcessInfo[] AllProcess = GetProcesses(); List Result = new List(); foreach (ProcessInfo Info in AllProcess) { if (Info.Name == Name) { Result.Add(Info); } } return Result.ToArray(); } /// /// Gets the filenames of all modules associated with a process /// /// Process ID /// Filename of the binary associated with the process. /// An array of all module filenames associated with the process. Can be empty of the process is no longer running. public virtual string[] GetProcessModules(int PID, string Filename) { List Modules = new List(); try { Process Proc = Process.GetProcessById(PID); if (Proc != null) { foreach (ProcessModule Module in Proc.Modules.Cast()) { if (Module.FileName != null) { Modules.Add(Path.GetFullPath(Module.FileName)); } } } } catch { } return Modules.ToArray(); } /// /// Determines if the UBT process is running through WINE /// /// Sequence of project file formats public virtual bool IsRunningOnWine() { return false; } /// /// Determines the default project file formats for this platform /// /// Sequence of project file formats internal abstract IEnumerable GetDefaultProjectFileFormats(); } class WindowsBuildHostPlatform : BuildHostPlatform { public override UnrealTargetPlatform Platform => UnrealTargetPlatform.Win64; public override FileReference Shell => new FileReference(Environment.GetEnvironmentVariable("COMSPEC")!); public override ShellType ShellType => ShellType.Cmd; public override string BinarySuffix => ".exe"; [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); public override bool IsRunningOnWine() { IntPtr NtdllHandle = GetModuleHandle("ntdll.dll"); return NtdllHandle.ToInt64() != 0 && GetProcAddress(NtdllHandle, "wine_get_version").ToInt64() != 0; } internal override IEnumerable GetDefaultProjectFileFormats() { yield return ProjectFileFormat.VisualStudio; #if __VPROJECT_AVAILABLE__ yield return ProjectFileFormat.VProject; #endif } } class MacBuildHostPlatform : BuildHostPlatform { public override UnrealTargetPlatform Platform => UnrealTargetPlatform.Mac; public override FileReference Shell => new FileReference("/bin/sh"); public override ShellType ShellType => ShellType.Sh; public override string BinarySuffix => String.Empty; /// /// (needs confirmation) Currently returns incomplete process names in Process.GetProcesses() so we need to parse 'ps' output. /// /// public override ProcessInfo[] GetProcesses() { List Result = new List(); string TempFile = Path.Combine("/var/tmp", Path.GetTempFileName()); ProcessStartInfo StartInfo = new ProcessStartInfo(); StartInfo.FileName = "/bin/sh"; StartInfo.Arguments = "-c \"ps -eaw -o pid,comm > " + TempFile + "\""; StartInfo.CreateNoWindow = true; Process Proc = new Process(); Proc.StartInfo = StartInfo; try { Proc.Start(); foreach (string FileLine in File.ReadAllLines(TempFile)) { string Line = FileLine.Trim(); int PIDEnd = Line.IndexOf(' '); string PIDString = Line.Substring(0, PIDEnd); if (PIDString != "PID") { string Filename = Line.Substring(PIDEnd + 1); int Pid = Int32.Parse(PIDString); try { Process ExistingProc = Process.GetProcessById(Pid); if (ExistingProc != null && Pid != Environment.ProcessId && ExistingProc.HasExited == false) { ProcessInfo ProcInfo = new ProcessInfo(ExistingProc.Id, Path.GetFileName(Filename), Filename); Result.Add(ProcInfo); } } catch { } } } File.Delete(TempFile); Proc.WaitForExit(); } catch { } return Result.ToArray(); } /// /// (needs confirmation) Currently returns incomplete list of modules for Process.Modules so we need to parse vmmap output. /// /// /// /// public override string[] GetProcessModules(int PID, string Filename) { HashSet Modules = new HashSet(); // Add the process file name to the module list. This is to make it compatible with the results of Process.Modules on Windows. Modules.Add(Filename); ProcessStartInfo StartInfo = new ProcessStartInfo(); StartInfo.FileName = "vmmap"; StartInfo.Arguments = String.Format("{0} -w", PID); StartInfo.CreateNoWindow = true; StartInfo.UseShellExecute = false; StartInfo.RedirectStandardOutput = true; Process Proc = new Process(); Proc.StartInfo = StartInfo; try { Proc.Start(); // Start processing output before vmmap exits otherwise it's going to hang while (!Proc.WaitForExit(1)) { ProcessVMMapOutput(Proc, Modules); } ProcessVMMapOutput(Proc, Modules); } catch { } return Modules.ToArray(); } private void ProcessVMMapOutput(Process Proc, HashSet Modules) { for (string? Line = Proc.StandardOutput.ReadLine(); Line != null; Line = Proc.StandardOutput.ReadLine()) { Line = Line.Trim(); if (Line.EndsWith(".dylib")) { const int SharingModeLength = 6; int SMStart = Line.IndexOf("SM="); int PathStart = SMStart + SharingModeLength; string Module = Line.Substring(PathStart).Trim(); if (!Modules.Contains(Module)) { Modules.Add(Module); } } } } internal override IEnumerable GetDefaultProjectFileFormats() { yield return ProjectFileFormat.XCode; yield return ProjectFileFormat.VisualStudioMac; #if __VPROJECT_AVAILABLE__ yield return ProjectFileFormat.VProject; #endif } } class LinuxBuildHostPlatform : BuildHostPlatform { public override UnrealTargetPlatform Platform => UnrealTargetPlatform.Linux; public override FileReference Shell => new FileReference("/bin/sh"); public override ShellType ShellType => ShellType.Sh; public override string BinarySuffix => String.Empty; /// /// (needs confirmation) Currently returns incomplete process names in Process.GetProcesses() so we need to use /proc /// (also, locks up during process traversal sometimes, trying to open /dev/snd/pcm*) /// /// public override ProcessInfo[] GetProcesses() { // @TODO: Implement for Linux return new List().ToArray(); } /// /// (needs confirmation) Currently returns incomplete list of modules for Process.Modules so we need to parse /proc/PID/maps. /// (also, locks up during process traversal sometimes, trying to open /dev/snd/pcm*) /// /// /// /// public override string[] GetProcessModules(int PID, string Filename) { // @TODO: Implement for Linux return new List().ToArray(); } internal override IEnumerable GetDefaultProjectFileFormats() { yield return ProjectFileFormat.Make; yield return ProjectFileFormat.VisualStudioCode; #if __VPROJECT_AVAILABLE__ yield return ProjectFileFormat.VProject; #endif } } }