// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace UnrealBuildBase { public static class Unreal { private static DirectoryReference FindRootDirectory() { if (LocationOverride.RootDirectory != null) { return DirectoryReference.FindCorrectCase(LocationOverride.RootDirectory); } string? OverrideArg = Environment.GetCommandLineArgs().FirstOrDefault(x => x?.StartsWith("-rootdirectory=") ?? false, null); if (OverrideArg != null) { string[] Parts = OverrideArg.Split('=', 2); return new DirectoryReference(Path.GetFullPath(Parts[1])); } // This base library may be used - and so be launched - from more than one location (at time of writing, UnrealBuildTool and AutomationTool) // Programs that use this assembly must be located under "Engine/Binaries/DotNET" and so we look for that sequence of directories in that path of the executing assembly // Use the EntryAssembly (the application path), rather than the ExecutingAssembly (the library path) string AssemblyLocation = Assembly.GetEntryAssembly()!.GetOriginalLocation(); DirectoryReference? FoundRootDirectory = DirectoryReference.FindCorrectCase(DirectoryReference.FromString(AssemblyLocation)!); // Search up through the directory tree for the deepest instance of the sub-path "Engine/Binaries/DotNET" while (FoundRootDirectory != null) { if (String.Equals("DotNET", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; if (FoundRootDirectory != null && String.Equals("Binaries", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; if (FoundRootDirectory != null && String.Equals("Engine", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; break; } continue; } continue; } FoundRootDirectory = FoundRootDirectory.ParentDirectory; } // Search up through the directory tree for the deepest instance of the sub-path "Engine/Source/Programs" if (FoundRootDirectory == null) { FoundRootDirectory = DirectoryReference.FindCorrectCase(DirectoryReference.FromString(AssemblyLocation)!); while (FoundRootDirectory != null) { if (String.Equals("Programs", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; if (FoundRootDirectory != null && String.Equals("Source", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; if (FoundRootDirectory != null && String.Equals("Engine", FoundRootDirectory.GetDirectoryName())) { FoundRootDirectory = FoundRootDirectory.ParentDirectory; break; } continue; } continue; } FoundRootDirectory = FoundRootDirectory.ParentDirectory; } } if (FoundRootDirectory == null) { throw new Exception($"This code requires that applications using it are launched from a path containing \"Engine/Binaries/DotNET\" or \"Engine/Source/Programs\". This application was launched from {Path.GetDirectoryName(AssemblyLocation)}"); } // Confirm that we've found a valid root directory, by testing for the existence of a well-known file FileReference ExpectedExistingFile = FileReference.Combine(FoundRootDirectory, "Engine", "Build", "Build.version"); if (!FileReference.Exists(ExpectedExistingFile)) { throw new Exception($"Expected file \"Engine/Build/Build.version\" was not found at {ExpectedExistingFile.FullName}"); } return FoundRootDirectory; } private static FileReference FindUnrealBuildToolDll() { // UnrealBuildTool.dll is assumed to be located under {RootDirectory}/Engine/Binaries/DotNET/UnrealBuildTool/ FileReference UnrealBuildToolDllPath = FileReference.Combine(EngineDirectory, "Binaries", "DotNET", "UnrealBuildTool", "UnrealBuildTool.dll"); UnrealBuildToolDllPath = FileReference.FindCorrectCase(UnrealBuildToolDllPath); if (!FileReference.Exists(UnrealBuildToolDllPath)) { throw new Exception($"Unable to find UnrealBuildTool.dll in the expected location at {UnrealBuildToolDllPath.FullName}"); } return UnrealBuildToolDllPath; } private static string DotnetVersionDirectory = "8.0.300"; private static string FindRelativeDotnetDirectory(RuntimePlatform.Type HostPlatform) { string platform; string architecture; switch (HostPlatform) { case RuntimePlatform.Type.Linux: platform = "linux"; break; case RuntimePlatform.Type.Mac: platform = "mac"; break; case RuntimePlatform.Type.Windows: platform = "win"; break; default: throw new Exception($"Unsupported host platform {HostPlatform}"); } switch (RuntimeInformation.ProcessArchitecture) { case Architecture.Arm64: architecture = "arm64"; break; case Architecture.X64: architecture = "x64"; break; default: throw new Exception($"Unsupported host architecture {RuntimeInformation.ProcessArchitecture}"); } return Path.Combine("Binaries", "ThirdParty", "DotNet", DotnetVersionDirectory, $"{platform}-{architecture}"); } private static string FindRelativeDotnetDirectory() => FindRelativeDotnetDirectory(RuntimePlatform.Current); /// /// Relative path to the dotnet executable from EngineDir /// /// public static string RelativeDotnetDirectory => _RelativeDotnetDirectory.Value; private static readonly Lazy _RelativeDotnetDirectory = new(FindRelativeDotnetDirectory); private static DirectoryReference FindDotnetDirectory() => DirectoryReference.Combine(EngineDirectory, RelativeDotnetDirectory); /// /// The full name of the root UE directory /// public static DirectoryReference RootDirectory => _RootDirectory.Value; private static readonly Lazy _RootDirectory = new(FindRootDirectory); /// /// The full name of the Engine directory /// public static DirectoryReference EngineDirectory => _EngineDirectory.Value; private static readonly Lazy _EngineDirectory = new(() => DirectoryReference.Combine(RootDirectory, "Engine")); /// /// The full name of the Engine/Source directory /// public static DirectoryReference EngineSourceDirectory => _EngineSourceDirectory.Value; private static readonly Lazy _EngineSourceDirectory = new(() => DirectoryReference.Combine(EngineDirectory, "Source")); /// /// Returns the Application Settings Directory path. This matches FPlatformProcess::ApplicationSettingsDir(). /// public static DirectoryReference ApplicationSettingDirectory => _ApplicationSettingDirectory.Value; private static readonly Lazy _ApplicationSettingDirectory = new(GetApplicationSettingDirectory); /// /// Returns the User Settings Directory path. This matches FPlatformProcess::UserSettingsDir(). /// public static DirectoryReference UserSettingDirectory => _UserSettingDirectory.Value; private static readonly Lazy _UserSettingDirectory = new(GetUserSettingDirectory); /// /// Returns the User Directory path. This matches FPlatformProcess::UserDir(). /// public static DirectoryReference? UserDirectory => _UserDirectory.Value; private static readonly Lazy _UserDirectory = new(GetUserDirectory); /// /// Writable engine directory. Uses the user's settings folder for installed builds. /// public static DirectoryReference WritableEngineDirectory => _WritableEngineDirectory.Value; private static readonly Lazy _WritableEngineDirectory = new(() => IsEngineInstalled() ? DirectoryReference.Combine(UserSettingDirectory, "UnrealEngine") : EngineDirectory); /// /// The engine saved programs directory /// public static DirectoryReference EngineProgramSavedDirectory => _EngineProgramSavedDirectory.Value; private static readonly Lazy _EngineProgramSavedDirectory = new(() => IsEngineInstalled() ? UserSettingDirectory : DirectoryReference.Combine(EngineDirectory, "Programs")); /// /// The path to UBT /// [Obsolete("Deprecated in UE5.1; to launch UnrealBuildTool, use this dll as the first argument with DonetPath")] public static FileReference UnrealBuildToolPath => _UnrealBuildToolDllPath.Value.ChangeExtension(RuntimePlatform.ExeExtension); /// /// The path to UBT /// public static FileReference UnrealBuildToolDllPath => _UnrealBuildToolDllPath.Value; private static readonly Lazy _UnrealBuildToolDllPath = new(FindUnrealBuildToolDll); /// /// The directory containing the bundled .NET installation /// public static DirectoryReference DotnetDirectory => _DotnetDirectory.Value; private static readonly Lazy _DotnetDirectory = new(FindDotnetDirectory); /// /// The path of the bundled dotnet executable /// public static FileReference DotnetPath => _DotnetPath.Value; private static readonly Lazy _DotnetPath = new(() => FileReference.Combine(DotnetDirectory, "dotnet" + RuntimePlatform.ExeExtension)); /// /// Returns true if the application is running on a build machine /// /// True if running on a build machine public static bool IsBuildMachine() => _IsBuildMachine.Value; private static readonly Lazy _IsBuildMachine = new Lazy(() => Environment.GetEnvironmentVariable("IsBuildMachine")?.Trim() == "1"); /// /// Returns true if the application is running using installed Engine components /// /// True if running using installed Engine components public static bool IsEngineInstalled() => _IsEngineInstalled.Value; private static readonly Lazy _IsEngineInstalled = new(() => FileReference.Exists(FileReference.Combine(EngineDirectory, "Build", "InstalledBuild.txt"))); /// /// If we are running with an installed project, specifies the path to it /// private static readonly Lazy _InstalledProjectFile = new(() => { FileReference installedProjectLocationFile = FileReference.Combine(EngineDirectory, "Build", "InstalledProjectBuild.txt"); return FileReference.Exists(installedProjectLocationFile) ? FileReference.Combine(RootDirectory, FileReference.ReadAllText(installedProjectLocationFile).Trim()) : null; }); /// /// The original root directory that was used to compile the installed engine /// Used to remap source code paths when debugging. /// public static DirectoryReference OriginalCompilationRootDirectory => _OriginalCompilationRootDirectory.Value; private static readonly Lazy _OriginalCompilationRootDirectory = new(FindOriginalCompilationRootDirectory); /// /// Returns where another platform's Dotnet is located /// /// /// public static DirectoryReference FindDotnetDirectoryForPlatform(RuntimePlatform.Type HostPlatform) => DirectoryReference.Combine(EngineDirectory, FindRelativeDotnetDirectory(HostPlatform)); /// /// Returns true if the application is running using an installed project (ie. a mod kit) /// /// True if running using an installed project public static bool IsProjectInstalled() => _InstalledProjectFile.Value != null; /// /// Gets the installed project file /// /// Location of the installed project file public static FileReference? GetInstalledProjectFile() => _InstalledProjectFile.Value; /// /// Checks whether the given file is under an installed directory, and should not be overridden /// /// File to test /// True if the file is part of the installed distribution, false otherwise public static bool IsFileInstalled(FileReference File) { if (IsEngineInstalled() && File.IsUnderDirectory(EngineDirectory)) { return true; } if (IsProjectInstalled() && File.IsUnderDirectory(_InstalledProjectFile.Value!.Directory)) { return true; } return false; } private static DirectoryReference FindOriginalCompilationRootDirectory() { if (IsEngineInstalled()) { // Load Engine\Intermediate\Build\BuildRules\*RulesManifest.json DirectoryReference BuildRules = DirectoryReference.Combine(EngineDirectory, "Intermediate", "Build", "BuildRules"); FileReference? RulesManifest = DirectoryReference.EnumerateFiles(BuildRules, "*RulesManifest.json").FirstOrDefault(); if (RulesManifest != null) { JsonObject Manifest = JsonObject.Read(RulesManifest); if (Manifest.TryGetStringArrayField("SourceFiles", out string[]? SourceFiles)) { FileReference? SourceFile = FileReference.FromString(SourceFiles.FirstOrDefault()); if (SourceFile != null && !SourceFile.IsUnderDirectory(EngineDirectory)) { // Walk up parent directory until Engine is found DirectoryReference? Directory = SourceFile.Directory; while (Directory != null && !Directory.IsRootDirectory()) { if (Directory.GetDirectoryName() == "Engine" && Directory.ParentDirectory != null) { return Directory.ParentDirectory; } Directory = Directory.ParentDirectory; } } } } } return RootDirectory; } public static class LocationOverride { /// /// If set, this value will be used to populate Unreal.RootDirectory /// public static DirectoryReference? RootDirectory = null; } // A subset of the functionality in DataDrivenPlatformInfo.GetAllPlatformInfos() - finds the DataDrivenPlatformInfo.ini files and records their existence, but does not parse them // (perhaps DataDrivenPlatformInfo.GetAllPlatformInfos() could be modified to use this data to avoid an additional search through the filesystem) private static readonly Lazy> IniPresentForPlatform = new Lazy>(() => { HashSet Set = new HashSet(StringComparer.OrdinalIgnoreCase); // find all platform directories (skipping NFL/NoRedist) foreach (DirectoryReference EngineConfigDir in GetExtensionDirs(Unreal.EngineDirectory, "Config", bIncludeRestrictedDirectories: false)) { // look through all config dirs looking for the data driven ini file foreach (string FilePath in Directory.EnumerateFiles(EngineConfigDir.FullName, "DataDrivenPlatformInfo.ini", SearchOption.AllDirectories)) { FileReference FileRef = new FileReference(FilePath); // get the platform name from the path string IniPlatformName; if (FileRef.IsUnderDirectory(DirectoryReference.Combine(Unreal.EngineDirectory, "Config"))) { // Foo/Engine/Config//DataDrivenPlatformInfo.ini IniPlatformName = Path.GetFileName(Path.GetDirectoryName(FilePath))!; } else { // Foo/Engine/Platforms//Config/DataDrivenPlatformInfo.ini IniPlatformName = Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(FilePath)))!; } // DataDrivenPlatformInfo.GetAllPlatformInfos() checks that [DataDrivenPlatformInfo] section exists as part of validating that the file exists // This code should probably behave the same way. Set.Add(IniPlatformName); } } return Set; }); private static bool DataDrivenPlatformInfoIniIsPresent(string PlatformName) => IniPresentForPlatform.Value.Contains(PlatformName); // cached dictionary of BaseDir to extension directories private static ConcurrentDictionary, List>> CachedExtensionDirectories = new(); /// /// Finds all the extension directories for the given base directory. This includes platform extensions and restricted folders. /// /// Location of the base directory /// If true, platform subdirectories are included (will return platform directories under Restricted dirs, even if bIncludeRestrictedDirectories is false) /// If true, restricted (NotForLicensees, NoRedist) subdirectories are included /// If true, BaseDir is included /// List of extension directories, including the given base directory public static List GetExtensionDirs(DirectoryReference BaseDir, bool bIncludePlatformDirectories = true, bool bIncludeRestrictedDirectories = true, bool bIncludeBaseDirectory = true) { Tuple, List> CachedDirs = CachedExtensionDirectories.GetOrAdd(BaseDir, _ => { Tuple, List> NewCachedDirs = Tuple.Create(new List(), new List()); DirectoryReference PlatformExtensionBaseDir = DirectoryReference.Combine(BaseDir, "Platforms"); if (DirectoryReference.Exists(PlatformExtensionBaseDir)) { NewCachedDirs.Item1.AddRange(DirectoryReference.EnumerateDirectories(PlatformExtensionBaseDir)); } DirectoryReference RestrictedBaseDir = DirectoryReference.Combine(BaseDir, "Restricted"); if (DirectoryReference.Exists(RestrictedBaseDir)) { IEnumerable RestrictedDirs = DirectoryReference.EnumerateDirectories(RestrictedBaseDir); NewCachedDirs.Item2.AddRange(RestrictedDirs); // also look for nested platforms in the restricted foreach (DirectoryReference RestrictedDir in RestrictedDirs) { DirectoryReference RestrictedPlatformExtensionBaseDir = DirectoryReference.Combine(RestrictedDir, "Platforms"); if (DirectoryReference.Exists(RestrictedPlatformExtensionBaseDir)) { NewCachedDirs.Item1.AddRange(DirectoryReference.EnumerateDirectories(RestrictedPlatformExtensionBaseDir)); } } } // remove any platform directories in non-engine locations if the engine doesn't have the platform if (BaseDir != Unreal.EngineDirectory && NewCachedDirs.Item1.Count > 0) { // if the DDPI.ini file doesn't exist, we haven't synced the platform, so just skip this directory NewCachedDirs.Item1.RemoveAll(x => !DataDrivenPlatformInfoIniIsPresent(x.GetDirectoryName())); } return NewCachedDirs; }); // now return what the caller wanted (always include BaseDir) List ExtensionDirs = []; if (bIncludeBaseDirectory) { ExtensionDirs.Add(BaseDir); } if (bIncludePlatformDirectories) { ExtensionDirs.AddRange(CachedDirs.Item1); } if (bIncludeRestrictedDirectories) { ExtensionDirs.AddRange(CachedDirs.Item2); } return ExtensionDirs; } /// /// Finds all the extension directories for the given base directory. This includes platform extensions and restricted folders.f /// /// Location of the base directory /// The subdirectory to find /// If true, platform subdirectories are included (will return platform directories under Restricted dirs, even if bIncludeRestrictedDirectories is false) /// If true, restricted (NotForLicensees, NoRedist) subdirectories are included /// If true, BaseDir is included /// List of extension directories, including the given base directory public static List GetExtensionDirs(DirectoryReference BaseDir, string SubDir, bool bIncludePlatformDirectories = true, bool bIncludeRestrictedDirectories = true, bool bIncludeBaseDirectory = true) { return GetExtensionDirs(BaseDir, bIncludePlatformDirectories, bIncludeRestrictedDirectories, bIncludeBaseDirectory) .Select(x => DirectoryReference.Combine(x, SubDir)) .Where(x => DirectoryReference.Exists(x)) .ToList(); } /// /// Returns the Application Settings Directory path. This matches FPlatformProcess::ApplicationSettingsDir(). /// private static DirectoryReference GetApplicationSettingDirectory() { if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) { return new DirectoryReference(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Epic")); } return new DirectoryReference(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Epic")); } /// /// Returns the User Settings Directory path. This matches FPlatformProcess::UserSettingsDir(). /// private static DirectoryReference GetUserSettingDirectory() { if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) { // Mac and Linux use the same folder for UserSettingsDir and ApplicationSettingsDir return GetApplicationSettingDirectory(); } else { // Not all user accounts have a local application data directory (eg. SYSTEM, used by Jenkins for builds). List DataFolders = [ Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolder.CommonApplicationData ]; foreach (Environment.SpecialFolder DataFolder in DataFolders) { string DirectoryName = Environment.GetFolderPath(DataFolder); if (!String.IsNullOrEmpty(DirectoryName)) { return new DirectoryReference(DirectoryName); } } } return DirectoryReference.Combine(EngineDirectory, "Saved"); } /// /// Returns the User Directory path. This matches FPlatformProcess::UserDir(). /// private static DirectoryReference? GetUserDirectory() { // Some user accounts (eg. SYSTEM on Windows) don't have a home directory. Ignore them if Environment.GetFolderPath() returns an empty string. string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal); if (!String.IsNullOrEmpty(PersonalFolder)) { if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) { return new DirectoryReference(System.IO.Path.Combine(PersonalFolder, "Documents")); } else { return new DirectoryReference(PersonalFolder); } } return null; } /// /// The current Machine name /// public static string MachineName => _MachineName.Value; private static readonly Lazy _MachineName = new(() => { try { // this likely can't fail, but just in case, fallback to preview implementation string machineName = System.Net.Dns.GetHostName(); if (OperatingSystem.IsMacOS() && machineName.EndsWith(".local")) { machineName = machineName.Replace(".local", ""); } return machineName; } catch (Exception) { } return Environment.MachineName; }); } }