// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
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;
///
/// Allow use of Rad linker
///
public readonly bool bAllowRadLinker;
///
/// The path to the compiler for compiling code
///
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;
///
/// The path to the toolchain compiler for compiling code
///
public readonly FileReference ToolchainCompilerPath;
///
/// 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)
{
Compiler = Params.Compiler;
CompilerDir = Params.CompilerDir;
CompilerVersion = Params.CompilerVersion;
Architecture = Params.Architecture;
ToolChain = Params.ToolChain;
ToolChainDir = Params.ToolChainDir;
ToolChainVersion = Params.ToolChainVersion;
WindowsSdkDir = Params.WindowsSdkDir;
WindowsSdkVersion = Params.WindowsSdkVersion;
RedistDir = Params.RedistDir;
bUseCPPWinRT = Params.bUseCPPWinRT;
bAllowClangLinker = Params.bAllowClangLinker;
bAllowRadLinker = Params.bAllowRadLinker;
// 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);
ToolchainCompilerPath = GetCompilerToolPath(ToolChain, Architecture, ToolChainDir);
// Get the resource compiler path from the Windows SDK
ResourceCompilerPath = GetResourceCompilerToolPath(WindowsSdkDir, WindowsSdkVersion, Logger);
// Register any executables for cross-architecture support with UBA
RegisterCrossArchitectureToolPaths(WindowsSdkDir, WindowsSdkVersion);
// 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);
Environment.SetEnvironmentVariable("VC_TOOLCHAIN_DIR", ToolchainCompilerPath.Directory.FullName, EnvironmentVariableTarget.Process);
DirectoryReference.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).
DirectoryReference.AddDirectoryToPath(GetVCToolPath(ToolChainDir, UnrealArch.X64));
}
// Add the Windows SDK directory to the path too, for mt.exe.
if (WindowsSdkVersion >= new VersionNumber(10))
{
string BuildHostArch = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64 ? "arm64" : "x64";
DirectoryReference.AddDirectoryToPath(DirectoryReference.Combine(WindowsSdkDir, "bin", WindowsSdkVersion.ToString(), BuildHostArch));
}
}
///
/// Gets the path to the VC 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", MicrosoftPlatformSDK.MSVCHostDirectoryName, 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 Clang tool binaries.
///
/// Base directory for the Clang toolchain
/// Directory containing the 64-bit toolchain binaries
public static DirectoryReference GetClangToolPath(DirectoryReference ClangToolChainDir)
{
// If on an arm64 host, use arm64 binaries if available, otherwise use x64
if (!MicrosoftPlatformSDK.MSVCHostUnrealArch.bIsX64 && DirectoryReference.Exists(DirectoryReference.Combine(ClangToolChainDir, "bin-woa64")))
{
return DirectoryReference.Combine(ClangToolChainDir, "bin-woa64");
}
return DirectoryReference.Combine(ClangToolChainDir, "bin");
}
///
/// Gets the path to the compiler.
///
static FileReference GetCompilerToolPath(WindowsCompiler Compiler, UnrealArch Architecture, DirectoryReference CompilerDir)
{
if (Compiler == WindowsCompiler.Clang)
{
return FileReference.Combine(GetClangToolPath(CompilerDir), "clang-cl.exe");
}
else if (Compiler == WindowsCompiler.ClangRTFM)
{
return FileReference.Combine(GetClangToolPath(CompilerDir), "verse-clang-cl.exe");
}
else if (Compiler == WindowsCompiler.ClangInstrument)
{
return FileReference.Combine(GetClangToolPath(CompilerDir), "instr-clang-cl.exe");
}
else if (Compiler == WindowsCompiler.ClangCustom)
{
return FileReference.Combine(CompilerDir, "bin", "clang-cl.exe");
}
else if (Compiler == WindowsCompiler.Intel)
{
return FileReference.Combine(CompilerDir, "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 (bAllowRadLinker)
{
return FileReference.Combine(UnrealBuildBase.Unreal.EngineDirectory, "Binaries", "Win64", "RadDebugger", Architecture.ToString(), "radlink.exe");
}
else if ((Compiler == WindowsCompiler.Clang || Compiler == WindowsCompiler.ClangRTFM || Compiler == WindowsCompiler.ClangInstrument || Compiler == WindowsCompiler.ClangCustom) && bAllowClangLinker)
{
return FileReference.Combine(GetClangToolPath(CompilerDir), "lld-link.exe");
}
else if (Compiler == WindowsCompiler.Intel && bAllowClangLinker)
{
return FileReference.Combine(CompilerDir, "bin", "compiler", "lld-link.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 (bAllowClangLinker && Compiler.IsClang())
{
// Since obj files could be LLVM IR Stream file format (when building with ltcg) we can't use link.exe. UbaObjTool support all formats and produce identical lib files as link.exe when running non-ltcg
DirectoryReference Dir = DirectoryReference.Combine(UnrealBuildBase.Unreal.EngineDirectory, "Binaries", "Win64", "UnrealBuildAccelerator", Architecture.ToString());
return FileReference.Combine(Dir, "UbaObjTool.exe");
}
return FileReference.Combine(GetVCToolPath(ToochainDir, Architecture), "link.exe"); // We add /LIB to cmd line so we can use link.exe directly instead of going via lib.exe
}
///
/// Gets the path to the resource compiler.
///
protected virtual FileReference GetResourceCompilerToolPath(DirectoryReference WindowsSdkDir, VersionNumber WindowsSdkVersion, ILogger Logger)
{
string BuildHostArch = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.Arm64 ? "arm64" : "x64";
FileReference ResourceCompilerPath = FileReference.Combine(WindowsSdkDir, "bin", WindowsSdkVersion.ToString(), BuildHostArch, "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);
}
///
/// Registers all SDK binaries with UBA for cross-architecture support
///
void RegisterCrossArchitectureToolPaths(DirectoryReference WindowsSdkDir, VersionNumber WindowsSdkVersion)
{
IEnumerable ToolPaths = [
DirectoryReference.Combine(WindowsSdkDir, "bin", WindowsSdkVersion.ToString()),
DirectoryReference.Combine(Unreal.EngineDirectory, "Binaries", "Win64", "RadDebugger"),
DirectoryReference.Combine(Unreal.EngineDirectory, "Binaries", "Win64", "UnrealBuildAccelerator")
];
foreach (DirectoryReference ToolPath in ToolPaths)
{
DirectoryReference HostBinDir = DirectoryReference.Combine(ToolPath, MicrosoftPlatformSDK.MSVCHostUnrealArch.bIsX64 ? "x64" : "arm64");
DirectoryReference CrossBinDir = DirectoryReference.Combine(ToolPath, !MicrosoftPlatformSDK.MSVCHostUnrealArch.bIsX64 ? "x64" : "arm64");
if (FileReference.Exists(HostBinDir) && FileReference.Exists(CrossBinDir))
{
EpicGames.UBA.Utils.RegisterCrossArchitecturePath(HostBinDir.FullName, CrossBinDir.FullName);
}
}
}
///
/// Return the standard Visual C++ library path.
///
protected virtual DirectoryReference GetToolChainLibsDir()
{
string ArchFolder = Architecture.WindowsSystemLibDir;
// Add the standard Visual C++ library paths
if (Compiler.IsIntel() && bAllowClangLinker)
{
VersionNumber ClangVersion = MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(CompilerPath);
return DirectoryReference.Combine(CompilerDir, "lib", "clang", ClangVersion.GetComponent(0).ToString(), "lib", "windows");
}
else 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)
{
VersionNumber ClangVersion = MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(CompilerPath);
IncludePaths.Add(DirectoryReference.Combine(CompilerDir, "compiler", "include"));
IncludePaths.Add(DirectoryReference.Combine(CompilerDir, "lib", "clang", ClangVersion.GetComponent(0).ToString(), "include"));
LibraryPaths.Add(DirectoryReference.Combine(CompilerDir, "lib"));
}
}
///
/// 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 compiler version to use
/// The specific toolchain version to use (if the compiler isn't msvc)
/// 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
/// Allow use of Rad 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? ToolchainVersion, string? WindowsSdkVersion, string? SuppliedSdkDirectoryForVersion, bool bUseCPPWinRT, bool bAllowClangLinker, bool bAllowRadLinker, ILogger Logger)
{
return Create(new VCEnvironmentParameters(Compiler, ToolChain, Platform, Architecture, CompilerVersion, ToolchainVersion, WindowsSdkVersion, SuppliedSdkDirectoryForVersion, bUseCPPWinRT, bAllowClangLinker, bAllowRadLinker, 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;
/// Allow use of Rad linker
public bool bAllowRadLinker;
///
/// 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 compiler version to use
/// The specific toolchain version to use (if the compiler isn't msvc)
/// 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
/// Allow use of Rad linker
/// Logger for output
/// Creation parameters for VC environment
[SupportedOSPlatform("windows")]
public VCEnvironmentParameters(WindowsCompiler Compiler, WindowsCompiler ToolChain, UnrealTargetPlatform Platform, UnrealArch Architecture, string? CompilerVersion, string? ToolchainVersion, string? WindowsSdkVersion, string? SuppliedSdkDirectoryForVersion, bool bUseCPPWinRT, bool bAllowClangLinker, bool bAllowRadLinker, 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, ToolchainVersion, Architecture, Logger, out SelectedToolChainVersion, out SelectedToolChainDir, out SelectedRedistDir))
{
throw new BuildException("{0} must be installed in order to build this target.", 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))
{
MicrosoftPlatformSDK.DumpWindowsSdkDirs(Logger);
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;
CompilerDir = SelectedCompilerDir;
this.CompilerVersion = SelectedCompilerVersion;
this.Architecture = Architecture;
this.ToolChain = ToolChain;
ToolChainDir = SelectedToolChainDir;
ToolChainVersion = SelectedToolChainVersion;
WindowsSdkDir = SelectedWindowsSdkDir;
this.WindowsSdkVersion = SelectedWindowsSdkVersion;
RedistDir = SelectedRedistDir;
this.bUseCPPWinRT = bUseCPPWinRT;
this.bAllowClangLinker = bAllowClangLinker;
this.bAllowRadLinker = bAllowRadLinker;
}
}
}