Files
UnrealEngine/Engine/Source/Programs/UnrealBuildTool/System/BuildHostPlatform.cs
2025-05-18 13:04:45 +08:00

437 lines
12 KiB
C#

// 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
{
/// <summary>
/// The type of shell supported by this platform. Used to configure command line arguments.
/// </summary>
public enum ShellType
{
/// <summary>
/// The Bourne shell
/// </summary>
Sh,
/// <summary>
/// Windows command interpreter
/// </summary>
Cmd,
}
/// <summary>
/// Host platform abstraction
/// </summary>
public abstract class BuildHostPlatform
{
private static BuildHostPlatform? CurrentPlatform;
/// <summary>
/// Host platform singleton.
/// </summary>
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;
}
}
/// <summary>
/// Gets the current host platform type.
/// </summary>
public abstract UnrealTargetPlatform Platform { get; }
/// <summary>
/// Gets the path to the shell for this platform
/// </summary>
public abstract FileReference Shell { get; }
/// <summary>
/// The type of shell returned by the Shell parameter
/// </summary>
public abstract ShellType ShellType { get; }
/// <summary>
/// The executable binary suffix for this platform
/// </summary>
public abstract string BinarySuffix { get; }
/// <summary>
/// Class that holds information about a running process
/// </summary>
public class ProcessInfo
{
/// <summary>
/// Process ID
/// </summary>
public int PID;
/// <summary>
/// Name of the process
/// </summary>
public string Name;
/// <summary>
/// Filename of the process binary
/// </summary>
public string Filename;
/// <summary>
/// Constructor
/// </summary>
/// <param name="InPID">The process ID</param>
/// <param name="InName">The process name</param>
/// <param name="InFilename">The process filename</param>
public ProcessInfo(int InPID, string InName, string InFilename)
{
PID = InPID;
Name = InName;
Filename = InFilename;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="Proc">Process to take information from</param>
public ProcessInfo(Process Proc)
{
PID = Proc.Id;
Name = Proc.ProcessName;
Filename = Proc.MainModule?.FileName != null ? Path.GetFullPath(Proc.MainModule.FileName) : String.Empty;
}
/// <summary>
/// Format as a string for debugging
/// </summary>
/// <returns>String containing process info</returns>
public override string ToString()
{
return String.Format("{0}, {1}", Name, Filename);
}
}
/// <summary>
/// Gets all currently running processes.
/// </summary>
/// <returns></returns>
public virtual ProcessInfo[] GetProcesses()
{
Process[] AllProcesses = Process.GetProcesses();
List<ProcessInfo> Result = new List<ProcessInfo>(AllProcesses.Length);
foreach (Process Proc in AllProcesses)
{
try
{
if (!Proc.HasExited)
{
Result.Add(new ProcessInfo(Proc));
}
}
catch { }
}
return Result.ToArray();
}
/// <summary>
/// Gets a process by name.
/// </summary>
/// <param name="Name">Name of the process to get information for.</param>
/// <returns></returns>
public virtual ProcessInfo? GetProcessByName(string Name)
{
ProcessInfo[] AllProcess = GetProcesses();
foreach (ProcessInfo Info in AllProcess)
{
if (Info.Name == Name)
{
return Info;
}
}
return null;
}
/// <summary>
/// Gets processes by name.
/// </summary>
/// <param name="Name">Name of the process to get information for.</param>
/// <returns></returns>
public virtual ProcessInfo[] GetProcessesByName(string Name)
{
ProcessInfo[] AllProcess = GetProcesses();
List<ProcessInfo> Result = new List<ProcessInfo>();
foreach (ProcessInfo Info in AllProcess)
{
if (Info.Name == Name)
{
Result.Add(Info);
}
}
return Result.ToArray();
}
/// <summary>
/// Gets the filenames of all modules associated with a process
/// </summary>
/// <param name="PID">Process ID</param>
/// <param name="Filename">Filename of the binary associated with the process.</param>
/// <returns>An array of all module filenames associated with the process. Can be empty of the process is no longer running.</returns>
public virtual string[] GetProcessModules(int PID, string Filename)
{
List<string> Modules = new List<string>();
try
{
Process Proc = Process.GetProcessById(PID);
if (Proc != null)
{
foreach (ProcessModule Module in Proc.Modules.Cast<System.Diagnostics.ProcessModule>())
{
if (Module.FileName != null)
{
Modules.Add(Path.GetFullPath(Module.FileName));
}
}
}
}
catch { }
return Modules.ToArray();
}
/// <summary>
/// Determines if the UBT process is running through WINE
/// </summary>
/// <returns>Sequence of project file formats</returns>
public virtual bool IsRunningOnWine()
{
return false;
}
/// <summary>
/// Determines the default project file formats for this platform
/// </summary>
/// <returns>Sequence of project file formats</returns>
internal abstract IEnumerable<ProjectFileFormat> 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<ProjectFileFormat> 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;
/// <summary>
/// (needs confirmation) Currently returns incomplete process names in Process.GetProcesses() so we need to parse 'ps' output.
/// </summary>
/// <returns></returns>
public override ProcessInfo[] GetProcesses()
{
List<ProcessInfo> Result = new List<ProcessInfo>();
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();
}
/// <summary>
/// (needs confirmation) Currently returns incomplete list of modules for Process.Modules so we need to parse vmmap output.
/// </summary>
/// <param name="PID"></param>
/// <param name="Filename"></param>
/// <returns></returns>
public override string[] GetProcessModules(int PID, string Filename)
{
HashSet<string> Modules = new HashSet<string>();
// 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<string> 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<ProjectFileFormat> 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;
/// <summary>
/// (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*)
/// </summary>
/// <returns></returns>
public override ProcessInfo[] GetProcesses()
{
// @TODO: Implement for Linux
return new List<ProcessInfo>().ToArray();
}
/// <summary>
/// (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*)
/// </summary>
/// <param name="PID"></param>
/// <param name="Filename"></param>
/// <returns></returns>
public override string[] GetProcessModules(int PID, string Filename)
{
// @TODO: Implement for Linux
return new List<string>().ToArray();
}
internal override IEnumerable<ProjectFileFormat> GetDefaultProjectFileFormats()
{
yield return ProjectFileFormat.Make;
yield return ProjectFileFormat.VisualStudioCode;
#if __VPROJECT_AVAILABLE__
yield return ProjectFileFormat.VProject;
#endif
}
}
}