// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.Win32; using System.Text; using EpicGames.Core; using UnrealBuildBase; using System.Runtime.Versioning; using Microsoft.Extensions.Logging; namespace UnrealBuildTool { /// /// Stores information about a Visual C++ installation and compile environment /// class VCEnvironment { /// /// The compiler version /// public readonly WindowsCompiler Compiler; /// /// The compiler directory /// public readonly DirectoryReference CompilerDir; /// /// The compiler version /// public readonly VersionNumber CompilerVersion; /// /// The compiler Architecture /// public readonly UnrealArch Architecture; /// /// The underlying toolchain to use. Using Clang/ICL will piggy-back on a Visual Studio toolchain for the CRT, linker, etc... /// public readonly WindowsCompiler ToolChain; /// /// Root directory containing the toolchain /// public readonly DirectoryReference ToolChainDir; /// /// The toolchain version number /// public readonly VersionNumber ToolChainVersion; /// /// Root directory containing the Windows Sdk /// public readonly DirectoryReference WindowsSdkDir; /// /// Version number of the Windows Sdk /// public readonly VersionNumber WindowsSdkVersion; /// /// Use the CPP/WinRT language projection /// public readonly bool bUseCPPWinRT; /// /// Allow use of Clang linker /// public readonly bool bAllowClangLinker; /// /// The path to the linker for linking executables /// public readonly FileReference CompilerPath; /// /// The path to the linker for linking executables /// public readonly FileReference LinkerPath; /// /// The path to the linker for linking libraries /// public readonly FileReference LibraryManagerPath; /// /// Path to the resource compiler from the Windows SDK /// public readonly FileReference ResourceCompilerPath; /// /// Optional directory containing redistributable items (DLLs etc) /// public readonly DirectoryReference? RedistDir = null; /// /// The default system include paths /// public readonly List IncludePaths = new List(); /// /// The default system library paths /// public readonly List LibraryPaths = new List(); /// /// Constructor /// /// Main constructor parameters /// Logger for output [SupportedOSPlatform("windows")] public VCEnvironment(VCEnvironmentParameters Params, ILogger Logger) { this.Compiler = Params.Compiler; this.CompilerDir = Params.CompilerDir; this.CompilerVersion = Params.CompilerVersion; this.Architecture = Params.Architecture; this.ToolChain = Params.ToolChain; this.ToolChainDir = Params.ToolChainDir; this.ToolChainVersion = Params.ToolChainVersion; this.WindowsSdkDir = Params.WindowsSdkDir; this.WindowsSdkVersion = Params.WindowsSdkVersion; this.RedistDir = Params.RedistDir; this.bUseCPPWinRT = Params.bUseCPPWinRT; this.bAllowClangLinker = Params.bAllowClangLinker; // Get the compiler and linker paths from the Toolchain directory CompilerPath = GetCompilerToolPath(Compiler, Architecture, CompilerDir); LinkerPath = GetLinkerToolPath(Compiler, Architecture, CompilerDir, ToolChainDir); LibraryManagerPath = GetLibraryLinkerToolPath(Compiler, Architecture, CompilerDir, ToolChainDir); // Get the resource compiler path from the Windows SDK ResourceCompilerPath = GetResourceCompilerToolPath(WindowsSdkDir, WindowsSdkVersion, Logger); // Get all the system include paths SetupEnvironment(Logger); } /// /// Updates environment variables needed for running with this toolchain /// public void SetEnvironmentVariables() { // Add the compiler path and directory as environment variables for the process so they may be used elsewhere. Environment.SetEnvironmentVariable("VC_COMPILER_PATH", CompilerPath.FullName, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("VC_COMPILER_DIR", CompilerPath.Directory.FullName, EnvironmentVariableTarget.Process); AddDirectoryToPath(GetVCToolPath(ToolChainDir, Architecture)); if (Architecture == UnrealArch.Arm64) { // Add both toolchain paths to the PATH environment variable. There are some support DLLs which are only added to one of the paths, but which the toolchain in the other directory // needs to run (eg. mspdbcore.dll). AddDirectoryToPath(GetVCToolPath(ToolChainDir, UnrealArch.X64)); } // Add the Windows SDK directory to the path too, for mt.exe. if (WindowsSdkVersion >= new VersionNumber(10)) { string BuildHostArch = Architecture.ToString(); if (Architecture == UnrealArch.Arm64 || Architecture == UnrealArch.Arm64ec) { Debug.Assert(BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64); BuildHostArch = "x64"; } AddDirectoryToPath(DirectoryReference.Combine(WindowsSdkDir, "bin", WindowsSdkVersion.ToString(), BuildHostArch)); } } /// /// Add a directory to the PATH environment variable /// /// The path to add static void AddDirectoryToPath(DirectoryReference ToolPath) { string PathEnvironmentVariable = Environment.GetEnvironmentVariable("PATH") ?? ""; if (!PathEnvironmentVariable.Split(';').Any(x => String.Compare(x, ToolPath.FullName, true) == 0)) { PathEnvironmentVariable = ToolPath.FullName + ";" + PathEnvironmentVariable; Environment.SetEnvironmentVariable("PATH", PathEnvironmentVariable); } } /// /// Gets the path to the tool binaries. /// /// Base directory for the VC toolchain /// Target Architecture /// Directory containing the 64-bit toolchain binaries public static DirectoryReference GetVCToolPath(DirectoryReference VCToolChainDir, UnrealArch Architecture) { FileReference CompilerPath = FileReference.Combine(VCToolChainDir, "bin", "Hostx64", Architecture.WindowsToolChain, "cl.exe"); if (FileReference.Exists(CompilerPath)) { return CompilerPath.Directory; } throw new BuildException("No required {0} compiler toolchain found in {1}", Architecture, VCToolChainDir); } /// /// Gets the path to the compiler. /// static FileReference GetCompilerToolPath(WindowsCompiler Compiler, UnrealArch Architecture, DirectoryReference CompilerDir) { if (Compiler == WindowsCompiler.Clang) { return FileReference.Combine(CompilerDir, "bin", "clang-cl.exe"); } else if (Compiler == WindowsCompiler.Intel) { return FileReference.Combine(CompilerDir, "windows", "bin", "icx.exe"); } return FileReference.Combine(GetVCToolPath(CompilerDir, Architecture), "cl.exe"); } /// /// Gets the path to the linker. /// FileReference GetLinkerToolPath(WindowsCompiler Compiler, UnrealArch Architecture, DirectoryReference CompilerDir, DirectoryReference ToochainDir) { if (Compiler == WindowsCompiler.Clang && bAllowClangLinker) { return FileReference.Combine(CompilerDir, "bin", "lld-link.exe"); } else if (Compiler == WindowsCompiler.Intel && bAllowClangLinker) { return FileReference.Combine(CompilerDir, "windows", "bin", "intel64", "xilink.exe"); } return FileReference.Combine(GetVCToolPath(ToochainDir, Architecture), "link.exe"); } /// /// Gets the path to the library linker. /// FileReference GetLibraryLinkerToolPath(WindowsCompiler Compiler, UnrealArch Architecture, DirectoryReference CompilerDir, DirectoryReference ToochainDir) { if (Compiler == WindowsCompiler.Clang && bAllowClangLinker) { // @todo: lld-link is not currently working for building .lib //return FileReference.Combine(CompilerDir, "bin", "lld-link.exe"); } else if (Compiler == WindowsCompiler.Intel && bAllowClangLinker) { return FileReference.Combine(CompilerDir, "windows", "bin", "intel64", "xilib.exe"); } return FileReference.Combine(GetVCToolPath(ToochainDir, Architecture), "lib.exe"); } /// /// Gets the path to the resource compiler. /// virtual protected FileReference GetResourceCompilerToolPath(DirectoryReference WindowsSdkDir, VersionNumber WindowsSdkVersion, ILogger Logger) { FileReference ResourceCompilerPath = FileReference.Combine(WindowsSdkDir, "bin", WindowsSdkVersion.ToString(), "x64", "rc.exe"); if(FileReference.Exists(ResourceCompilerPath)) { return ResourceCompilerPath; } ResourceCompilerPath = FileReference.Combine(WindowsSdkDir, "bin", "x64", "rc.exe"); if(FileReference.Exists(ResourceCompilerPath)) { return ResourceCompilerPath; } throw new BuildException("Unable to find path to the Windows resource compiler under {0} (version {1})", WindowsSdkDir, WindowsSdkVersion); } /// /// Return the standard Visual C++ library path. /// protected virtual DirectoryReference GetToolChainLibsDir() { string ArchFolder = Architecture.WindowsSystemLibDir; // Add the standard Visual C++ library paths if (ToolChain.IsMSVC()) { return DirectoryReference.Combine(ToolChainDir, "lib", ArchFolder); } else { DirectoryReference LibsPath = DirectoryReference.Combine(ToolChainDir, "LIB"); if (Architecture == UnrealArch.X64) { LibsPath = DirectoryReference.Combine(LibsPath, "amd64"); } return LibsPath; } } /// /// Sets up the standard compile environment for the toolchain /// [SupportedOSPlatform("windows")] private void SetupEnvironment(ILogger Logger) { string ArchFolder = Architecture.WindowsSystemLibDir; // Add the standard Visual C++ include paths IncludePaths.Add(DirectoryReference.Combine(ToolChainDir, "INCLUDE")); // Add the standard Visual C++ library paths LibraryPaths.Add(GetToolChainLibsDir()); // If we're on >= Visual Studio 2015 and using pre-Windows 10 SDK, we need to find a Windows 10 SDK and add the UCRT include paths if(ToolChain.IsMSVC() && WindowsSdkVersion < new VersionNumber(10)) { KeyValuePair Pair = MicrosoftPlatformSDK.FindUniversalCrtDirs(Logger).OrderByDescending(x => x.Key).FirstOrDefault(); if(Pair.Key == null || Pair.Key < new VersionNumber(10)) { throw new BuildException("{0} requires the Universal CRT to be installed.", WindowsPlatform.GetCompilerName(ToolChain)); } DirectoryReference IncludeRootDir = DirectoryReference.Combine(Pair.Value, "include", Pair.Key.ToString()); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "ucrt")); DirectoryReference LibraryRootDir = DirectoryReference.Combine(Pair.Value, "lib", Pair.Key.ToString()); LibraryPaths.Add(DirectoryReference.Combine(LibraryRootDir, "ucrt", ArchFolder)); } // Add the Windows SDK paths if (WindowsSdkVersion >= new VersionNumber(10)) { DirectoryReference IncludeRootDir = DirectoryReference.Combine(WindowsSdkDir, "include", WindowsSdkVersion.ToString()); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "ucrt")); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "shared")); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "um")); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "winrt")); if (bUseCPPWinRT) { IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "cppwinrt")); } DirectoryReference LibraryRootDir = DirectoryReference.Combine(WindowsSdkDir, "lib", WindowsSdkVersion.ToString()); LibraryPaths.Add(DirectoryReference.Combine(LibraryRootDir, "ucrt", ArchFolder)); LibraryPaths.Add(DirectoryReference.Combine(LibraryRootDir, "um", ArchFolder)); } else { DirectoryReference IncludeRootDir = DirectoryReference.Combine(WindowsSdkDir, "include"); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "shared")); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "um")); IncludePaths.Add(DirectoryReference.Combine(IncludeRootDir, "winrt")); DirectoryReference LibraryRootDir = DirectoryReference.Combine(WindowsSdkDir, "lib", "winv6.3"); LibraryPaths.Add(DirectoryReference.Combine(LibraryRootDir, "um", ArchFolder)); } // Add path to Intel math libraries when using Intel oneAPI if (Compiler == WindowsCompiler.Intel) { IncludePaths.Add(DirectoryReference.Combine(CompilerDir, "windows", "compiler", "include")); LibraryPaths.Add(DirectoryReference.Combine(CompilerDir, "windows", "compiler", "lib", "intel64_win")); } } /// /// Creates an environment with the given settings /// /// The compiler version to use /// The toolchain version to use, when a non-msvc compiler is used /// The platform to target /// The Architecture to target /// The specific toolchain version to use /// Version of the Windows SDK to use /// If specified, this is the SDK directory to use, otherwise, attempt to look up via registry. If specified, the WindowsSdkVersion is used directly /// Include the CPP/WinRT language projection /// Allow use of Clang linker /// Logger for output /// New environment object with paths for the given settings [SupportedOSPlatform("windows")] public static VCEnvironment Create(WindowsCompiler Compiler, WindowsCompiler ToolChain, UnrealTargetPlatform Platform, UnrealArch Architecture, string? CompilerVersion, string? WindowsSdkVersion, string? SuppliedSdkDirectoryForVersion, bool bUseCPPWinRT, bool bAllowClangLinker, ILogger Logger) { return Create( new VCEnvironmentParameters(Compiler, ToolChain, Platform, Architecture, CompilerVersion, WindowsSdkVersion, SuppliedSdkDirectoryForVersion, bUseCPPWinRT, bAllowClangLinker, Logger), Logger ); } /// /// Creates an environment with the given parameters /// [SupportedOSPlatform("windows")] public static VCEnvironment Create( VCEnvironmentParameters Params, ILogger Logger) { return new VCEnvironment(Params, Logger); } } /// /// Parameter structure for constructing VCEnvironment /// struct VCEnvironmentParameters { /// The platform to find the compiler for public UnrealTargetPlatform Platform; /// The compiler to use public WindowsCompiler Compiler; /// The compiler directory public DirectoryReference CompilerDir; /// The compiler version number public VersionNumber CompilerVersion; /// The compiler Architecture public UnrealArch Architecture; /// The base toolchain version public WindowsCompiler ToolChain; /// Directory containing the toolchain public DirectoryReference ToolChainDir; /// Version of the toolchain public VersionNumber ToolChainVersion; /// Root directory containing the Windows Sdk public DirectoryReference WindowsSdkDir; /// Version of the Windows Sdk public VersionNumber WindowsSdkVersion; /// Optional directory for redistributable items (DLLs etc) public DirectoryReference? RedistDir; /// Include the CPP/WinRT language projection public bool bUseCPPWinRT; /// Allow use of Clang linker public bool bAllowClangLinker; /// /// Creates VC environment construction parameters with the given settings /// /// The compiler version to use /// The toolchain version to use, when a non-msvc compiler is used /// The platform to target /// The Architecture to target /// The specific toolchain version to use /// Version of the Windows SDK to use /// If specified, this is the SDK directory to use, otherwise, attempt to look up via registry. If specified, the WindowsSdkVersion is used directly /// Include the CPP/WinRT language projection /// Allow use of Clang linker /// Logger for output /// Creation parameters for VC environment [SupportedOSPlatform("windows")] public VCEnvironmentParameters (WindowsCompiler Compiler, WindowsCompiler ToolChain, UnrealTargetPlatform Platform, UnrealArch Architecture, string? CompilerVersion, string? WindowsSdkVersion, string? SuppliedSdkDirectoryForVersion, bool bUseCPPWinRT, bool bAllowClangLinker, ILogger Logger) { // Get the compiler version info VersionNumber? SelectedCompilerVersion; DirectoryReference? SelectedCompilerDir; DirectoryReference? SelectedRedistDir; if(!WindowsPlatform.TryGetToolChainDir(Compiler, CompilerVersion, Architecture, Logger, out SelectedCompilerVersion, out SelectedCompilerDir, out SelectedRedistDir)) { throw new BuildException("{0}{1} {2} must be installed in order to build this target.", WindowsPlatform.GetCompilerName(Compiler), String.IsNullOrEmpty(CompilerVersion)? "" : String.Format(" ({0})", CompilerVersion), Architecture.ToString()); } // Get the toolchain info VersionNumber? SelectedToolChainVersion; DirectoryReference? SelectedToolChainDir; if (Compiler.IsClang()) { if (ToolChain.IsClang() || ToolChain == WindowsCompiler.Default) { throw new BuildException("{0} is not a valid ToolChain for Compiler {1}", WindowsPlatform.GetCompilerName(ToolChain), WindowsPlatform.GetCompilerName(Compiler)); } if (!WindowsPlatform.TryGetToolChainDir(ToolChain, null, Architecture, Logger, out SelectedToolChainVersion, out SelectedToolChainDir, out SelectedRedistDir)) { throw new BuildException("{0} or {1} must be installed in order to build this target.", WindowsPlatform.GetCompilerName(WindowsCompiler.VisualStudio2019), WindowsPlatform.GetCompilerName(WindowsCompiler.VisualStudio2022)); } } else { ToolChain = Compiler; SelectedToolChainVersion = SelectedCompilerVersion; SelectedToolChainDir = SelectedCompilerDir; } // Get the actual Windows SDK directory VersionNumber? SelectedWindowsSdkVersion; DirectoryReference? SelectedWindowsSdkDir; if (SuppliedSdkDirectoryForVersion != null) { SelectedWindowsSdkDir = new DirectoryReference(SuppliedSdkDirectoryForVersion); SelectedWindowsSdkVersion = VersionNumber.Parse(WindowsSdkVersion!); if (!DirectoryReference.Exists(SelectedWindowsSdkDir)) { throw new BuildException("Windows SDK{0} must be installed at {1}.", String.IsNullOrEmpty(WindowsSdkVersion) ? "" : String.Format(" ({0})", WindowsSdkVersion), SuppliedSdkDirectoryForVersion); } } else { if (!WindowsPlatform.TryGetWindowsSdkDir(WindowsSdkVersion, Logger, out SelectedWindowsSdkVersion, out SelectedWindowsSdkDir)) { throw new BuildException("Windows SDK{0} must be installed in order to build this target.", String.IsNullOrEmpty(WindowsSdkVersion) ? "" : String.Format(" ({0})", WindowsSdkVersion)); } } // Store the final parameters this.Platform = Platform; this.Compiler = Compiler; this.CompilerDir = SelectedCompilerDir; this.CompilerVersion = SelectedCompilerVersion; this.Architecture = Architecture; this.ToolChain = ToolChain; this.ToolChainDir = SelectedToolChainDir; this.ToolChainVersion = SelectedToolChainVersion; this.WindowsSdkDir = SelectedWindowsSdkDir; this.WindowsSdkVersion = SelectedWindowsSdkVersion; this.RedistDir = SelectedRedistDir; this.bUseCPPWinRT = bUseCPPWinRT; this.bAllowClangLinker = bAllowClangLinker; } } }