// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using EpicGames.Core; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.Setup.Configuration; using Microsoft.Win32; using UnrealBuildBase; /////////////////////////////////////////////////////////////////// // If you are looking for supported version numbers, look in the // MicrosoftPlatformSDK.Versions.cs file next to this file /////////////////////////////////////////////////////////////////// namespace UnrealBuildTool { internal partial class MicrosoftPlatformSDK : UEBuildPlatformSDK { public MicrosoftPlatformSDK(ILogger Logger) : base(Logger) { } [SupportedOSPlatform("windows")] protected override string? GetInstalledSDKVersion() { if (!OperatingSystem.IsWindows()) { return null; } // use the PreferredWindowsSdkVersions array to find the SDK version that UBT will use to build with - VersionNumber? WindowsVersion; if (TryGetWindowsSdkDir(null, Logger, out WindowsVersion, out _)) { return WindowsVersion.ToString(); } // if that failed, we aren't able to build, so give up return null; } public override bool TryConvertVersionToInt(string? StringValue, out ulong OutValue, string? Hint) { OutValue = 0; if (StringValue == null) { return false; } Match Result = Regex.Match(StringValue, @"^(\d+).(\d+).(\d+)"); if (Result.Success) { // 8 bits for major, 8 for minor, 16 for build - we ignore patch (ie the 1234 in 10.0.14031.1234) OutValue |= UInt64.Parse(Result.Groups[1].Value) << 24; OutValue |= UInt64.Parse(Result.Groups[2].Value) << 16; OutValue |= UInt64.Parse(Result.Groups[3].Value) << 0; return true; } Result = Regex.Match(StringValue, @"^(\d+).(\d+)"); if (Result.Success) { // 8 bits for major, 8 for minor - ignore build and patch OutValue |= UInt64.Parse(Result.Groups[1].Value) << 24; OutValue |= UInt64.Parse(Result.Groups[2].Value) << 16; return true; } return false; } #region Windows Specific SDK discovery #region Cached Locations /// /// Cache of Visual C++ installation directories /// private static ConcurrentDictionary> CachedToolChainInstallations = new ConcurrentDictionary>(); /// /// Cache of Windows SDK installation directories /// private static IReadOnlyDictionary? CachedWindowsSdkDirs; /// /// Cache of Universal CRT installation directories /// private static IReadOnlyDictionary? CachedUniversalCrtDirs; /// /// Cache of Visual Studio installation directories /// private static IReadOnlyList? CachedVisualStudioInstallations; /// /// Cache of DIA SDK installation directories /// private static ConcurrentDictionary> CachedDiaSdkDirs = new ConcurrentDictionary>(); #endregion #region WindowsSdk #region Public Interface /// /// Finds all the installed Windows SDK versions /// /// Logger for output /// Map of version number to Windows SDK directories [SupportedOSPlatform("windows")] public static IReadOnlyDictionary FindWindowsSdkDirs(ILogger Logger) { // Update the cache of install directories, if it's not set if (CachedWindowsSdkDirs == null) { UpdateCachedWindowsSdks(Logger); } return CachedWindowsSdkDirs!; } /// /// List all the installed Windows SDK versions /// /// Logger for output [SupportedOSPlatform("windows")] public static void DumpWindowsSdkDirs(ILogger Logger) { IOrderedEnumerable> SortedSdkDirs = FindWindowsSdkDirs(Logger).OrderByDescending(x => x.Key); if (SortedSdkDirs.Any()) { Logger.LogInformation("Available Windows SDKs ({Count}):", SortedSdkDirs.Count()); foreach (KeyValuePair Item in SortedSdkDirs) { Logger.LogInformation(" * {SdkDir}\n (Version={Version})", Item.Value, Item.Key); } } else { Logger.LogInformation("No available Windows SDKs found"); } } /// /// Finds all the installed Universal CRT versions /// /// Logger for output /// Map of version number to universal CRT directories [SupportedOSPlatform("windows")] public static IReadOnlyDictionary FindUniversalCrtDirs(ILogger Logger) { if (CachedUniversalCrtDirs == null) { UpdateCachedWindowsSdks(Logger); } return CachedUniversalCrtDirs!; } /// /// Determines the directory containing the Windows SDK toolchain /// /// The desired Windows SDK version. This may be "Latest", a specific version number, or null. If null, the function will look for DefaultWindowsSdkVersion. Failing that, it will return the latest version. /// Logger for output /// Receives the version number of the selected Windows SDK /// Receives the root directory for the selected SDK /// Optional minimum required version. Ignored if DesiredVesrion is specified /// Optional maximum required version. Ignored if DesiredVesrion is specified /// True if the toolchain directory was found correctly [SupportedOSPlatform("windows")] public static bool TryGetWindowsSdkDir(string? DesiredVersion, ILogger Logger, [NotNullWhen(true)] out VersionNumber? OutSdkVersion, [NotNullWhen(true)] out DirectoryReference? OutSdkDir, VersionNumber? MinVersion = null, VersionNumber? MaxVersion = null) { // Get a map of Windows SDK versions to their root directories /*IReadOnlyDictionary WindowsSdkDirs =*/ FindWindowsSdkDirs(Logger); // Figure out which version number to look for VersionNumber? WindowsSdkVersion = null; if (!String.IsNullOrEmpty(DesiredVersion)) { if (String.Equals(DesiredVersion, "Latest", StringComparison.InvariantCultureIgnoreCase) && CachedWindowsSdkDirs!.Count > 0) { WindowsSdkVersion = CachedWindowsSdkDirs.OrderBy(x => x.Key).Last().Key; } else if (!VersionNumber.TryParse(DesiredVersion, out WindowsSdkVersion)) { throw new BuildException("Unable to find requested Windows SDK; '{0}' is an invalid version", DesiredVersion); } } else if (CachedWindowsSdkDirs!.Count > 0) { IEnumerable> AllowedSdkDirs = CachedWindowsSdkDirs.OrderBy(x => x.Key).Where( x => (MinVersion == null || x.Key >= MinVersion) && (MaxVersion == null || x.Key <= MaxVersion)); // convert the desired version into a VersionNumber VersionNumber MainVersion = VersionNumber.Parse(GetSDKForPlatform("Win64")!.GetMainVersion()); if (AllowedSdkDirs.Any(x => x.Key == MainVersion)) { WindowsSdkVersion = MainVersion; } // if it's not an installed version, use the highest installed version else if (AllowedSdkDirs.Any()) { WindowsSdkVersion = AllowedSdkDirs.Last().Key; } } // Get the actual directory for this version DirectoryReference? SdkDir; if (WindowsSdkVersion != null && CachedWindowsSdkDirs!.TryGetValue(WindowsSdkVersion, out SdkDir)) { OutSdkDir = SdkDir; OutSdkVersion = WindowsSdkVersion; return true; } else { OutSdkDir = null; OutSdkVersion = null; return false; } } #endregion #region Private implementation /// /// Updates the CachedWindowsSdkDirs and CachedUniversalCrtDirs variables /// [SupportedOSPlatform("windows")] private static void UpdateCachedWindowsSdks(ILogger Logger) { Dictionary WindowsSdkDirs = new Dictionary(); Dictionary UniversalCrtDirs = new Dictionary(); // Enumerate the Windows 8.1 SDK, if present DirectoryReference? InstallDir_8_1; if (TryReadInstallDirRegistryKey32("Microsoft\\Microsoft SDKs\\Windows\\v8.1", "InstallationFolder", out InstallDir_8_1)) { if (FileReference.Exists(FileReference.Combine(InstallDir_8_1, "Include", "um", "windows.h"))) { Logger.LogDebug("Found Windows 8.1 SDK at {InstallDir_8_1}", InstallDir_8_1); VersionNumber Version_8_1 = new VersionNumber(8, 1); WindowsSdkDirs[Version_8_1] = InstallDir_8_1; } } // Find all the root directories for Windows 10 SDKs List InstallDirs_10 = new List(); EnumerateSdkRootDirs(InstallDirs_10, Logger); // Enumerate all the Windows 10 SDKs foreach (DirectoryReference InstallDir_10 in InstallDirs_10.Distinct()) { DirectoryReference IncludeRootDir = DirectoryReference.Combine(InstallDir_10, "Include"); if (DirectoryReference.Exists(IncludeRootDir)) { foreach (DirectoryReference IncludeDir in DirectoryReference.EnumerateDirectories(IncludeRootDir)) { VersionNumber? IncludeVersion; if (VersionNumber.TryParse(IncludeDir.GetDirectoryName(), out IncludeVersion)) { if (FileReference.Exists(FileReference.Combine(IncludeDir, "um", "windows.h"))) { Logger.LogDebug("Found Windows 10 SDK version {IncludeVersion} at {InstallDir_10}", IncludeVersion, InstallDir_10); WindowsSdkDirs[IncludeVersion] = InstallDir_10; } if (FileReference.Exists(FileReference.Combine(IncludeDir, "ucrt", "corecrt.h"))) { Logger.LogDebug("Found Universal CRT version {IncludeVersion} at {InstallDir_10}", IncludeVersion, InstallDir_10); UniversalCrtDirs[IncludeVersion] = InstallDir_10; } } } } } CachedWindowsSdkDirs = WindowsSdkDirs; CachedUniversalCrtDirs = UniversalCrtDirs; } /// /// Enumerates all the Windows 10 SDK root directories /// /// Receives all the Windows 10 sdk root directories /// Logger for output [SupportedOSPlatform("windows")] public static void EnumerateSdkRootDirs(List RootDirs, ILogger Logger) { DirectoryReference? RootDir; if (TryReadInstallDirRegistryKey32("Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10", out RootDir)) { Logger.LogDebug("Found Windows 10 SDK root at {RootDir} (1)", RootDir); RootDirs.Add(RootDir); } if (TryReadInstallDirRegistryKey32("Microsoft\\Microsoft SDKs\\Windows\\v10.0", "InstallationFolder", out RootDir)) { Logger.LogDebug("Found Windows 10 SDK root at {RootDir} (2)", RootDir); RootDirs.Add(RootDir); } DirectoryReference? HostAutoSdkDir; if (TryGetHostPlatformAutoSDKDir(out HostAutoSdkDir)) { DirectoryReference WindowsKitsDirAutoSdk = DirectoryReference.Combine(HostAutoSdkDir, "Win64", "Windows Kits"); if (DirectoryReference.Exists(WindowsKitsDirAutoSdk)) { foreach (DirectoryReference RootDirAutoSdk in DirectoryReference.EnumerateDirectories(WindowsKitsDirAutoSdk)) { DirectoryReference IncludeRootDir = DirectoryReference.Combine(RootDirAutoSdk, "Include"); if (DirectoryReference.Exists(IncludeRootDir)) { Logger.LogDebug("Found Windows 10 AutoSDK root at {RootDirAutoSdk}", RootDirAutoSdk); RootDirs.Add(RootDirAutoSdk); } } } } } #endregion #endregion #region NetFx /// /// Gets the installation directory for the NETFXSDK /// /// Receives the installation directory on success /// True if the directory was found, false otherwise [SupportedOSPlatform("windows")] public static bool TryGetNetFxSdkInstallDir([NotNullWhen(true)] out DirectoryReference? OutInstallDir) { DirectoryReference? HostAutoSdkDir; string[] PreferredVersions = new string[] { "4.6.2", "4.6.1", "4.6" }; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out HostAutoSdkDir)) { foreach (string PreferredVersion in PreferredVersions) { DirectoryReference NetFxDir = DirectoryReference.Combine(HostAutoSdkDir, "Win64", "Windows Kits", "NETFXSDK", PreferredVersion); if (FileReference.Exists(FileReference.Combine(NetFxDir, "Include", "um", "mscoree.h"))) { OutInstallDir = NetFxDir; return true; } } } string NetFxSDKKeyName = "Microsoft\\Microsoft SDKs\\NETFXSDK"; foreach (string PreferredVersion in PreferredVersions) { if (TryReadInstallDirRegistryKey32(NetFxSDKKeyName + "\\" + PreferredVersion, "KitsInstallationFolder", out OutInstallDir)) { return true; } } // If we didn't find one of our preferred versions for NetFXSDK, use the max version present on the system Version? MaxVersion = null; string? MaxVersionString = null; foreach (string ExistingVersionString in ReadInstallDirSubKeys32(NetFxSDKKeyName)) { Version? ExistingVersion; if (!Version.TryParse(ExistingVersionString, out ExistingVersion)) { continue; } if (MaxVersion == null || ExistingVersion.CompareTo(MaxVersion) > 0) { MaxVersion = ExistingVersion; MaxVersionString = ExistingVersionString; } } if (MaxVersionString != null) { return TryReadInstallDirRegistryKey32(NetFxSDKKeyName + "\\" + MaxVersionString, "KitsInstallationFolder", out OutInstallDir); } OutInstallDir = null; return false; } #region Private implementation [SupportedOSPlatform("windows")] static readonly Lazy[]> InstallDirRoots = new Lazy[]>(() => new KeyValuePair[] { new KeyValuePair(Registry.CurrentUser, "SOFTWARE\\"), new KeyValuePair(Registry.LocalMachine, "SOFTWARE\\"), new KeyValuePair(Registry.CurrentUser, "SOFTWARE\\Wow6432Node\\"), new KeyValuePair(Registry.LocalMachine, "SOFTWARE\\Wow6432Node\\") }); /// /// Reads an install directory for a 32-bit program from a registry key. This checks for per-user and machine wide settings, and under the Wow64 virtual keys (HKCU\SOFTWARE, HKLM\SOFTWARE, HKCU\SOFTWARE\Wow6432Node, HKLM\SOFTWARE\Wow6432Node). /// /// Path to the key to read, under one of the roots listed above. /// Value to be read. /// On success, the directory corresponding to the value read. /// True if the key was read, false otherwise. [SupportedOSPlatform("windows")] public static bool TryReadInstallDirRegistryKey32(string KeySuffix, string ValueName, [NotNullWhen(true)] out DirectoryReference? InstallDir) { foreach (KeyValuePair InstallRoot in InstallDirRoots.Value) { using (RegistryKey? Key = InstallRoot.Key.OpenSubKey(InstallRoot.Value + KeySuffix)) { if (Key != null && TryReadDirRegistryKey(Key.Name, ValueName, out InstallDir)) { return true; } } } InstallDir = null; return false; } /// /// For each root location relevant to install dirs, look for the given key and add its subkeys to the set of subkeys to return. /// This checks for per-user and machine wide settings, and under the Wow64 virtual keys (HKCU\SOFTWARE, HKLM\SOFTWARE, HKCU\SOFTWARE\Wow6432Node, HKLM\SOFTWARE\Wow6432Node). /// /// The subkey to look for under each root location /// A list of unique subkeys found under any of the existing subkeys [SupportedOSPlatform("windows")] static string[] ReadInstallDirSubKeys32(string KeyName) { HashSet AllSubKeys = new HashSet(StringComparer.Ordinal); foreach (KeyValuePair Root in InstallDirRoots.Value) { using (RegistryKey? Key = Root.Key.OpenSubKey(Root.Value + KeyName)) { if (Key == null) { continue; } foreach (string SubKey in Key.GetSubKeyNames()) { AllSubKeys.Add(SubKey); } } } return AllSubKeys.ToArray(); } /// /// Attempts to reads a directory name stored in a registry key /// /// Key to read from /// Value within the key to read /// The directory read from the registry key /// True if the key was read, false if it was missing or empty [SupportedOSPlatform("windows")] static bool TryReadDirRegistryKey(string KeyName, string ValueName, [NotNullWhen(true)] out DirectoryReference? Value) { string? StringValue = Registry.GetValue(KeyName, ValueName, null) as string; if (String.IsNullOrEmpty(StringValue)) { Value = null; return false; } else { Value = new DirectoryReference(StringValue); return true; } } #endregion #endregion #region Toolchain #region Public Interface /// /// Gets the MSBuild path, and throws an exception on failure. /// /// Path to MSBuild [SupportedOSPlatform("windows")] public static FileReference GetMsBuildToolPath(ILogger Logger) { FileReference? Location; if (!TryGetMsBuildPath(Logger, out Location)) { throw new BuildException("Unable to find installation of MSBuild."); } return Location; } /// /// Determines if a given compiler is installed /// /// Compiler to check for /// Architecture the compiler must support /// Logger for output /// True if the given compiler is installed public static bool HasCompiler(WindowsCompiler Compiler, UnrealArch Architecture, ILogger Logger) { return FindToolChainInstallations(Compiler, Logger).Where(x => (x.Architecture == Architecture)).Any(); } /// /// Determines if a given compiler is installed and valid /// /// Compiler to check for /// Architecture the compiler must support /// Logger for output /// True if the given compiler is installed and valid public static bool HasValidCompiler(WindowsCompiler Compiler, UnrealArch Architecture, ILogger Logger) { // since this is static, we get the instance for it MicrosoftPlatformSDK SDK = (MicrosoftPlatformSDK)GetSDKForPlatform("Win64")!; return FindToolChainInstallations(Compiler, Logger).Where(x => (x.Error == null && x.Architecture == Architecture)).Any(); } /// /// Determines the directory containing the MSVC toolchain /// /// Major version of the compiler to use /// The minimum compiler version to use /// Architecture that is required /// Logger for output /// Receives the chosen toolchain version /// Receives the directory containing the toolchain /// Receives the optional directory containing redistributable components /// True if the toolchain directory was found correctly public static bool TryGetToolChainDir(WindowsCompiler Compiler, string? CompilerVersion, UnrealArch Architecture, ILogger Logger, [NotNullWhen(true)] out VersionNumber? OutToolChainVersion, [NotNullWhen(true)] out DirectoryReference? OutToolChainDir, out DirectoryReference? OutRedistDir) { // Find all the installed toolchains List ToolChains = FindToolChainInstallations(Compiler, Logger); // Figure out the actual version number that we want ToolChainInstallation? ToolChain = null; if (String.IsNullOrEmpty(CompilerVersion)) { ToolChain = SelectToolChain(ToolChains, x => x, Architecture); if (ToolChain == null) { OutToolChainVersion = null; OutToolChainDir = null; OutRedistDir = null; return false; } } else if (VersionNumber.TryParse(CompilerVersion, out VersionNumber? ToolChainVersion)) { ToolChain = SelectToolChain(ToolChains, x => x.ThenByDescending(x => x.Version == ToolChainVersion).ThenByDescending(x => x.Family == ToolChainVersion), Architecture); if (ToolChain == null || !(ToolChain.Version == ToolChainVersion || ToolChain.Family == ToolChainVersion)) { DumpToolChains(ToolChains, x => x.ThenByDescending(x => x.Version == ToolChainVersion).ThenByDescending(x => x.Family == ToolChainVersion), Architecture, Logger); throw new BuildException("Unable to find valid {0} C++ toolchain for {1} {2}", ToolChainVersion, Compiler, Architecture.ToString()); } } foreach (WindowsCompilerChannel Channel in Enum.GetValues(typeof(WindowsCompilerChannel))) { if (String.Equals(CompilerVersion, Channel.ToString(), StringComparison.OrdinalIgnoreCase)) { ToolChain = SelectToolChain(ToolChains.Where(x => x.ReleaseChannel.HasFlag(Channel)), x => x.ThenBy(x => x.ReleaseChannel).ThenByDescending(x => x.Version), Architecture); if (ToolChain == null) { DumpToolChains(ToolChains, x => x.ThenBy(x => x.ReleaseChannel).ThenByDescending(x => x.Version), Architecture, Logger); throw new BuildException("Unable to find valid {0} C++ toolchain for {1} {2}", Channel, GetCompilerName(Compiler), Architecture); } break; } } if (ToolChain == null) { DumpToolChains(ToolChains, x => x.ThenBy(x => x.ReleaseChannel).ThenByDescending(x => x.Version), Architecture, Logger); throw new BuildException("Unable to find {0} {1} C++ toolchain; '{2}' is an invalid version", GetCompilerName(Compiler), Architecture, CompilerVersion); } // Get the actual directory for this version OutToolChainVersion = ToolChain.Version; OutToolChainDir = ToolChain.BaseDir; OutRedistDir = ToolChain.RedistDir; return true; } /// /// Returns the human-readable name of the given compiler /// /// The compiler value /// Name of the compiler public static string GetCompilerName(WindowsCompiler Compiler) { switch (Compiler) { case WindowsCompiler.VisualStudio2022: return "Visual Studio 2022"; default: return Compiler.ToString(); } } #endregion #region Private implementation /// /// Select which toolchain to use, combining a custom preference with a default sort order /// /// /// Ordering function /// Architecture that must be supported /// static ToolChainInstallation? SelectToolChain(IEnumerable ToolChains, Func, IOrderedEnumerable> Preference, UnrealArch Architecture) { ToolChainInstallation? ToolChain = Preference(ToolChains.Where(x => x.Architecture == Architecture && x.HostArchitecture == MicrosoftPlatformSDK.MSVCHostUnrealArch) .OrderByDescending(x => x.Error == null)) .ThenBy(x => x.ReleaseChannel) .ThenBy(x => x.FamilyRank) .ThenByDescending(x => x.IsAutoSdk) .ThenByDescending(x => x.Version) .FirstOrDefault(); // Allow x64 host toolchains on arm64 if no native arm64 toolchains are available, and not running via wine if (!BuildHostPlatform.Current.IsRunningOnWine() && MicrosoftPlatformSDK.MSVCHostUnrealArch == UnrealArch.Arm64 && (ToolChain == null || ToolChain.Error != null)) { ToolChain = Preference(ToolChains.Where(x => x.Architecture == Architecture && x.HostArchitecture != MicrosoftPlatformSDK.MSVCHostUnrealArch) .OrderByDescending(x => x.Error == null)) .ThenBy(x => x.ReleaseChannel) .ThenBy(x => x.FamilyRank) .ThenByDescending(x => x.IsAutoSdk) .ThenByDescending(x => x.Version) .FirstOrDefault(); } if (ToolChain?.Error != null) { if (!IgnoreToolchainErrors) { throw new BuildException(ToolChain.Error); } // If errors are ignored, log the warning instead Log.TraceWarningOnce(ToolChain.Error); } return ToolChain; } /// /// Dump all available toolchain info, combining a custom preference with a default sort order /// /// /// Ordering function /// Architecture that must be supported /// The ILogger interface to write to static void DumpToolChains(IEnumerable ToolChains, Func, IOrderedEnumerable> Preference, UnrealArch Architecture, ILogger Logger) { IOrderedEnumerable SortedToolChains = Preference(ToolChains.Where(x => x.Architecture == Architecture) .OrderByDescending(x => x.Error == null)) .ThenBy(x => x.ReleaseChannel) .ThenBy(x => x.FamilyRank) .ThenByDescending(x => x.IsAutoSdk) .ThenByDescending(x => x.Version) .ThenBy(x => x.HostArchitecture.ToString()); if (SortedToolChains.Any()) { IEnumerable AvailableToolChains = SortedToolChains.Where(x => x.Error == null); IEnumerable UnavailableToolChains = SortedToolChains.Where(x => x.Error != null); if (AvailableToolChains.Any()) { Logger.LogInformation("Available {Architecture} toolchains ({Count}):", Architecture, AvailableToolChains.Count()); foreach (ToolChainInstallation ToolChain in AvailableToolChains) { Logger.LogInformation(" * {ToolChainDir}\n (Family={Family}, FamilyRank={FamilyRank}, Version={Version}, HostArchitecture={HostArchitecture}, ReleaseChannel={ReleaseChannel}, Architecture={Arch})", ToolChain.BaseDir, ToolChain.Family, ToolChain.FamilyRank, ToolChain.Version, ToolChain.HostArchitecture, ToolChain.ReleaseChannel, ToolChain.Architecture); } } if (UnavailableToolChains.Any()) { Logger.LogWarning("Unavailable {Architecture} toolchains ({Count}):", Architecture, UnavailableToolChains.Count()); foreach (ToolChainInstallation ToolChain in UnavailableToolChains.Where(x => x.Error != null)) { Logger.LogWarning(" * {ToolChainDir}\n (Family={Family}, FamilyRank={FamilyRank}, Version={Version}, HostArchitecture={HostArchitecture}, ReleaseChannel={ReleaseChannel}, Architecture={Arch}, Error=\"{Error}\")", ToolChain.BaseDir, ToolChain.Family, ToolChain.FamilyRank, ToolChain.Version, ToolChain.HostArchitecture, ToolChain.ReleaseChannel, ToolChain.Architecture, ToolChain.Error); } } } else { Logger.LogInformation("No available {Architecture} toolchains found", Architecture); } } /// /// Dump all available toolchain info for a compiler /// /// The compiler to filter by /// Architecture that must be supported /// The ILogger interface to write to public static void DumpAllToolChainInstallations(WindowsCompiler Compiler, UnrealArch Architecture, ILogger Logger) { List? ToolChains = CachedToolChainInstallations.GetValueOrDefault(Compiler); if (ToolChains != null) { DumpToolChains(ToolChains, x => x.ThenBy(x => x.ReleaseChannel).ThenByDescending(x => x.Version), Architecture, Logger); } } static List FindToolChainInstallations(WindowsCompiler Compiler, ILogger Logger) { return CachedToolChainInstallations.GetOrAdd(Compiler, _ => { List? ToolChains = new List(); if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64) { if (Compiler == WindowsCompiler.Clang) { // Check for a manual installation to the default directory DirectoryReference ManualInstallDir = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.ProgramFiles)!, "LLVM"); AddClangToolChain(Compiler, ManualInstallDir, ToolChains, IsAutoSdk: false, Logger); // Check for a manual installation to a custom directory string? LlvmPath = Environment.GetEnvironmentVariable("LLVM_PATH"); if (!String.IsNullOrEmpty(LlvmPath)) { AddClangToolChain(Compiler, new DirectoryReference(LlvmPath), ToolChains, IsAutoSdk: false, Logger); } // Check for installations bundled with Visual Studio 2022 foreach (VisualStudioInstallation Installation in FindVisualStudioInstallations(WindowsCompiler.VisualStudio2022, Logger)) { AddClangToolChain(Compiler, DirectoryReference.Combine(Installation.BaseDir, "VC", "Tools", "Llvm"), ToolChains, IsAutoSdk: false, Logger); AddClangToolChain(Compiler, DirectoryReference.Combine(Installation.BaseDir, "VC", "Tools", "Llvm", "x64"), ToolChains, IsAutoSdk: false, Logger); AddClangToolChain(Compiler, DirectoryReference.Combine(Installation.BaseDir, "VC", "Tools", "Llvm", "ARM64"), ToolChains, IsAutoSdk: false, Logger); } // Check for AutoSDK paths DirectoryReference? AutoSdkDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out AutoSdkDir)) { DirectoryReference ClangBaseDir = DirectoryReference.Combine(AutoSdkDir, "Win64", "LLVM"); if (DirectoryReference.Exists(ClangBaseDir)) { foreach (DirectoryReference ToolChainDir in DirectoryReference.EnumerateDirectories(ClangBaseDir)) { AddClangToolChain(Compiler, ToolChainDir, ToolChains, IsAutoSdk: true, Logger); } } } } else if (Compiler == WindowsCompiler.ClangRTFM) { // the AutoRTFM Clang compiler is in-tree DirectoryReference AutoRTFMDir = DirectoryReference.Combine(Unreal.EngineDirectory, "Restricted", "NotForLicensees", "Binaries", "Win64", "AutoRTFM"); AddClangToolChain(Compiler, AutoRTFMDir, ToolChains, IsAutoSdk: false, Logger); // Check for AutoSDK paths DirectoryReference? AutoSdkDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out AutoSdkDir)) { DirectoryReference ClangBaseDir = DirectoryReference.Combine(AutoSdkDir, "Win64", "AutoRTFM"); if (DirectoryReference.Exists(ClangBaseDir)) { foreach (DirectoryReference ToolChainDir in DirectoryReference.EnumerateDirectories(ClangBaseDir)) { AddClangToolChain(Compiler, ToolChainDir, ToolChains, IsAutoSdk: true, Logger); } } } } else if (Compiler == WindowsCompiler.ClangInstrument) { // the UnrealInstrumentation Clang compiler is in-tree DirectoryReference UnrealInstrumentationDir = DirectoryReference.Combine(Unreal.EngineDirectory, "Binaries", "Win64", "UnrealInstrumentation"); AddClangToolChain(Compiler, UnrealInstrumentationDir, ToolChains, IsAutoSdk: false, Logger); // Check for AutoSDK paths DirectoryReference? AutoSdkDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out AutoSdkDir)) { DirectoryReference ClangBaseDir = DirectoryReference.Combine(AutoSdkDir, "Win64", "UnrealInstrumentation"); if (DirectoryReference.Exists(ClangBaseDir)) { foreach (DirectoryReference ToolChainDir in DirectoryReference.EnumerateDirectories(ClangBaseDir)) { AddClangToolChain(Compiler, ToolChainDir, ToolChains, IsAutoSdk: true, Logger); } } } } else if (Compiler == WindowsCompiler.ClangCustom) { // Check for a manual installation to a custom directory string? LlvmPath = Environment.GetEnvironmentVariable("LLVM_CUSTOM_PATH"); if (!String.IsNullOrEmpty(LlvmPath)) { AddClangToolChain(Compiler, new DirectoryReference(LlvmPath), ToolChains, IsAutoSdk: false, Logger); } else { throw new BuildException("ClangCustom needs LLVM_CUSTOM_PATH environment variable to be defined"); } } else if (Compiler == WindowsCompiler.Intel) { DirectoryReference InstallDir = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.ProgramFiles)!, "Intel", "oneAPI", "compiler"); FindIntelOneApiToolChains(InstallDir, ToolChains, IsAutoSdk: false, Logger); InstallDir = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.ProgramFilesX86)!, "Intel", "oneAPI", "compiler"); FindIntelOneApiToolChains(InstallDir, ToolChains, IsAutoSdk: false, Logger); // Check for AutoSDK paths DirectoryReference? AutoSdkDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out AutoSdkDir)) { DirectoryReference IntelBaseDir = DirectoryReference.Combine(AutoSdkDir, "Win64", "Intel"); if (DirectoryReference.Exists(IntelBaseDir)) { FindIntelOneApiToolChains(IntelBaseDir, ToolChains, IsAutoSdk: true, Logger); } } } else if (Compiler.IsMSVC()) { // Enumerate all the manually installed toolchains List Installations = FindVisualStudioInstallations(Compiler, Logger); foreach (VisualStudioInstallation Installation in Installations) { DirectoryReference ToolChainBaseDir = DirectoryReference.Combine(Installation.BaseDir, "VC", "Tools", "MSVC"); DirectoryReference RedistBaseDir = DirectoryReference.Combine(Installation.BaseDir, "VC", "Redist", "MSVC"); FindVisualStudioToolChains(ToolChainBaseDir, RedistBaseDir, Installation.ReleaseChannel, ToolChains, IsAutoSdk: false, Logger); } // Enumerate all the AutoSDK toolchains DirectoryReference? PlatformDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out PlatformDir)) { string VSDir = String.Empty; switch (Compiler) { case WindowsCompiler.VisualStudio2022: VSDir = "VS2022"; break; } if (!String.IsNullOrEmpty(VSDir)) { DirectoryReference ReleaseBaseDir = DirectoryReference.Combine(PlatformDir, "Win64", VSDir); FindVisualStudioToolChains(ReleaseBaseDir, null, WindowsCompilerChannel.Latest, ToolChains, IsAutoSdk: true, Logger); DirectoryReference PreviewBaseDir = DirectoryReference.Combine(PlatformDir, "Win64", $"{VSDir}-Preview"); FindVisualStudioToolChains(PreviewBaseDir, null, WindowsCompilerChannel.Preview, ToolChains, IsAutoSdk: true, Logger); DirectoryReference ExperimentalBaseDir = DirectoryReference.Combine(PlatformDir, "Win64", $"{VSDir}-Experimental"); FindVisualStudioToolChains(ExperimentalBaseDir, null, WindowsCompilerChannel.Experimental, ToolChains, IsAutoSdk: true, Logger); } } } else { throw new BuildException("Unsupported compiler version ({0})", Compiler); } foreach (ToolChainInstallation? ToolChain in ToolChains.Where(x => x.Error == null && x.HostArchitecture == MSVCHostUnrealArch)) { ToolChainInstallation? CrossToolChain; if (Compiler.IsMSVC()) { // MSVC has the same base dir. Version may not match between Hostx64 and Hostarm64. CrossToolChain = ToolChains.FirstOrDefault(x => x.Error == null && x.Architecture == ToolChain.Architecture && x.ReleaseChannel == ToolChain.ReleaseChannel && x.BaseDir == ToolChain.BaseDir && x.HostArchitecture != MSVCHostUnrealArch); } else { // Clang the version should match. CrossToolChain = ToolChains.FirstOrDefault(x => x.Error == null && x.Architecture == ToolChain.Architecture && x.ReleaseChannel == ToolChain.ReleaseChannel && x.Version == ToolChain.Version && x.HostArchitecture != MSVCHostUnrealArch); } if (CrossToolChain != null) { DirectoryReference HostBinDir; DirectoryReference CrossBinDir; if (Compiler.IsMSVC()) { HostBinDir = DirectoryReference.Combine(ToolChain.BaseDir, "bin", UnrealArchToMSVCHostDirectoryName(ToolChain.HostArchitecture)); CrossBinDir = DirectoryReference.Combine(CrossToolChain.BaseDir, "bin", UnrealArchToMSVCHostDirectoryName(CrossToolChain.HostArchitecture)); } else { HostBinDir = DirectoryReference.Combine(ToolChain.BaseDir, MSVCHostUnrealArch.bIsX64 ? "bin" : "bin-woa64"); CrossBinDir = DirectoryReference.Combine(CrossToolChain.BaseDir, !MSVCHostUnrealArch.bIsX64 ? "bin" : "bin-woa64"); } if (FileReference.Exists(HostBinDir) && FileReference.Exists(CrossBinDir)) { EpicGames.UBA.Utils.RegisterCrossArchitecturePath(HostBinDir.FullName, CrossBinDir.FullName); } } } } return ToolChains; }); } /// /// Read the Visual Studio install directory for the given compiler version. Note that it is possible for the compiler toolchain to be installed without /// Visual Studio, and vice versa. /// /// List of directories containing Visual Studio installations public static IReadOnlyList FindVisualStudioInstallations(ILogger Logger) { if (CachedVisualStudioInstallations != null) { return CachedVisualStudioInstallations; } List Installations = new List(); if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64) { try { SetupConfiguration Setup = new SetupConfiguration(); IEnumSetupInstances Enumerator = Setup.EnumAllInstances(); ISetupInstance[] Instances = new ISetupInstance[1]; for (; ; ) { int NumFetched; Enumerator.Next(1, Instances, out NumFetched); if (NumFetched == 0) { break; } ISetupInstance2 Instance = (ISetupInstance2)Instances[0]; if ((Instance.GetState() & InstanceState.Local) != InstanceState.Local) { continue; } VersionNumber? Version; if (!VersionNumber.TryParse(Instance.GetInstallationVersion(), out Version)) { continue; } int MajorVersion = Version.GetComponent(0); WindowsCompiler Compiler; if (Version >= MicrosoftPlatformSDK.MinimumVisualStudio2022Version) // Treat any newer versions as 2022, until we have an explicit enum for them { Compiler = WindowsCompiler.VisualStudio2022; } else // Unsupported older versions still detected for legacy MSBuild projects, but will not be used to compile { #pragma warning disable CS0618 // Type or member is obsolete Compiler = WindowsCompiler.VisualStudioUnsupported; // Unsupported but still detected for legacy MSBuild projects #pragma warning restore CS0618 // Type or member is obsolete } ISetupInstanceCatalog? Catalog = Instance as ISetupInstanceCatalog; WindowsCompilerChannel ReleaseChannel = Catalog?.IsPrerelease() == true ? WindowsCompilerChannel.Preview : WindowsCompilerChannel.Latest; string ProductId = Instance.GetProduct().GetId(); bool bCommunity = ProductId.Equals("Microsoft.VisualStudio.Product.Community", StringComparison.Ordinal); DirectoryReference BaseDir = new DirectoryReference(Instance.GetInstallationPath()); Installations.Add(new VisualStudioInstallation(Compiler, Version, BaseDir, bCommunity, ReleaseChannel)); Logger.LogDebug("Found {VisualStudio} installation: {BaseDir} (Product={ProductId}, Version={Version})", Compiler, BaseDir, ProductId, Version); } Installations = Installations.OrderByDescending(x => x.Compiler) .ThenBy(x => x.bCommunity) .ThenBy(x => x.ReleaseChannel) .ThenByDescending(x => x.Version) .ToList(); } catch (Exception Ex) { Logger.LogDebug(Ex, "Unable to enumerate Visual Studio installations"); } } CachedVisualStudioInstallations = Installations; return Installations; } /// /// Read the Visual Studio install directory for the given compiler version. Note that it is possible for the compiler toolchain to be installed without /// Visual Studio, and vice versa. /// /// List of directories containing Visual Studio installations public static List FindVisualStudioInstallations(WindowsCompiler Compiler, ILogger Logger) { return FindVisualStudioInstallations(Logger).Where(x => x.Compiler == Compiler).ToList(); } /// /// Finds all the valid Visual Studio toolchains under the given base directory /// /// Base directory to search /// Optional directory for redistributable components (DLLs etc) /// The release channel of this installation /// Map of tool chain version to installation info /// Whether this folder contains AutoSDK entries /// Logger for output static void FindVisualStudioToolChains(DirectoryReference BaseDir, DirectoryReference? OptionalRedistDir, WindowsCompilerChannel ReleaseChannel, List ToolChains, bool IsAutoSdk, ILogger Logger) { if (DirectoryReference.Exists(BaseDir)) { foreach (DirectoryReference ToolChainDir in DirectoryReference.EnumerateDirectories(BaseDir)) { VersionNumber? Version; if (IsValidToolChainDirMSVC(ToolChainDir, UnrealArch.X64, out Version)) { DirectoryReference? RedistDir = FindVisualStudioRedistForToolChain(ToolChainDir, OptionalRedistDir, Version); AddVisualCppToolChain(Version, UnrealArch.X64, ReleaseChannel, ToolChainDir, RedistDir, ToolChains, IsAutoSdk, Logger); } if (IsValidToolChainDirMSVC(ToolChainDir, UnrealArch.Arm64, out Version)) { DirectoryReference? RedistDir = FindVisualStudioRedistForToolChain(ToolChainDir, OptionalRedistDir, Version); AddVisualCppToolChain(Version, UnrealArch.Arm64, ReleaseChannel, ToolChainDir, RedistDir, ToolChains, IsAutoSdk, Logger); } } } } /// /// Finds the most appropriate redist directory for the given toolchain version /// /// /// /// /// static DirectoryReference? FindVisualStudioRedistForToolChain(DirectoryReference ToolChainDir, DirectoryReference? OptionalRedistDir, VersionNumber Version) { DirectoryReference? RedistDir; if (OptionalRedistDir == null) { RedistDir = DirectoryReference.Combine(ToolChainDir, "redist"); // AutoSDK keeps redist under the toolchain } else { RedistDir = DirectoryReference.Combine(OptionalRedistDir, ToolChainDir.GetDirectoryName()); // exact redist not found - find highest version (they are backwards compatible) if (!DirectoryReference.Exists(RedistDir) && DirectoryReference.Exists(OptionalRedistDir)) { RedistDir = DirectoryReference.EnumerateDirectories(OptionalRedistDir) .Where(X => VersionNumber.TryParse(X.GetDirectoryName(), out VersionNumber? DirVersion)) .OrderByDescending(X => VersionNumber.Parse(X.GetDirectoryName())) .FirstOrDefault(); } } if (RedistDir != null && DirectoryReference.Exists(RedistDir)) { return RedistDir; } return null; } /// /// Adds a Visual C++ toolchain to a list of installations /// /// /// /// /// /// /// /// /// static void AddVisualCppToolChain(VersionNumber Version, UnrealArch HostArchitecture, WindowsCompilerChannel ReleaseChannel, DirectoryReference ToolChainDir, DirectoryReference? RedistDir, List ToolChains, bool IsAutoSdk, ILogger Logger) { VersionNumber? Family; if (!VersionNumber.TryParse(ToolChainDir.GetDirectoryName(), out Family)) { Family = Version; } int FamilyRank = PreferredVisualCppVersions.TakeWhile(x => !x.Contains(Family)).Count(); string? Error = null; if (Version < MinimumVisualCppVersion) { Error = $"UnrealBuildTool requires at minimum the MSVC {MinimumVisualCppVersion} toolchain. Please install a later toolchain such as {PreferredVisualCppVersions.Select(x => x.Min).Max()} from the Visual Studio installer."; } VersionNumberRange? Banned = BannedVisualCppVersions.FirstOrDefault(x => x.Contains(Version)); if (Banned != null) { Error = $"UnrealBuildTool has banned the MSVC {Banned} toolchains due to compiler issues. Please install a different toolchain such as {PreferredVisualCppVersions.Select(x => x.Min).Max()} by opening the generated solution and installing recommended components or from the Visual Studio installer."; } if (HasX64ToolChain(ToolChainDir, HostArchitecture)) { Logger.LogDebug("Found Visual Studio toolchain: {ToolChainDir} (Family={Family}, FamilyRank={FamilyRank}, Version={Version}, HostArchitecture={HostArchitecture}, ReleaseChannel={ReleaseChannel}, Architecture={Arch}, Error={Error}, Redist={RedistDir})", ToolChainDir, Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.X64.ToString(), Error != null, RedistDir); ToolChains.Add(new ToolChainInstallation(Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.X64, Error, ToolChainDir, RedistDir, IsAutoSdk)); } if (HasArm64ToolChain(ToolChainDir, HostArchitecture)) { Logger.LogDebug("Found Visual Studio toolchain: {ToolChainDir} (Family={Family}, FamilyRank={FamilyRank}, Version={Version}, HostArchitecture={HostArchitecture}, ReleaseChannel={ReleaseChannel}, Architecture={Arch}, Error={Error}, Redist={RedistDir})", ToolChainDir, Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.Arm64.ToString(), Error != null, RedistDir); ToolChains.Add(new ToolChainInstallation(Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.Arm64, Error, ToolChainDir, RedistDir, IsAutoSdk)); if (HasArm64ECToolChain(ToolChainDir)) { Logger.LogDebug("Found Visual Studio toolchain: {ToolChainDir} (Family={Family}, FamilyRank={FamilyRank}, Version={Version}, HostArchitecture={HostArchitecture}, ReleaseChannel={ReleaseChannel}, Architecture={Arch}, Error={Error}, Redist={RedistDir})", ToolChainDir, Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.Arm64ec.ToString(), Error != null, RedistDir); ToolChains.Add(new ToolChainInstallation(Family, FamilyRank, Version, HostArchitecture, ReleaseChannel, UnrealArch.Arm64ec, Error, ToolChainDir, RedistDir, IsAutoSdk)); } } } /// /// Add a Clang toolchain /// /// /// /// /// /// /// static void AddClangToolChain(WindowsCompiler Compiler, DirectoryReference ToolChainDir, FileReference CompilerFile, List ToolChains, bool IsAutoSdk, ILogger Logger) { if (FileReference.Exists(CompilerFile)) { FileVersionInfo VersionInfo = FileVersionInfo.GetVersionInfo(CompilerFile.FullName); VersionNumber Version = new VersionNumber(VersionInfo.FileMajorPart, VersionInfo.FileMinorPart, VersionInfo.FileBuildPart); VersionNumber Family = new VersionNumber(VersionInfo.FileMajorPart); int Rank = PreferredClangVersions.TakeWhile(x => !x.Contains(Version)).Count(); string? Error = null; if (Version < MinimumClangVersion) { Error = $"UnrealBuildTool requires at minimum the Clang {MinimumClangVersion} toolchain. Please install a later toolchain such as {PreferredClangVersions.Select(x => x.Min).Max()} from LLVM."; } UnrealArch HostArchitecture; if (IsX64Executable(CompilerFile)) { HostArchitecture = UnrealArch.X64; } else if (IsArm64Executable(CompilerFile)) { HostArchitecture = UnrealArch.Arm64; } else { // Unknown binary architecture HostArchitecture = UnrealArch.Deprecated; Error = $"Clang binary is not x64 or arm64."; } Logger.LogDebug("Found Clang toolchain: {ToolChainDir} (Version={Version}, HostArchitecture={HostArchitecture}, Rank={Rank}, Error={Error})", ToolChainDir, Version, HostArchitecture, Rank, Error != null); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.X64, Error, ToolChainDir, null, IsAutoSdk)); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.Arm64, Error, ToolChainDir, null, IsAutoSdk)); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.Arm64ec, Error, ToolChainDir, null, IsAutoSdk)); } } /// /// Add a Clang toolchain /// /// /// /// /// /// static void AddClangToolChain(WindowsCompiler Compiler, DirectoryReference ToolChainDir, List ToolChains, bool IsAutoSdk, ILogger Logger) { string CompilerFilename = Compiler == WindowsCompiler.ClangRTFM ? "verse-clang-cl.exe" : (Compiler == WindowsCompiler.ClangInstrument ? "instr-clang-cl.exe" : "clang-cl.exe"); AddClangToolChain(Compiler, ToolChainDir, FileReference.Combine(ToolChainDir, "bin", CompilerFilename), ToolChains, IsAutoSdk, Logger); AddClangToolChain(Compiler, ToolChainDir, FileReference.Combine(ToolChainDir, "bin-woa64", CompilerFilename), ToolChains, IsAutoSdk, Logger); } /// /// Add an Intel OneAPI toolchain /// /// /// /// /// static void AddIntelOneApiToolChain(DirectoryReference ToolChainDir, List ToolChains, bool IsAutoSdk, ILogger Logger) { FileReference CompilerFile = FileReference.Combine(ToolChainDir, "bin", "icx.exe"); if (FileReference.Exists(CompilerFile)) { FileVersionInfo VersionInfo = FileVersionInfo.GetVersionInfo(CompilerFile.FullName); VersionNumber Version = new VersionNumber(VersionInfo.FileMajorPart, VersionInfo.FileMinorPart, VersionInfo.FileBuildPart); VersionNumber Family = new VersionNumber(VersionInfo.FileMajorPart); // The icx.exe version may be lower than the toolchain folder version, so use that instead if available if (VersionNumber.TryParse(ToolChainDir.GetDirectoryName(), out VersionNumber? FolderVersion) && FolderVersion > Version) { Version = FolderVersion; } int Rank = PreferredIntelOneApiVersions.TakeWhile(x => !x.Contains(Version)).Count(); bool Is64Bit = IsX64Executable(CompilerFile); string? Error = null; if (Version < MinimumIntelOneApiVersion) { Error = $"UnrealBuildTool requires at minimum the Intel OneAPI {MinimumIntelOneApiVersion} toolchain. Please install a later toolchain such as {PreferredIntelOneApiVersions.Select(x => x.Min).Max()} from Intel."; } UnrealArch HostArchitecture; if (IsX64Executable(CompilerFile)) { HostArchitecture = UnrealArch.X64; } else if (IsArm64Executable(CompilerFile)) { HostArchitecture = UnrealArch.Arm64; } else { // Unknown binary architecture HostArchitecture = UnrealArch.Deprecated; Error = $"Clang binary is not x64 or arm64."; } Logger.LogDebug("Found Intel OneAPI toolchain: {ToolChainDir} (Version={Version}, HostArchitecture={HostArchitecture}, Rank={Rank}, Error={Error})", ToolChainDir, Version, Is64Bit, Rank, Error != null); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.X64, Error, ToolChainDir, null, IsAutoSdk)); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.Arm64, Error, ToolChainDir, null, IsAutoSdk)); ToolChains.Add(new ToolChainInstallation(Family, Rank, Version, HostArchitecture, WindowsCompilerChannel.Any, UnrealArch.Arm64ec, Error, ToolChainDir, null, IsAutoSdk)); } } /// /// Finds all the valid Intel oneAPI toolchains under the given base directory /// /// Base directory to search /// Map of tool chain version to installation info /// /// Logger for output static void FindIntelOneApiToolChains(DirectoryReference BaseDir, List ToolChains, bool IsAutoSdk, ILogger Logger) { if (DirectoryReference.Exists(BaseDir)) { foreach (DirectoryReference ToolChainDir in DirectoryReference.EnumerateDirectories(BaseDir)) { AddIntelOneApiToolChain(ToolChainDir, ToolChains, IsAutoSdk, Logger); } } } /// /// The compiler host directory name for the running process architecture. /// public static UnrealArch MSVCHostUnrealArch => RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? UnrealArch.Arm64 : UnrealArch.X64; /// /// The compiler host directory name for an UnrealArch. /// /// The binary architecture for the toolchain public static string UnrealArchToMSVCHostDirectoryName(UnrealArch HostArchitecture) => HostArchitecture.bIsX64 ? "Hostx64" : "Hostarm64"; /// /// The compiler host directory name for the running process architecture. /// public static string MSVCHostDirectoryName => UnrealArchToMSVCHostDirectoryName(MSVCHostUnrealArch); /// /// Test whether an executable is a specific machine type /// /// Executable to test /// The machine type to check /// static bool IsExecutableMachineType(FileReference File, ushort MachineType) { using (FileStream Stream = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { byte[] Header = new byte[64]; if (Stream.Read(Header, 0, Header.Length) != Header.Length) { return false; } if (Header[0] != (byte)'M' || Header[1] != (byte)'Z') { return false; } int Offset = BinaryPrimitives.ReadInt32LittleEndian(Header.AsSpan(0x3c)); if (Stream.Seek(Offset, SeekOrigin.Begin) != Offset) { return false; } byte[] PeHeader = new byte[6]; if (Stream.Read(PeHeader, 0, PeHeader.Length) != PeHeader.Length) { return false; } if (BinaryPrimitives.ReadUInt32BigEndian(PeHeader.AsSpan()) != 0x50450000) { return false; } return MachineType == BinaryPrimitives.ReadUInt16LittleEndian(PeHeader.AsSpan(4)); } } /// /// Test whether an executable is x64 /// /// Executable to test /// static bool IsX64Executable(FileReference File) { const ushort IMAGE_FILE_MACHINE_AMD64 = 0x8664; return IsExecutableMachineType(File, IMAGE_FILE_MACHINE_AMD64); } /// /// Test whether an executable is arm64 /// /// Executable to test /// static bool IsArm64Executable(FileReference File) { const ushort IMAGE_FILE_MACHINE_ARM64 = 0xaa64; return IsExecutableMachineType(File, IMAGE_FILE_MACHINE_ARM64); } /// /// Determines if the given path is a valid Visual C++ version number /// /// The toolchain directory /// The binary architecture for the toolchain /// The version number for the toolchain /// True if the path is a valid version static bool IsValidToolChainDirMSVC(DirectoryReference ToolChainDir, UnrealArch HostArchitecture, [NotNullWhen(true)] out VersionNumber? Version) { FileReference CompilerExe = FileReference.Combine(ToolChainDir, "bin", UnrealArchToMSVCHostDirectoryName(HostArchitecture), "x64", "cl.exe"); if (!FileReference.Exists(CompilerExe)) { CompilerExe = FileReference.Combine(ToolChainDir, "bin", UnrealArchToMSVCHostDirectoryName(HostArchitecture), "arm64", "cl.exe"); } if (!FileReference.Exists(CompilerExe)) { Version = null; return false; } FileVersionInfo VersionInfo = FileVersionInfo.GetVersionInfo(CompilerExe.FullName); if (VersionInfo.ProductMajorPart != 0) { Version = new VersionNumber(VersionInfo.ProductMajorPart, VersionInfo.ProductMinorPart, VersionInfo.ProductBuildPart); return true; } return VersionNumber.TryParse(ToolChainDir.GetDirectoryName(), out Version); } /// /// Checks if the given directory contains a x64 toolchain. /// /// Directory to check /// The host binary architecture to check /// True if the given directory contains a 64-bit toolchain static bool HasX64ToolChain(DirectoryReference ToolChainDir, UnrealArch HostArchitecture) { return FileReference.Exists(FileReference.Combine(ToolChainDir, "bin", UnrealArchToMSVCHostDirectoryName(HostArchitecture), "x64", "cl.exe")); } /// /// Checks if the given directory contains an arm64 toolchain. /// /// Directory to check /// The host binary architecture to check /// True if the given directory contains the arm64 toolchain static bool HasArm64ToolChain(DirectoryReference ToolChainDir, UnrealArch HostArchitecture) { return FileReference.Exists(FileReference.Combine(ToolChainDir, "bin", UnrealArchToMSVCHostDirectoryName(HostArchitecture), "arm64", "cl.exe")); } /// /// Checks if the given directory contains an arm64ec toolchain. /// /// Directory to check /// True if the given directory contains the arm64ec toolchain static bool HasArm64ECToolChain(DirectoryReference ToolChainDir) { return FileReference.Exists(FileReference.Combine(ToolChainDir, "lib", "arm64ec", "binmode.obj")); } /// /// Determines if an IDE for the given compiler is installed. /// /// Compiler to check for /// Logger for output /// True if the given compiler is installed public static bool HasIDE(WindowsCompiler Compiler, ILogger Logger) { return FindVisualStudioInstallations(Compiler, Logger).Count > 0; } /// /// Gets the path to MSBuild. This mirrors the logic in GetMSBuildPath.bat. /// /// Logger for output /// On success, receives the path to the MSBuild executable. /// True on success. [SupportedOSPlatform("windows")] public static bool TryGetMsBuildPath(ILogger Logger, [NotNullWhen(true)] out FileReference? OutLocation) { // Get the Visual Studio install directory #pragma warning disable CS0618 // Type or member is obsolete IEnumerable InstallDirs = FindVisualStudioInstallations(WindowsCompiler.VisualStudio2022, Logger) .Concat(FindVisualStudioInstallations(WindowsCompiler.VisualStudioUnsupported, Logger)) .Select(x => x.BaseDir); #pragma warning restore CS0618 // Type or member is obsolete foreach (DirectoryReference InstallDir in InstallDirs) { FileReference MsBuildLocation = FileReference.Combine(InstallDir, "MSBuild", "Current", "Bin", "MSBuild.exe"); if (FileReference.Exists(MsBuildLocation)) { OutLocation = MsBuildLocation; return true; } } OutLocation = null; return false; } /// /// Function to query the registry under HKCU/HKLM Win32/Wow64 software registry keys for a certain install directory. /// This mirrors the logic in GetMSBuildPath.bat. /// /// [SupportedOSPlatform("windows")] static bool TryReadMsBuildInstallPath(string KeyRelativePath, string KeyName, string MsBuildRelativePath, [NotNullWhen(true)] out FileReference? OutMsBuildPath) { string[] KeyBasePaths = { @"HKEY_CURRENT_USER\SOFTWARE\", @"HKEY_LOCAL_MACHINE\SOFTWARE\", @"HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\", @"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\" }; foreach (string KeyBasePath in KeyBasePaths) { string? Value = Registry.GetValue(KeyBasePath + KeyRelativePath, KeyName, null) as string; if (Value != null) { FileReference MsBuildPath = FileReference.Combine(new DirectoryReference(Value), MsBuildRelativePath); if (FileReference.Exists(MsBuildPath)) { OutMsBuildPath = MsBuildPath; return true; } } } OutMsBuildPath = null; return false; } #endregion // Toolchain Private #endregion // Toolchain #region Dia Sdk /// /// Determines the directory containing the MSVC toolchain /// /// Major version of the compiler to use /// Map of version number to directories public static List FindDiaSdkDirs(WindowsCompiler Compiler) { return CachedDiaSdkDirs.GetOrAdd(Compiler, _ => { List DiaSdkDirs = new List(); DirectoryReference? PlatformDir; if (UEBuildPlatformSDK.TryGetHostPlatformAutoSDKDir(out PlatformDir)) { string VSDir = String.Empty; switch (Compiler) { case WindowsCompiler.VisualStudio2022: VSDir = "VS2022"; break; } if (!String.IsNullOrEmpty(VSDir)) { DirectoryReference DiaSdkDir = DirectoryReference.Combine(PlatformDir, "Win64", "DIA SDK", VSDir); if (IsValidDiaSdkDir(DiaSdkDir)) { DiaSdkDirs.Add(DiaSdkDir); } } } List VisualStudioDirs = MicrosoftPlatformSDK.FindVisualStudioInstallations(Compiler, Log.Logger).ConvertAll(x => x.BaseDir); foreach (DirectoryReference VisualStudioDir in VisualStudioDirs) { DirectoryReference DiaSdkDir = DirectoryReference.Combine(VisualStudioDir, "DIA SDK"); if (IsValidDiaSdkDir(DiaSdkDir)) { DiaSdkDirs.Add(DiaSdkDir); } } return DiaSdkDirs; }); } /// /// Determines if a directory contains a valid DIA SDK /// /// The directory to check /// True if it contains a valid DIA SDK static bool IsValidDiaSdkDir(DirectoryReference DiaSdkDir) { return FileReference.Exists(FileReference.Combine(DiaSdkDir, "bin", "amd64", "msdia140.dll")); } #endregion // Dia #endregion // Windows Specific SDK } /// /// Information about a particular toolchain installation /// [DebuggerDisplay("{BaseDir}")] internal class ToolChainInstallation { /// /// The version "family" (ie. the nominal version number of the directory this toolchain is installed to) /// public VersionNumber Family { get; } /// /// Index into the preferred version range /// public int FamilyRank { get; } /// /// The actual version number of this toolchain /// public VersionNumber Version { get; } /// /// The host architecture of this ToolChainInstallation (multiple ToolChainInstallation instances may be created, one per host architecture). /// public UnrealArch HostArchitecture { get; } /// /// The release channel of this toolchain /// public WindowsCompilerChannel ReleaseChannel { get; } /// /// The architecture of this ToolChainInstallation (multiple ToolChainInstallation instances may be created, one per architecture). /// public UnrealArch Architecture { get; } /// /// Reason for this toolchain not being compatible /// public string? Error { get; } /// /// Base directory for the toolchain /// public DirectoryReference BaseDir { get; } /// /// Base directory for the redistributable components /// public DirectoryReference? RedistDir { get; } /// /// Whether this toolchain comes from AutoSDK. /// public bool IsAutoSdk { get; } /// /// Constructor /// /// /// /// /// The architecture of the binaries in this toolchain /// The release channel of this toolchain /// The target architecture this toolchain can compile /// /// Base directory for the toolchain /// Optional directory for redistributable components (DLLs etc) /// Whether this toolchain comes from AutoSDK public ToolChainInstallation(VersionNumber Family, int FamilyRank, VersionNumber Version, UnrealArch HostArchitecture, WindowsCompilerChannel ReleaseChannel, UnrealArch Architecture, string? Error, DirectoryReference BaseDir, DirectoryReference? RedistDir, bool IsAutoSdk) { this.Family = Family; this.FamilyRank = FamilyRank; this.Version = Version; this.HostArchitecture = HostArchitecture; this.ReleaseChannel = ReleaseChannel; this.Architecture = Architecture; this.Error = Error; this.BaseDir = BaseDir; this.RedistDir = RedistDir; this.IsAutoSdk = IsAutoSdk; } } }