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

3696 lines
150 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Core;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using System.Text;
using System.Xml.Linq;
using UnrealBuildBase;
namespace UnrealBuildTool
{
class VCToolChain : ISPCToolChain
{
/// <summary>
/// The target being built
/// </summary>
protected ReadOnlyTargetRules Target;
/// <summary>
/// The Visual C++ environment
/// </summary>
protected VCEnvironment EnvVars;
public VCToolChain(ReadOnlyTargetRules Target, ILogger Logger)
: base(Logger)
{
this.Target = Target;
EnvVars = Target.WindowsPlatform.Environment!;
Logger.LogDebug("Compiler: {Path}", EnvVars.CompilerPath);
Logger.LogDebug("Linker: {Path}", EnvVars.LinkerPath);
Logger.LogDebug("Library Manager: {Path}", EnvVars.LibraryManagerPath);
Logger.LogDebug("Resource Compiler: {Path}", EnvVars.ResourceCompilerPath);
if (Target.WindowsPlatform.ObjSrcMapFile != null)
{
try
{
File.Delete(Target.WindowsPlatform.ObjSrcMapFile);
}
catch
{
}
}
}
public override FileReference? GetCppCompilerPath()
{
return EnvVars.CompilerPath;
}
public IEnumerable<DirectoryReference> GetVCIncludePaths()
{
return EnvVars.IncludePaths;
}
protected bool IsDynamicDebuggingEnabled => Target.WindowsPlatform.bDynamicDebugging
&& Target.WindowsPlatform.Compiler.IsMSVC()
&& !(Target.WindowsPlatform.bAllowRadLinker)
&& !(Target.WindowsPlatform.Environment != null && Target.WindowsPlatform.Environment.CompilerVersion < new VersionNumber(14, 44, 34918))
&& !(Target.bAllowLTCG || Target.bPGOOptimize || Target.bPGOProfile)
&& !(Target.Configuration == UnrealTargetConfiguration.Debug || Target.Configuration == UnrealTargetConfiguration.DebugGame)
&& !(Target.WindowsPlatform.bEnableAddressSanitizer)
&& !(Target.bSupportEditAndContinue);
protected void DynamicDebuggingInfo(List<string> Lines)
{
if (Target.WindowsPlatform.bDynamicDebugging)
{
if (IsDynamicDebuggingEnabled)
{
Lines.Add("Visual Studio Dynamic Debugging is enabled");
return;
}
Lines.Add("Visual Studio Dynamic Debugging has been disabled due to the following:");
if (!Target.WindowsPlatform.Compiler.IsMSVC())
{
Lines.Add($" * Target.{nameof(Target.WindowsPlatform)}.{nameof(Target.WindowsPlatform.Compiler)} '{Target.WindowsPlatform.Compiler}' is not MSVC");
}
if (Target.WindowsPlatform.bAllowRadLinker)
{
Lines.Add($" * Target.{nameof(Target.WindowsPlatform)}.{nameof(Target.WindowsPlatform.bAllowRadLinker)} is enabled");
}
if (Target.WindowsPlatform.Environment != null && Target.WindowsPlatform.Environment.CompilerVersion < new VersionNumber(14, 44, 34918))
{
Lines.Add($" * MSVC compiler version {Target.WindowsPlatform.Environment.CompilerVersion} older than 14.44.34918");
}
if (Target.bAllowLTCG || Target.bPGOOptimize || Target.bPGOProfile)
{
Lines.Add($" * Target.{nameof(Target.bAllowLTCG)}, Target.{nameof(Target.bPGOOptimize)}, or Target.{nameof(Target.bPGOProfile)} is enabled");
}
if (Target.Configuration == UnrealTargetConfiguration.Debug || Target.Configuration == UnrealTargetConfiguration.DebugGame)
{
Lines.Add($" * Target.{nameof(Target.Configuration)} '{Target.Configuration}' is Debug or DebugGame. Development, Test, or Shipping configuration is recommended when Dynamic Debugging is requested");
}
if (Target.WindowsPlatform.bEnableAddressSanitizer)
{
Lines.Add($" * Target.{nameof(Target.WindowsPlatform)}.{nameof(Target.WindowsPlatform.bEnableAddressSanitizer)} is enabled");
}
if (Target.bSupportEditAndContinue)
{
Lines.Add($" * Target.{nameof(Target.bSupportEditAndContinue)} is enabled");
}
}
}
/// <summary>
/// Prepares the environment for building
/// </summary>
public override void SetEnvironmentVariables()
{
EnvVars.SetEnvironmentVariables();
// This allows PGD files generated from this build to be portable to other machines. It needs to be set in all configurations so we don't split the environment
List<string> pgoPathTranslations = new() { $"{Unreal.RootDirectory.FullName}=ROOT" };
if (Target.ProjectFile != null)
{
pgoPathTranslations.Add($"{Target.ProjectFile.Directory.FullName}=PROJ");
}
Environment.SetEnvironmentVariable("PGO_PATH_TRANSLATION", String.Join(";", pgoPathTranslations));
// Don't allow the INCLUDE environment variable to propagate. It's set by the IDE based on the IncludePath property in the project files which we
// add to improve Visual Studio memory usage, but we don't actually need it to set when invoking the compiler. Doing so results in it being converted
// into /I arguments by the CL driver, which results in errors due to the command line not fitting into the PDB debug record.
Environment.SetEnvironmentVariable("INCLUDE", null);
// Don't allow the CL or _CL_ environment variable to propagate.
Environment.SetEnvironmentVariable("CL", null);
Environment.SetEnvironmentVariable("_CL_", null);
// Don't allow the LIBPATH environment variable to propagate.
Environment.SetEnvironmentVariable("LIBPATH", null);
}
/// <summary>
/// Returns the version info for the toolchain. This will be output before building.
/// </summary>
/// <returns>String describing the current toolchain</returns>
public override void GetVersionInfo(List<string> Lines)
{
if (EnvVars.Compiler == EnvVars.ToolChain)
{
Lines.Add($"Using {WindowsPlatform.GetCompilerName(EnvVars.Compiler)} {EnvVars.ToolChainVersion} toolchain ({EnvVars.ToolChainDir}) and Windows {EnvVars.WindowsSdkVersion} SDK ({EnvVars.WindowsSdkDir}).");
}
else if (EnvVars.Compiler == WindowsCompiler.Intel)
{
Lines.Add($"Using {WindowsPlatform.GetCompilerName(EnvVars.Compiler)} {EnvVars.CompilerVersion} compiler ({EnvVars.CompilerDir}) based on Clang {MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath)} with {WindowsPlatform.GetCompilerName(EnvVars.ToolChain)} {EnvVars.ToolChainVersion} runtime ({EnvVars.ToolChainDir}) and Windows {EnvVars.WindowsSdkVersion} SDK ({EnvVars.WindowsSdkDir}).");
}
else
{
Lines.Add($"Using {WindowsPlatform.GetCompilerName(EnvVars.Compiler)} {EnvVars.CompilerVersion} compiler ({EnvVars.CompilerDir}) with {WindowsPlatform.GetCompilerName(EnvVars.ToolChain)} {EnvVars.ToolChainVersion} runtime ({EnvVars.ToolChainDir}) and Windows {EnvVars.WindowsSdkVersion} SDK ({EnvVars.WindowsSdkDir}).");
}
DynamicDebuggingInfo(Lines);
if (Target.WindowsPlatform.ToolchainVersionWarningLevel == WarningLevel.Warning)
{
if (!MicrosoftPlatformSDK.IsPreferredVersion(EnvVars.Compiler, EnvVars.CompilerVersion))
{
Lines.Add($"Warning: {WindowsPlatform.GetCompilerName(EnvVars.Compiler)} compiler is not a preferred version");
}
if (EnvVars.Compiler != EnvVars.ToolChain && !MicrosoftPlatformSDK.IsPreferredVersion(EnvVars.ToolChain, EnvVars.ToolChainVersion))
{
Lines.Add($"Warning: {WindowsPlatform.GetCompilerName(EnvVars.ToolChain)} runtime is not a preferred version");
}
}
}
public override void GetExternalDependencies(HashSet<FileItem> ExternalDependencies)
{
base.GetExternalDependencies(ExternalDependencies);
ExternalDependencies.Add(FileItem.GetItemByFileReference(EnvVars.CompilerPath));
ExternalDependencies.Add(FileItem.GetItemByFileReference(EnvVars.LinkerPath));
ExternalDependencies.Add(FileItem.GetItemByFileReference(EnvVars.ResourceCompilerPath));
if (EnvVars.Compiler != EnvVars.ToolChain)
{
ExternalDependencies.Add(FileItem.GetItemByFileReference(EnvVars.ToolchainCompilerPath));
}
}
public static void AddDefinition(List<string> Arguments, string Definition)
{
// Split the definition into name and value
int ValueIdx = Definition.IndexOf('=');
if (ValueIdx == -1)
{
AddDefinition(Arguments, Definition, null);
}
else
{
AddDefinition(Arguments, Definition.Substring(0, ValueIdx), Definition.Substring(ValueIdx + 1));
}
}
public static void AddDefinition(List<string> Arguments, string Variable, string? Value)
{
// If the value has a space in it and isn't wrapped in quotes, do that now
if (Value != null && !Value.StartsWith("\"") && (Value.Contains(' ') || Value.Contains('$')))
{
Value = "\"" + Value + "\"";
}
if (Value != null)
{
Arguments.Add("/D" + Variable + "=" + Value);
}
else
{
Arguments.Add("/D" + Variable);
}
}
public static new string NormalizeCommandLinePath(FileSystemReference Reference) =>
// Try to use a relative path to shorten command line length and to enable remote distribution where absolute paths are not desired
Reference.IsUnderDirectory(Unreal.EngineDirectory)
? Reference.MakeRelativeTo(Unreal.EngineSourceDirectory).Replace('\\', '/')
: Reference.FullName.Replace('\\', '/');
public static new string NormalizeCommandLinePath(FileItem Item) => NormalizeCommandLinePath(Item.Location);
public static new string NormalizeCommandLinePath(FileSystemReference Reference, CppRootPaths RootPaths) =>
RootPaths.GetVfsOverlayPath(Reference, out string? vfsPath) ? vfsPath : NormalizeCommandLinePath(Reference);
public static new string NormalizeCommandLinePath(FileItem Item, CppRootPaths RootPaths) => NormalizeCommandLinePath(Item.Location, RootPaths);
public static void AddResponseFile(List<string> Arguments, FileItem ResponseFile, CppRootPaths RootPaths)
{
string ResponseFileString = NormalizeCommandLinePath(ResponseFile, RootPaths);
Arguments.Add($"@{Utils.MakePathSafeToUseWithCommandLine(ResponseFileString)}");
}
public static void AddSourceFile(List<string> Arguments, FileItem SourceFile, CppRootPaths RootPaths)
{
string SourceFileString = NormalizeCommandLinePath(SourceFile, RootPaths);
Arguments.Add(Utils.MakePathSafeToUseWithCommandLine(SourceFileString));
}
private static void AddIncludePath(List<string> Arguments, DirectoryReference IncludePath, WindowsCompiler Compiler, bool bSystemInclude, CppRootPaths RootPaths)
{
// Try to use a relative path to shorten command line length.
string IncludePathString = NormalizeCommandLinePath(IncludePath, RootPaths);
if (Compiler.IsClang() && bSystemInclude)
{
// Clang has special treatment for system headers; only system include directories are searched when include directives use angle brackets,
// and warnings are disabled to allow compiler toolchains to be upgraded separately.
Arguments.Add("/imsvc " + Utils.MakePathSafeToUseWithCommandLine(IncludePathString));
}
else if (Compiler.IsMSVC() && Compiler >= WindowsCompiler.VisualStudio2022 && bSystemInclude)
{
if (!Arguments.Contains("/external:W0"))
{
Arguments.Add("/external:W0");
}
// Defines a root directory that contains external headers.
Arguments.Add("/external:I " + Utils.MakePathSafeToUseWithCommandLine(IncludePathString));
}
else
{
Arguments.Add("/I " + Utils.MakePathSafeToUseWithCommandLine(IncludePathString));
}
}
public static void AddIncludePath(List<string> Arguments, DirectoryReference IncludePath, WindowsCompiler Compiler, CppRootPaths RootPaths)
{
AddIncludePath(Arguments, IncludePath, Compiler, false, RootPaths);
}
public static void AddSystemIncludePath(List<string> Arguments, DirectoryReference IncludePath, WindowsCompiler Compiler, CppRootPaths RootPaths)
{
AddIncludePath(Arguments, IncludePath, Compiler, true, RootPaths);
}
public static void AddForceIncludeFile(List<string> Arguments, FileItem ForceIncludeFile, CppRootPaths RootPaths)
{
string ForceIncludeFileString = NormalizeCommandLinePath(ForceIncludeFile, RootPaths);
Arguments.Add($"/FI\"{ForceIncludeFileString}\"");
}
public static void AddCreatePchFile(List<string> Arguments, FileItem PchThroughHeaderFile, FileItem CreatePchFile, CppRootPaths RootPaths)
{
string PchThroughHeaderFilePath = PchThroughHeaderFile.Location.GetFileName();
string CreatePchFilePath = NormalizeCommandLinePath(CreatePchFile, RootPaths);
if (CreatePchFile.Name.EndsWith(".ifc"))
{
Arguments.Add($"/exportHeader");
Arguments.Add($"/translateInclude");
Arguments.Add($"/dxifcInlineFunctions");
Arguments.Add($"/ifcOutput {CreatePchFilePath}");
}
else
{
Arguments.Add($"/Yc\"{PchThroughHeaderFilePath}\"");
Arguments.Add($"/Fp\"{CreatePchFilePath}\"");
}
}
public static void AddUsingPchFile(List<string> Arguments, FileItem PchThroughHeaderFile, FileItem UsingPchFile, CppRootPaths RootPaths)
{
string PchThroughHeaderFilePath = NormalizeCommandLinePath(PchThroughHeaderFile, RootPaths);
string UsingPchFilePath = NormalizeCommandLinePath(UsingPchFile, RootPaths);
if (UsingPchFile.Name.EndsWith(".ifc"))
{
Arguments.Add("/wd4127"); // with header units pragma warning disable is not propagated and this warning is very spammy so explicitly turned off here. Hoping ms will change so warning disable is propgated from HU
Arguments.Add($"/translateInclude");
Arguments.Add($"/headerUnit:quote {PchThroughHeaderFilePath}={UsingPchFilePath}");
}
else
{
Arguments.Add($"/Yu\"{PchThroughHeaderFilePath}\"");
Arguments.Add($"/Fp\"{UsingPchFilePath}\"");
}
}
public static void AddPreprocessedFile(List<string> Arguments, FileItem PreprocessedFile, CppRootPaths RootPaths)
{
string PreprocessedFileString = NormalizeCommandLinePath(PreprocessedFile, RootPaths);
Arguments.Add("/P"); // Preprocess
Arguments.Add("/C"); // Preserve comments when preprocessing
Arguments.Add($"/Fi\"{PreprocessedFileString}\""); // Preprocess to a file
}
public static void AddObjectFile(List<string> Arguments, FileItem ObjectFile, CppRootPaths RootPaths)
{
string ObjectFileString = NormalizeCommandLinePath(ObjectFile, RootPaths);
Arguments.Add($"/Fo\"{ObjectFileString}\"");
}
public static void AddAssemblyFile(List<string> Arguments, FileItem AssemblyFile, CppRootPaths RootPaths)
{
string AssemblyFileString = NormalizeCommandLinePath(AssemblyFile, RootPaths);
Arguments.Add("/FAs");// Write out an assembly file (.asm) with the c++ code embedded in it via comments
Arguments.Add($"/Fa\"{AssemblyFileString}\""); // Set the output patj for the asm file
}
public static void AddAnalyzeLogFile(List<string> Arguments, FileItem AnalyzeLogFile, CppRootPaths RootPaths)
{
string AnalyzeLogFileString = NormalizeCommandLinePath(AnalyzeLogFile, RootPaths);
Arguments.Add("/analyze:log " + Utils.MakePathSafeToUseWithCommandLine(AnalyzeLogFileString));
}
public static void AddExperimentalLogFile(List<string> Arguments, FileItem ExperimentalLogFile, CppRootPaths RootPaths)
{
string ExperimentalLogFileString = NormalizeCommandLinePath(ExperimentalLogFile, RootPaths);
Arguments.Add("/experimental:log " + Utils.MakePathSafeToUseWithCommandLine(ExperimentalLogFileString));
}
public static void AddSourceDependenciesFile(List<string> Arguments, FileItem SourceDependenciesFile, CppRootPaths RootPaths)
{
string SourceDependenciesFileString = NormalizeCommandLinePath(SourceDependenciesFile, RootPaths);
Arguments.Add("/sourceDependencies " + Utils.MakePathSafeToUseWithCommandLine(SourceDependenciesFileString));
}
public static void AddSourceDependsFile(List<string> Arguments, FileItem SourceDependsFile, CppRootPaths RootPaths)
{
string SourceDependsFileString = NormalizeCommandLinePath(SourceDependsFile, RootPaths);
Arguments.Add($"/clang:-MD /clang:-MF\"{SourceDependsFileString}\"");
}
protected static FileReference GetClangProfDataFilename(CppCompileEnvironment CompileEnvironment)
{
string ProfDataFilename = Path.Combine(CompileEnvironment.PGODirectory!, CompileEnvironment.PGOFilenamePrefix!);
if (File.Exists(ProfDataFilename))
{
return new FileReference(ProfDataFilename);
}
// If an exact match doesn't exist, fall back to an alternative. This is supported for building Test with Shipping PGO data for example
string[] ProfDataFiles = Directory.GetFiles(CompileEnvironment.PGODirectory!, "*.profdata");
if (ProfDataFiles.Length > 1)
{
throw new BuildException($"More than one .profdata file found in \"{CompileEnvironment.PGODirectory}\" and \"{ProfDataFilename}\" not found ");
}
if (ProfDataFiles.Length == 0)
{
throw new BuildException($"No .profdata files found in \"{CompileEnvironment.PGODirectory}\".");
}
return new FileReference(ProfDataFiles.First());
}
protected virtual void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Workaround for MSVC 14.31 compiler crash
if (EnvVars.Compiler.IsMSVC() && EnvVars.ToolChainVersion < new VersionNumber(14, 43) && EnvVars.ToolChainVersion >= new VersionNumber(14, 31))
{
Arguments.Add("/d2ssa-cfg-question-");
}
if (CompileEnvironment.bVcRemoveUnreferencedComdat)
{
// Suppress generation of object code for unreferenced inline functions. Enabling this option is more standards compliant, and causes a big reduction
// in object file sizes (and link times) due to the amount of stuff we inline.
Arguments.Add("/Zc:inline");
}
else
{
Arguments.Add("/Zc:inline-");
}
// @todo clang: Clang on Windows doesn't respect "#pragma warning (error: ####)", and we're not passing "/WX", so warnings are not
// treated as errors when compiling on Windows using Clang right now.
// NOTE re: clang: the arguments for clang-cl can be found at http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Driver/CLCompatOptions.td?view=markup
// This will show the cl.exe options that map to clang.exe ones, which ones are ignored and which ones are unsupported.
if (Target.WindowsPlatform.Compiler.IsClang())
{
// Sync the compatibility version with the MSVC toolchain version (14.xx which maps to advertised
// compiler version of 19.xx).
Arguments.Add($"-fms-compatibility-version=19.{EnvVars.ToolChainVersion.GetComponent(1)}");
// We have 'this' vs nullptr comparisons that get optimized away for newer versions of Clang, which is undesirable until we refactor these checks.
Arguments.Add("-fno-delete-null-pointer-checks");
// The temp files are there to prevent corrupt output files but with uba we don't write output files until process has finished successfully so creating temp files just create wasteful IOPS
Arguments.Add("-fno-temp-file");
// Prevents clang-cl from reading a few files from installed visual studio folders (that we are not even using)
Arguments.Add("-vctoolsdir undefined");
if (Target.bWithLiveCoding)
{
Arguments.Add("-Z7");
Arguments.Add("-fms-hotpatch");
Arguments.Add("-Gy");
Arguments.Add("-Xclang -mno-constructor-aliases");
}
if (Target.StaticAnalyzer == StaticAnalyzer.Default && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create && !CompileEnvironment.bDisableStaticAnalysis)
{
Arguments.Add("-Wno-unused-command-line-argument");
// Enable the static analyzer with default checks.
Arguments.Add("--analyze");
// Write out a pretty web page with navigation to understand how the analysis was derived if HTML is enabled.
Arguments.Add($"-Xclang -analyzer-output={Target.StaticAnalyzerOutputType.ToString().ToLowerInvariant()}");
// Needed for some of the C++ checkers.
Arguments.Add("-Xclang -analyzer-config -Xclang aggressive-binary-operation-simplification=true");
// If writing to HTML, use the source filename as a basis for the report filename.
Arguments.Add("-Xclang -analyzer-config -Xclang stable-report-filename=true");
Arguments.Add("-Xclang -analyzer-config -Xclang report-in-main-source-file=true");
Arguments.Add("-Xclang -analyzer-config -Xclang path-diagnostics-alternate=true");
// Run shallow analyze if requested.
if (Target.StaticAnalyzerMode == StaticAnalyzerMode.Shallow)
{
Arguments.Add("-Xclang -analyzer-config -Xclang mode=shallow");
}
if (CompileEnvironment.StaticAnalyzerCheckers.Count > 0)
{
// Disable all default checks
Arguments.Add("--analyzer-no-default-checks");
// Only enable specific checks.
foreach (string Checker in CompileEnvironment.StaticAnalyzerCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, EnvVars.CompilerVersion)))
{
Arguments.Add($"-Xclang -analyzer-checker -Xclang {Checker}");
}
}
else
{
// Disable default checks.
foreach (string Checker in CompileEnvironment.StaticAnalyzerDisabledCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, EnvVars.CompilerVersion)))
{
Arguments.Add($"-Xclang -analyzer-disable-checker -Xclang {Checker}");
}
// Enable additional non-default checks.
foreach (string Checker in CompileEnvironment.StaticAnalyzerAdditionalCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, EnvVars.CompilerVersion)))
{
Arguments.Add($"-Xclang -analyzer-checker -Xclang {Checker}");
}
}
}
else if (Target.StaticAnalyzer == StaticAnalyzer.Default && CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
AddDefinition(Arguments, "__clang_analyzer__");
}
if (CompileEnvironment.RootPaths.bUseVfs)
{
Arguments.Add("-fcoverage-compilation-dir=.");
Arguments.Add("-fdebug-compilation-dir=.");
Arguments.Add("-vctoolsdir=.");
Arguments.Add("-winsdkdir=.");
Arguments.Add("-winsysroot=.");
Arguments.Add("-Xclang -gno-codeview-command-line");
Arguments.Add("-fintegrated-cc1");
}
// Resource dir is for built-in includes. We need it for vfs but also for uba include crawler to find includes to transfer
VersionNumber ClangVersion = Target.WindowsPlatform.Compiler == WindowsCompiler.Intel ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
DirectoryReference ResourceDir = DirectoryReference.Combine(EnvVars.CompilerDir!, "lib", "clang", ClangVersion.GetComponent(0).ToString());
Arguments.Add($"-resource-dir=\"{NormalizeCommandLinePath(ResourceDir, CompileEnvironment.RootPaths)}\"");
}
else if (Target.StaticAnalyzer == StaticAnalyzer.Default && !CompileEnvironment.bDisableStaticAnalysis)
{
Arguments.Add("/analyze");
if (CompileEnvironment.bStaticAnalyzerExtensions)
{
FileReference EspxEngine = FileReference.Combine(EnvVars.CompilerPath.Directory, "EspXEngine.dll");
Arguments.Add($"/analyze:plugin\"{NormalizeCommandLinePath(EspxEngine)}\"");
}
foreach (FileReference Ruleset in CompileEnvironment.StaticAnalyzerRulesets)
{
Arguments.Add($"/analyze:ruleset\"{NormalizeCommandLinePath(Ruleset)}\"");
}
if (Target.WindowsPlatform.Compiler >= WindowsCompiler.VisualStudio2022)
{
// Ignore warnings in external headers
Arguments.Add("/analyze:external-");
}
// Report functions that use a LOT of stack space. You can lower this value if you
// want more aggressive checking for functions that use a lot of stack memory.
Arguments.Add("/analyze:stacksize" + CompileEnvironment.AnalyzeStackSizeWarning);
// Don't bother generating code, only analyze code (may report fewer warnings though.)
//Arguments.Add("/analyze:only");
// Re-evalulate new analysis warnings at a later time
Arguments.Add("/wd6031"); // return value ignored: called-function could return unexpected value
if (CompileEnvironment.SystemIncludePaths.Concat(CompileEnvironment.SharedSystemIncludePaths).Any(x => x.FullName.Contains("GoogleTest", StringComparison.OrdinalIgnoreCase)))
{
Arguments.Add("/wd6326"); // Potential comparison of a constant with another constant
}
}
// Prevents the compiler from displaying its logo for each invocation.
Arguments.Add("/nologo");
// Enable intrinsic functions.
Arguments.Add("/Oi");
// Trace includes
if (Target.bShowIncludes)
{
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("/clang:--trace-includes");
}
else if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add("/showIncludes");
}
}
// Print absolute paths in diagnostics
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("-fdiagnostics-absolute-paths");
}
else if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add("/FC");
// Add column (in addition to line) to error messages
Arguments.Add("/diagnostics:caret");
}
if (Target.WindowsPlatform.Compiler.IsClang() && Target.Platform.IsInGroup(UnrealPlatformGroup.Windows))
{
// Tell the Clang compiler to generate 64-bit code
if (CompileEnvironment.Architecture.bIsX64)
{
Arguments.Add("--target=x86_64-pc-windows-msvc");
// UE5 minspec is 4.2
Arguments.Add("-msse4.2");
// Use tpause on supported processors.
Arguments.Add("-mwaitpkg");
}
else if (CompileEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add("--target=arm64-pc-windows-msvc");
}
else if (CompileEnvironment.Architecture == UnrealArch.Arm64ec)
{
Arguments.Add("--target=arm64ec-pc-windows-msvc");
}
}
// Compile into an .obj file, and skip linking.
Arguments.Add("/c");
// Put symbols into different sections so the linker can remove them.
if (Target.WindowsPlatform.bOptimizeGlobalData)
{
Arguments.Add("/Gw");
}
// Reduce optimizations for huge functions, may improve compile time a the expense of speed for functions over the threshold
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bReducedOptimizeHugeFunctions)
{
Arguments.Add("/d2ReducedOptimizeHugeFunctions");
Arguments.Add($"/d2ReducedOptimizeThreshold:{Target.WindowsPlatform.ReducedOptimizeHugeFunctionsThreshold}");
}
if (IsDynamicDebuggingEnabled)
{
Arguments.Add("/dynamicdeopt");
}
// Separate functions for linker.
Arguments.Add("/Gy");
// Microsoft recommends not passing /Zm except in very limited circumstances, please see:
// https://learn.microsoft.com/en-us/cpp/build/reference/zm-specify-precompiled-header-memory-allocation-limit
if (Target.WindowsPlatform.PCHMemoryAllocationFactor > 0)
{
Arguments.Add($"/Zm{Target.WindowsPlatform.PCHMemoryAllocationFactor}");
}
// Fix Incredibuild errors with helpers using heterogeneous character sets
Arguments.Add("/utf-8");
// Disable "The file contains a character that cannot be represented in the current code page" warning for non-US windows.
Arguments.Add("/wd4819");
// Disable Microsoft extensions on VS2017+ for improved standards compliance.
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
if (Target.WindowsPlatform.bStrictConformanceMode)
{
// This define is needed to ensure that MSVC static analysis mode doesn't declare attributes that are incompatible with strict conformance mode
AddDefinition(Arguments, "SAL_NO_ATTRIBUTE_DECLARATIONS=1");
Arguments.Add("/permissive-");
Arguments.Add("/Zc:strictStrings-"); // Have to disable strict const char* semantics due to Windows headers not being compliant.
}
else
{
Arguments.Add("/Zc:hiddenFriend");
}
if (Target.WindowsPlatform.bUpdatedCPPMacro)
{
Arguments.Add("/Zc:__cplusplus");
}
if (Target.WindowsPlatform.bStrictInlineConformance)
{
Arguments.Add("/Zc:inline");
}
if (Target.WindowsPlatform.bStrictPreprocessorConformance)
{
Arguments.Add("/Zc:preprocessor");
}
if (Target.WindowsPlatform.bStrictEnumTypesConformance)
{
Arguments.Add("/Zc:enumTypes");
}
if (Target.WindowsPlatform.bStrictODRViolationConformance && Target.WindowsPlatform.bOptimizeGlobalData)
{
Arguments.Add("/Zc:checkGwOdr");
}
}
// @todo HoloLens: UE is non-compliant when it comes to use of %s and %S
// Previously %s meant "the current character set" and %S meant "the other one".
// Now %s means multibyte and %S means wide. %Ts means "natural width".
// Reverting this behaviour until the UE source catches up.
AddDefinition(Arguments, "_CRT_STDIO_LEGACY_WIDE_SPECIFIERS=1");
// @todo HoloLens: Silence the hash_map deprecation errors for now. This should be replaced with unordered_map for the real fix.
AddDefinition(Arguments, "_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS=1");
// Ignore secure CRT warnings on Clang
if (Target.WindowsPlatform.Compiler.IsClang())
{
AddDefinition(Arguments, "_CRT_SECURE_NO_WARNINGS");
}
// If compiling as a DLL, set the relevant defines
if (CompileEnvironment.bIsBuildingDLL)
{
AddDefinition(Arguments, "_WINDLL");
}
// Maintain the old std::aligned_storage behavior from VS from v15.8 onwards, in case of prebuilt third party libraries are reliant on it
AddDefinition(Arguments, "_DISABLE_EXTENDED_ALIGNED_STORAGE");
// Do not allow inline method expansion if E&C support is enabled or inline expansion has been disabled,
// or if we are compiling in a debug build with `clang-cl`, since this will interfere with debugging capabilities.
if (!CompileEnvironment.bSupportEditAndContinue && CompileEnvironment.bUseInlining
&& !(Target.WindowsPlatform.Compiler.IsClang() && CompileEnvironment.Configuration == CppConfiguration.Debug))
{
Arguments.Add($"/Ob{Math.Clamp(Target.WindowsPlatform.InlineFunctionExpansionLevel, 1, 3)}");
}
else
{
// Specifically disable inline expansion to override /O1,/O2/ or /Ox if set
Arguments.Add("/Ob0");
}
// Deterministic compile support
if (CompileEnvironment.bDeterministic)
{
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add("/experimental:deterministic");
// warning C5049: Embedding a full path may result in machine-dependent output
Arguments.Add("/wd5049");
}
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("/Brepro");
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bVCFastFail)
{
Arguments.Add("/fastfail");
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bVCExtendedWarningInfo)
{
Arguments.Add("/d2ExtendedWarningInfo");
}
if (Target.WindowsPlatform.bEnableInstrumentation)
{
AddDefinition(Arguments, "USING_INSTRUMENTATION=1");
}
// Address sanitizer
if (Target.WindowsPlatform.bEnableAddressSanitizer)
{
// Enable address sanitizer. This also requires companion libraries at link time.
// Works for clang too.
Arguments.Add("/fsanitize=address");
if (Target.WindowsPlatform.Compiler.IsClang())
{
// Don't check global variables when address sanitizer is enabled. This can lead to false / positive with ASan mixing types of different size in its shadow space
// (for instance, null character being shared for char and wchar_t but having different sizes (1 for char and 2 for wchar_t))
Arguments.Add("-mllvm -asan-globals=0");
}
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
// MSVC has no support for __has_feature(address_sanitizer)
AddDefinition(Arguments, "USING_ADDRESS_SANITISER=1");
}
// Disabling a couple annotations to workaround an issue related to building third party libraries with different options than the main binary.
// This fixes an error that looks like this: error LNK2038: mismatch detected for 'annotate_string': value '0' doesn't match value '1' in Module.Core.XX_of_YY.cpp.obj
AddDefinition(Arguments, "_DISABLE_STRING_ANNOTATION=1");
AddDefinition(Arguments, "_DISABLE_VECTOR_ANNOTATION=1");
// Currently the ASan headers are not default around. They can be found at this location so lets use this until this is resolved in the toolchain
// Jira with some more info and the MSVC bug at UE-144727
AddSystemIncludePath(Arguments, DirectoryReference.Combine(EnvVars.CompilerDir, "crt", "src"), Target.WindowsPlatform.Compiler, CompileEnvironment.RootPaths);
}
if (Target.WindowsPlatform.bEnableUndefinedBehaviorSanitizer)
{
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("-fsanitize=undefined");
}
else
{
Arguments.Add("/fsanitize=undefined");
}
}
if (Target.WindowsPlatform.bEnableLibFuzzer)
{
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("-fsanitize=fuzzer");
}
else
{
Arguments.Add("/fsanitize=fuzzer");
}
}
//
// Debug
//
if (CompileEnvironment.Configuration == CppConfiguration.Debug)
{
// Disable compiler optimization.
Arguments.Add("/Od");
// `/Os` causes `clang-cl` to effectively compile with `-Os` which enables optimizations,
// rendering passing `/Od` to it useless.
if (!Target.WindowsPlatform.Compiler.IsClang())
{
// Favor code size (especially useful for embedded platforms).
Arguments.Add("/Os");
}
// Runtime checks and ASan are incompatible.
if (!Target.WindowsPlatform.bEnableAddressSanitizer)
{
Arguments.Add("/RTCs");
}
}
//
// Development
//
else
{
if (!CompileEnvironment.bOptimizeCode)
{
// Disable compiler optimization.
Arguments.Add("/Od");
}
else
{
if (Target.WindowsPlatform.Compiler.IsClang())
{
switch (CompileEnvironment.OptimizationLevel)
{
case OptimizationMode.Size:
{
// We use Clang's -Oz here because it produces a smaller binary than /O1
Arguments.Add("-Xclang -Oz");
}
break;
case OptimizationMode.SizeAndSpeed:
{
// Typically the Cl compatible /Ox /Os args result in a smaller binary than just -XClang -Os (by 30MB or so in a Dev build)
// However, when using PGO, -XClang -Os is the same size but marginally faster
if ( CompileEnvironment.bPGOProfile || CompileEnvironment.bPGOOptimize )
{
Arguments.Add("-Xclang -Os");
}
else
{
Arguments.Add("/Ox");
Arguments.Add("/Os");
}
}
break;
case OptimizationMode.Speed:
{
if ( CompileEnvironment.bPGOProfile || CompileEnvironment.bPGOOptimize )
{
// This is optimal both for speed and size when PGO is enabled
Arguments.Add("-Xclang -Os");
}
else
{
// Maximum optimizations. We just use the MSVC flags and let the Clang-Cl driver translate
Arguments.Add("/Ox");
Arguments.Add("/Ot");
}
}
break;
}
}
else
{
// Maximum optimizations.
Arguments.Add("/Ox");
if (CompileEnvironment.OptimizationLevel != OptimizationMode.Speed)
{
Arguments.Add("/Os");
}
else
{
// Favor code speed.
Arguments.Add("/Ot");
}
}
// Coalesce duplicate strings
Arguments.Add("/GF");
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (CompileEnvironment.bOmitFramePointers == false)
{
Arguments.Add("/Oy-");
}
}
}
// Volatile Metadata is enabled by default and improves x64 emulation on arm64, but may come at a small perfomance cost
if (Target.WindowsPlatform.Compiler.IsMSVC() && CompileEnvironment.Architecture == UnrealArch.X64 && Target.WindowsPlatform.bDisableVolatileMetadata)
{
Arguments.Add("/volatileMetadata-");
}
//
// LTCG and PGO
//
bool bEnableLTCG =
CompileEnvironment.bPGOProfile ||
CompileEnvironment.bPGOOptimize ||
CompileEnvironment.bAllowLTCG;
if (bEnableLTCG && !Target.WindowsPlatform.Compiler.IsClang())
{
// Enable link-time code generation.
Arguments.Add("/GL");
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
if (CompileEnvironment.bAllowLTCG && Target.WindowsPlatform.bAllowClangLinker)
{
// Enable link-time code generation. Requires Clang linker.
Arguments.Add("-flto=thin");
}
if (CompileEnvironment.bPGOProfile)
{
// Generate instrumented code.
if (Target.WindowsPlatform.bSampleBasedPGO)
{
if (Target.WindowsPlatform.Compiler.IsIntel())
{
Arguments.Add("-fprofile-sample-generate");
Arguments.Add($"-fprofile-dwo-dir=\"{CompileEnvironment.PGODirectory!}\"");
Arguments.Add("-gsplit-dwarf");
}
else
{
Arguments.Add("-gdwarf");
Arguments.Add("-gsplit-dwarf");
Arguments.Add("-gline-tables-only");
Arguments.Add("/clang:-fdebug-info-for-profiling");
Arguments.Add("/clang:-funique-internal-linkage-names");
}
}
else
{
Arguments.Add("-fprofile-generate");
}
}
else if (CompileEnvironment.bPGOOptimize)
{
// Use a merged profdata file.
FileReference ProfDataFilename = GetClangProfDataFilename(CompileEnvironment);
Log.TraceInformationOnce($"Using PGO profile data \"{ProfDataFilename}\"");
if (Target.WindowsPlatform.bSampleBasedPGO)
{
if (Target.WindowsPlatform.Compiler.IsIntel())
{
Arguments.Add($"-fprofile-sample-use=\"{ProfDataFilename}\"");
}
else
{
Arguments.Add($"/clang:-fprofile-sample-use=\"{ProfDataFilename}\"");
}
}
else
{
Arguments.Add($"-fprofile-use=\"{ProfDataFilename}\"");
}
}
}
//
// PC
//
if (CompileEnvironment.Architecture == UnrealArch.X64 && CompileEnvironment.MinCpuArchX64 != MinimumCpuArchitectureX64.None)
{
// Define /arch:AVX[2,512] for the current compilation unit. Machines without AVX support will crash on any SSE/AVX instructions if they run this compilation unit.
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add($"/arch:{CompileEnvironment.MinCpuArchX64}");
}
else if (Target.WindowsPlatform.Compiler.IsClang())
{
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX)
{
// Apparently MSVC enables (a subset?) of BMI (bit manipulation instructions) when /arch:AVX is set. Some code relies on this, so mirror it by enabling BMI1
Arguments.Add("-mavx");
Arguments.Add("-mbmi");
}
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX2)
{
Arguments.Add("-mavx2");
}
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX512)
{
// Match MSVC which says (https://learn.microsoft.com/en-us/cpp/build/reference/arch-x64?view=msvc-170):
// > The __AVX512F__, __AVX512CD__, __AVX512BW__, __AVX512DQ__ and __AVX512VL__ preprocessor symbols are defined when the /arch:AVX512 compiler option is specified
Arguments.Add("-mavx512f");
Arguments.Add("-mavx512cd");
Arguments.Add("-mavx512bw");
Arguments.Add("-mavx512dq");
Arguments.Add("-mavx512vl");
}
}
// AVX available implies sse4 and sse2 available.
// Inform Unreal code that we have sse2, sse4, and AVX, both available to compile and available to run
// By setting the ALWAYS_HAS defines, we we direct Unreal code to skip cpuid checks to verify that the running hardware supports sse/avx.
AddDefinition(Arguments, "PLATFORM_ENABLE_VECTORINTRINSICS=1");
AddDefinition(Arguments, "PLATFORM_MAYBE_HAS_AVX=1");
AddDefinition(Arguments, "PLATFORM_ALWAYS_HAS_AVX=1");
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX2)
{
AddDefinition(Arguments, "PLATFORM_ALWAYS_HAS_AVX_2=1");
}
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX512)
{
AddDefinition(Arguments, "PLATFORM_ALWAYS_HAS_AVX_512=1");
}
}
if (CompileEnvironment.Architecture == UnrealArch.X64 && CompileEnvironment.MinCpuArchX64 == MinimumCpuArchitectureX64.None && Target.WindowsPlatform.Compiler.IsIntel())
{
// Intel oneAPI has /arch switch for sse4.2. Use it when no minimum is set.
Arguments.Add("/arch:sse4.2");
}
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Add("/errorReport:prompt");
// Enable C++ exceptions when building with the editor or when building UHT.
if (CompileEnvironment.bEnableExceptions)
{
// Enable C++ exception handling, but not C exceptions.
Arguments.Add("/EHsc");
Arguments.Add("/DPLATFORM_EXCEPTIONS_DISABLED=0");
}
else
{
// This is required to disable exception handling in VC platform headers.
AddDefinition(Arguments, "_HAS_EXCEPTIONS=0");
Arguments.Add("/DPLATFORM_EXCEPTIONS_DISABLED=1");
}
// If enabled, create debug information.
if (CompileEnvironment.bCreateDebugInfo)
{
/*
Disabled until we can find a better means to limit the cost of /JMC as it can add considerable runtime overhead
// Enable Just-My-Code in Debug or Development, unless on Clang
if ((CompileEnvironment.Configuration == CppConfiguration.Debug || CompileEnvironment.Configuration == CppConfiguration.Development) && !Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("/JMC");
}
*/
// Store debug info in .pdb files.
// @todo clang: PDB files are emited from Clang but do not fully work with Visual Studio yet (breakpoints won't hit due to "symbol read error")
// @todo clang (update): as of clang 3.9 breakpoints work with PDBs, and the callstack is correct, so could be used for crash dumps. However debugging is still impossible due to the large amount of unreadable variables and unpredictable breakpoint stepping behaviour
if (CompileEnvironment.bUsePDBFiles || CompileEnvironment.bSupportEditAndContinue)
{
// Create debug info suitable for E&C if wanted.
if (CompileEnvironment.bSupportEditAndContinue)
{
Arguments.Add("/ZI");
}
// Regular PDB debug information.
else
{
Arguments.Add("/Zi");
}
// We need to add this so VS won't lock the PDB file and prevent synchronous updates. This forces serialization through MSPDBSRV.exe.
// See http://msdn.microsoft.com/en-us/library/dn502518.aspx for deeper discussion of /FS switch.
if (CompileEnvironment.bUseIncrementalLinking)
{
Arguments.Add("/FS");
}
}
// Store C7-format debug info in the .obj files, which is faster.
else
{
Arguments.Add("/Z7");
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
VersionNumber ClangVersion = Target.WindowsPlatform.Compiler == WindowsCompiler.Intel ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
// https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline-tables-only
if (CompileEnvironment.bDebugLineTablesOnly)
{
Arguments.Add("-gline-tables-only");
}
// https://clang.llvm.org/docs/UsersManual.html#cmdoption-fstandalone-debug
if (Target.WindowsPlatform.bClangStandaloneDebug)
{
Arguments.Add("-fstandalone-debug");
}
// https://clang.llvm.org/docs/UsersManual.html#cmdoption-feliminate-unused-debug-types
// Intel ICX 2024.2 does not have this option, but reports Clang 19, so turn it off here
if (ClangVersion >= new VersionNumber(19) && !Target.WindowsPlatform.Compiler.IsIntel())
{
Arguments.Add("-fno-eliminate-unused-debug-types");
}
}
}
// Specify the appropriate runtime library based on the platform and config.
if (CompileEnvironment.bUseStaticCRT)
{
if (CompileEnvironment.bUseDebugCRT)
{
Arguments.Add("/MTd");
}
else
{
Arguments.Add("/MT");
}
}
else
{
if (CompileEnvironment.bUseDebugCRT)
{
Arguments.Add("/MDd");
}
else
{
Arguments.Add("/MD");
}
}
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
// Allow large object files to avoid hitting the 2^16 section limit when running with -StressTestUnity.
// Note: not needed for clang, it implicitly upgrades COFF files to bigobj format when necessary.
Arguments.Add("/bigobj");
}
FPSemanticsMode FPSemantics = CompileEnvironment.FPSemantics;
if (Target.WindowsPlatform.Compiler.IsClang())
{
// FMath::Sqrt calls get inlined and when reciprical is taken, turned into an rsqrtss instruction,
// which is *too* imprecise for, e.g., TestVectorNormalize_Sqrt in UnrealMathTest.cpp
// TODO: Observed in clang 7.0, presumably the same in Intel C++ Compiler?
FPSemantics = FPSemanticsMode.Precise;
}
switch (FPSemantics)
{
case FPSemanticsMode.Default: // Default is imprecise FP semantics.
case FPSemanticsMode.Imprecise: Arguments.Add("/fp:fast"); break;
case FPSemanticsMode.Precise: Arguments.Add("/fp:precise"); break;
default:
throw new BuildException($"Unsupported FP semantics: {FPSemantics}");
}
if (Target.WindowsPlatform.Compiler.IsIntel())
{
Arguments.Add("/Qvec-peel-loops");
Arguments.Add("/Qvec-remainder-loops");
Arguments.Add("/Qvec-with-mask");
Arguments.Add("/Qopt-dynamic-align");
Arguments.Add("/Qunroll");
Arguments.Add("/Qopt-streaming-stores:auto");
Arguments.Add("/Qopt-jump-tables");
Arguments.Add("/Qbranches-within-32B-boundaries");
VersionNumber ClangVersion = MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath);
// Intel ICX 2025.1 reports ldexpf as part of math library, but comes up as unresolved
if (ClangVersion >= new VersionNumber(20))
{
Arguments.Add("-fno-builtin-ldexpf");
}
}
// Intel oneAPI compiler does not support /Zo
if (CompileEnvironment.bOptimizeCode && Target.WindowsPlatform.Compiler != WindowsCompiler.Intel)
{
// Allow optimized code to be debugged more easily. This makes PDBs a bit larger, but doesn't noticeably affect
// compile times. The executable code is not affected at all by this switch, only the debugging information.
Arguments.Add("/Zo");
}
if (Target.WindowsPlatform.StructMemberAlignment != null)
{
Arguments.Add($"/Zp{Target.WindowsPlatform.StructMemberAlignment}");
}
if (CompileEnvironment.DefaultWarningLevel == WarningLevel.Error)
{
Arguments.Add("/WX");
}
if (CompileEnvironment.bUseHeaderUnitsForPch)
{
Arguments.Add("/wd4324"); // 'struct_name' : structure was padded due to __declspec(align())
Arguments.Add("/wd4201"); // nonstandard extension used: nameless struct/union
Arguments.Add("/wd4275"); // non - DLL-interface class 'class_1' used as base for DLL-interface class 'class_2'
Arguments.Add("/wd4251"); // 'type' : class 'type1' needs to have dll-interface to be used by clients of class 'type2'
Arguments.Add("/wd4702"); // unreachable code
Arguments.Add("/wd4180"); // qualifier applied to function type has no meaning; ignored
Arguments.Add("/wd4996"); // deprecation
Arguments.Add("/wd4200"); // nonstandard extension used: zero - sized array in struct/union
Arguments.Add("/wd4714"); // marked as __forceinline not inlined
Arguments.Add("/wd5321"); // nonstandard extension used: encoding '\xE5' as a multi-byte utf-8 character
}
// Downgrade C4702: unreachable code to a warning when running LTCG or PGO
if (CompileEnvironment.bPGOOptimize || CompileEnvironment.bPGOProfile || CompileEnvironment.bAllowLTCG)
{
Arguments.Add("/wd4702 /w44702");
}
AppendCLArguments_CompileWarnings(CompileEnvironment, Arguments);
if (CompileEnvironment.Architecture == UnrealArch.Arm64ec)
{
Arguments.Add("/arm64EC");
// The latest vc toolchain requires that these be manually set for arm64ec.
AddDefinition(Arguments, "_ARM64EC_");
AddDefinition(Arguments, "_ARM64EC_WORKAROUND_");
AddDefinition(Arguments, "ARM64EC");
AddDefinition(Arguments, "AMD64");
AddDefinition(Arguments, "_AMD64_");
AddDefinition(Arguments, "_WINDOWS");
AddDefinition(Arguments, "WIN32");
}
}
protected virtual void AppendCLArguments_H(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
AppendCLArguments_CPP(CompileEnvironment, Arguments);
if (Target.WindowsPlatform.Compiler.IsClang())
{
ClangWarnings.GetHeaderDisabledWarnings(Arguments);
}
}
protected virtual void AppendCLArguments_CPP(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Explicitly compile the file as C++.
Arguments.Add("/TP");
if (Target.WindowsPlatform.Compiler.IsClang())
{
string FileSpecifier = "c++";
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Tell Clang to generate a PCH header
FileSpecifier += "-header";
}
Arguments.Add($"-Xclang -x -Xclang \"{FileSpecifier}\"");
}
if (!CompileEnvironment.bEnableBufferSecurityChecks)
{
// This will disable buffer security checks (which are enabled by default) that the MS compiler adds around arrays on the stack,
// Which can add some performance overhead, especially in performance intensive code
// Only disable this if you know what you are doing, because it will be disabled for the entire module!
Arguments.Add("/GS-");
}
// Configure RTTI
if (CompileEnvironment.bUseRTTI)
{
// Enable C++ RTTI.
Arguments.Add("/GR");
}
else
{
// Disable C++ RTTI.
Arguments.Add("/GR-");
}
switch (CompileEnvironment.CppStandard)
{
case CppStandardVersion.Cpp20:
Arguments.Add("/std:c++20");
break;
case CppStandardVersion.Cpp23:
if (EnvVars.Compiler.IsMSVC() && EnvVars.CompilerVersion >= new VersionNumber(14, 43, 34808))
{
Arguments.Add("/std:c++23preview");
}
else
{
Arguments.Add("/std:c++latest");
}
break;
case CppStandardVersion.Latest:
Arguments.Add("/std:c++latest");
break;
default:
throw new BuildException($"Unsupported C++ standard type set: {CompileEnvironment.CppStandard}");
}
if (CompileEnvironment.CppStandard >= CppStandardVersion.Cpp20)
{
if (Target.WindowsPlatform.Compiler.IsMSVC() && !CompileEnvironment.AdditionalArguments.Contains("/Zc:preprocessor-", StringComparison.Ordinal))
{
Arguments.Add("/Zc:preprocessor");
}
// warning C5054: operator ___: deprecated between enumerations of different types
// re: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1120r0.html
// It seems unclear whether the deprecation will be enacted in C++23 or not
// e.g. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2139r2.html
// Until the path forward is clearer, it seems reasonable to leave things as they are.
Arguments.Add("/wd5054");
}
if (CompileEnvironment.bEnableCoroutines)
{
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add("/await:strict");
}
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
// Enable codeview ghash for faster lld links on Clang and Intel
if (Target.WindowsPlatform.bAllowClangLinker)
{
Arguments.Add("-Xclang -gcodeview-ghash");
}
if (CompileEnvironment.bEnableAutoRTFMInstrumentation)
{
Arguments.Add("-fautortfm");
if (CompileEnvironment.bEnableAutoRTFMVerification)
{
Arguments.Add("-fautortfm-verify");
}
}
if (CompileEnvironment.bAutoRTFMVerify)
{
Arguments.Add("-Xclang -mllvm -Xclang -autortfm-verify");
}
if (CompileEnvironment.bAutoRTFMClosedStaticLinkage)
{
Arguments.Add("-Xclang -mllvm -Xclang -autortfm-closed-static-linkage");
}
}
}
void AppendCLArguments_C(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Explicitly compile the file as C.
Arguments.Add("/TC");
// Level 0 warnings. Needed for external C projects that produce warnings at higher warning levels.
// Foricbly remove "/W4" in order to avoid excessive warning that we know will happen.
// > Command line warning D9025 : overriding '/W4' with '/W0'
if (Arguments.Contains("/W4"))
{
Arguments.Remove("/W4");
}
Arguments.Add("/W0");
// Select C Standard version available
// Select C Standard version available
switch (CompileEnvironment.CStandard)
{
case CStandardVersion.None:
case CStandardVersion.C89:
case CStandardVersion.C99:
break;
case CStandardVersion.C11:
Arguments.Add("/std:c11");
break;
case CStandardVersion.C17:
case CStandardVersion.Latest:
Arguments.Add("/std:c17");
break;
default:
throw new BuildException($"Unsupported C standard type set: {CompileEnvironment.CStandard}");
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
if (CompileEnvironment.bEnableAutoRTFMInstrumentation)
{
Arguments.Add("-fautortfm");
if (CompileEnvironment.bEnableAutoRTFMVerification)
{
Arguments.Add("-fautortfm-verify");
}
}
if (CompileEnvironment.bAutoRTFMVerify)
{
Arguments.Add("-Xclang -mllvm -Xclang -autortfm-verify");
}
if (CompileEnvironment.bAutoRTFMClosedStaticLinkage)
{
Arguments.Add("-Xclang -mllvm -Xclang -autortfm-closed-static-linkage");
}
}
}
void AppendCLArguments_CompileWarnings(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Set warning level.
// Restrictive during regular compilation.
Arguments.Add("/W4");
// Treat warnings as errors
if (CompileEnvironment.bWarningsAsErrors)
{
Arguments.Add("/WX");
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
// Disable specific warnings that cause problems with Clang
// NOTE: These must appear after we set the MSVC warning level
// @todo clang: Ideally we want as few warnings disabled as possible
// Treat all warnings as errors by default
Arguments.Add("-Werror"); // https://clang.llvm.org/docs/UsersManual.html#cmdoption-werror
VersionNumber ClangVersion = Target.WindowsPlatform.Compiler == WindowsCompiler.Intel ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
ClangWarnings.GetEnabledWarnings(Arguments);
ClangWarnings.GetDisabledWarnings(CompileEnvironment, Target.StaticAnalyzer, ClangVersion, Arguments);
}
Arguments.AddRange(CompileEnvironment.CppCompileWarnings.GenerateWarningCommandLineArgs(CompileEnvironment, typeof(VCToolChain)));
}
protected virtual void AppendLinkArguments(LinkEnvironment LinkEnvironment, List<string> Arguments)
{
if (Target.WindowsPlatform.Compiler == WindowsCompiler.Clang && Target.WindowsPlatform.bAllowClangLinker)
{
// @todo clang: The following static libraries aren't linking correctly with Clang:
// tbbmalloc.lib, zlib_64.lib, libpng_64.lib, freetype2412MT.lib, IlmImf.lib
// LLD: Assertion failed: result.size() == 1, file ..\tools\lld\lib\ReaderWriter\FileArchive.cpp, line 71
//
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if (!LinkEnvironment.bOmitFramePointers)
{
Arguments.Add("--disable-fp-elim");
}
}
// Don't create a side-by-side manifest file for the executable.
if (Target.WindowsPlatform.ManifestFile == null || LinkEnvironment.bIsBuildingDLL)
{
Arguments.Add("/MANIFEST:NO");
}
else
{
// Remove when we fix so pgo linking run in uba
CppRootPaths RootPaths = LinkEnvironment.RootPaths;
if (LinkEnvironment.bPGOOptimize || LinkEnvironment.bPGOProfile)
{
RootPaths = new(RootPaths);
RootPaths.bUseVfs = false;
}
Arguments.Add("/MANIFEST:EMBED");
FileItem ManifestFile = FileItem.GetItemByPath(Target.WindowsPlatform.ManifestFile);
Arguments.Add($"/MANIFESTINPUT:\"{NormalizeCommandLinePath(ManifestFile, RootPaths)}\"");
// Embed a dependency on the SxS assembly manifest. this is for dependent runtime DLLs
if (!Target.Architecture.bIsX64)
{
string AssemblyName = Target.Architecture.ToString().ToLower();
Arguments.Add("/MANIFESTDEPENDENCY:\"name='" + AssemblyName + "' processorArchitecture='arm64' version='1.0.0.0' type='win32'\"");
}
}
// Prevents the linker from displaying its logo for each invocation.
Arguments.Add("/NOLOGO");
// On Clang, use DWARF debug info format for sample based PGO profile configs
if (Target.WindowsPlatform.Compiler == WindowsCompiler.Clang && Target.WindowsPlatform.bAllowClangLinker &&
Target.WindowsPlatform.bSampleBasedPGO && LinkEnvironment.bPGOProfile)
{
Arguments.Add("/DEBUG:DWARF");
}
// Address sanitizer requires debug info for symbolizing callstacks whether
// we're building debug or shipping.
else if (LinkEnvironment.bCreateDebugInfo || Target.WindowsPlatform.bEnableAddressSanitizer)
{
// Output debug info for the linked executable.
// Beginning in Visual Studio 2017 /DEBUG defaults to /DEBUG:FASTLINK for debug builds
Arguments.Add("/DEBUG:FULL");
if (LinkEnvironment.bCreateDebugInfo && LinkEnvironment.bUseFastPDBLinking)
{
// Allow partial PDBs for faster linking on Clang and Intel
if (Target.WindowsPlatform.Compiler.IsClang() && Target.WindowsPlatform.bAllowClangLinker)
{
Arguments[^1] = "/DEBUG:GHASH";
}
else
{
Arguments[^1] = "/DEBUG:FASTLINK";
}
}
}
// Prompt the user before reporting internal errors to Microsoft.
if (!Target.WindowsPlatform.bAllowRadLinker)
{
Arguments.Add("/errorReport:prompt");
}
//
// PC
//
if (UseWindowsArchitecture(LinkEnvironment.Platform))
{
Arguments.Add($"/MACHINE:{WindowsExports.GetArchitectureName(Target.WindowsPlatform.Architecture)}");
{
if (LinkEnvironment.bIsBuildingConsoleApplication)
{
Arguments.Add("/SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Add("/SUBSYSTEM:WINDOWS");
}
}
if (LinkEnvironment.bIsBuildingConsoleApplication && !LinkEnvironment.bIsBuildingDLL && !String.IsNullOrEmpty(LinkEnvironment.WindowsEntryPointOverride))
{
// Use overridden entry point
Arguments.Add("/ENTRY:" + LinkEnvironment.WindowsEntryPointOverride);
}
// Allow the OS to load the EXE at different base addresses than its preferred base address.
Arguments.Add("/FIXED:No");
// Explicitly declare that the executable is compatible with Data Execution Prevention.
Arguments.Add("/NXCOMPAT");
}
// Set the default stack size.
if (LinkEnvironment.Platform.IsInGroup(UnrealPlatformGroup.Microsoft))
{
if (LinkEnvironment.DefaultStackSize > 0)
{
if (LinkEnvironment.DefaultStackSizeCommit > 0)
{
Arguments.Add("/STACK:" + LinkEnvironment.DefaultStackSize + "," + LinkEnvironment.DefaultStackSizeCommit);
}
else
{
Arguments.Add("/STACK:" + LinkEnvironment.DefaultStackSize);
}
}
}
// Allow delay-loaded DLLs to be explicitly unloaded.
if (Target.WindowsPlatform.Architecture == UnrealArch.X64)
{
Arguments.Add("/DELAY:UNLOAD");
}
if (LinkEnvironment.bIsBuildingDLL)
{
Arguments.Add("/DLL");
}
if (String.IsNullOrEmpty(Target.WindowsPlatform.PdbAlternatePath))
{
// Don't embed the full PDB path; we want to be able to move binaries elsewhere. They will always be side by side.
Arguments.Add("/PDBALTPATH:%_PDB%");
}
else
{
// Embed an alternate PDB path into the executable
Arguments.Add($"/PDBALTPATH:\"{Target.WindowsPlatform.PdbAlternatePath}\"");
}
// Deterministic link support
if (LinkEnvironment.bDeterministic)
{
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
Arguments.Add("/experimental:deterministic");
}
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("/Brepro");
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bVCFastFail && !LinkEnvironment.bPGOOptimize && !LinkEnvironment.bPGOProfile)
{
Arguments.Add("/fastfail");
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bVCExtendedWarningInfo && !Target.WindowsPlatform.bAllowRadLinker)
{
Arguments.Add("/d2:-ExtendedWarningInfo");
}
// Allow for PDBs larger than 4GB
if (Target.WindowsPlatform.PdbPageSize.HasValue)
{
Arguments.Add($"/PDBPAGESIZE:{System.Numerics.BitOperations.RoundUpToPowerOf2(Target.WindowsPlatform.PdbPageSize.Value)}");
//Arguments.Add("/PDBCompress"); // Do not turn this on, it makes link times almost 2x slower. This is _only_ to save local disk space. Will _not_ make actual file smaller for network transfer
}
// Reduce optimizations for huge functions, may improve compile time a the expense of speed for functions over the threshold
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bReducedOptimizeHugeFunctions)
{
Arguments.Add("/d2:\"-ReducedOptimizeHugeFunctions\"");
Arguments.Add($"/d2:\"-ReducedOptimizeThreshold:{Target.WindowsPlatform.ReducedOptimizeHugeFunctionsThreshold}\"");
}
//
// Shipping & LTCG
//
if (LinkEnvironment.bAllowLTCG)
{
// Use link-time code generation.
Arguments.Add("/LTCG");
// This is where we add in the PGO-Lite linkorder.txt if we are using PGO-Lite
//Result += " /ORDER:@linkorder.txt";
//Result += " /VERBOSE";
}
//
// Shipping binary
//
if (LinkEnvironment.Configuration == CppConfiguration.Shipping)
{
// Generate an EXE checksum.
Arguments.Add("/RELEASE");
}
// Eliminate unreferenced symbols.
if (Target.WindowsPlatform.bStripUnreferencedSymbols)
{
Arguments.Add("/OPT:REF");
}
else
{
Arguments.Add("/OPT:NOREF");
}
// Identical COMDAT folding. Prevent Identical Code Folding when instrumentation is enabled to avoid thunks to all be folded together and prevent hotpatching.
if (Target.WindowsPlatform.bMergeIdenticalCOMDATs && !(Target.WindowsPlatform.Compiler.IsClang() && Target.WindowsPlatform.bSampleBasedPGO && LinkEnvironment.bPGOProfile) && !Target.WindowsPlatform.bEnableInstrumentation)
{
Arguments.Add("/OPT:ICF");
}
else
{
Arguments.Add("/OPT:NOICF");
}
// Enable incremental linking if wanted. ( avoid /INCREMENTAL getting ignored (LNK4075) due to /LTCG, /RELEASE, and /OPT:ICF )
if (LinkEnvironment.bUseIncrementalLinking &&
LinkEnvironment.Configuration != CppConfiguration.Shipping &&
!Target.WindowsPlatform.bMergeIdenticalCOMDATs &&
!LinkEnvironment.bAllowLTCG &&
Target.WindowsPlatform.Architecture == UnrealArch.X64)
{
Arguments.Add("/INCREMENTAL");
Arguments.Add("/verbose:incr");
}
else
{
Arguments.Add("/INCREMENTAL:NO");
}
// Add any extra options from the target
if (!String.IsNullOrEmpty(Target.WindowsPlatform.AdditionalLinkerOptions))
{
Arguments.Add(Target.WindowsPlatform.AdditionalLinkerOptions);
}
// Disable
//LINK : warning LNK4199: /DELAYLOAD:nvtt_64.dll ignored; no imports found from nvtt_64.dll
// type warning as we leverage the DelayLoad option to put third-party DLLs into a
// non-standard location. This requires the module(s) that use said DLL to ensure that it
// is loaded prior to using it.
Arguments.Add("/ignore:4199");
// Suppress warnings about missing PDB files for statically linked libraries. We often don't want to distribute
// PDB files for these libraries.
Arguments.Add("/ignore:4099"); // warning LNK4099: PDB '<file>' was not found with '<file>'
// Workaround for linker errors when linking against static libraries that were compiled with an older msvc
// https://github.com/microsoft/STL/issues/2655
Arguments.Add("/ALTERNATENAME:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize");
Arguments.Add("/ALTERNATENAME:__imp___std_init_once_complete=__imp_InitOnceComplete");
if (Target.WindowsPlatform.bEnableAddressSanitizer)
{
// With clang we seemingly need to explicitly pass the .lib's to link against for ASan.
if (Target.WindowsPlatform.Compiler.IsClang())
{
DirectoryReference ASanRuntimeDir;
string ASanArchSuffix;
if (EnvVars.Architecture == UnrealArch.X64)
{
ASanRuntimeDir = DirectoryReference.Combine(EnvVars.ToolChainDir, "lib", "x64");
ASanArchSuffix = "x86_64";
}
else
{
throw new BuildException("Unsupported build architecture for Address Sanitizer");
}
string ASanRuntimeLib = $"clang_rt.asan_dynamic-{ASanArchSuffix}.lib";
string ASanDebugRuntimeLib = $"clang_rt.asan_dbg_dynamic-{ASanArchSuffix}.lib";
if (Target.bDebugBuildsActuallyUseDebugCRT)
{
LinkEnvironment.Libraries.Add(FileReference.Combine(ASanRuntimeDir, ASanDebugRuntimeLib));
}
else
{
LinkEnvironment.Libraries.Add(FileReference.Combine(ASanRuntimeDir, ASanRuntimeLib));
}
LinkEnvironment.Libraries.Add(FileReference.Combine(ASanRuntimeDir, $"clang_rt.asan_dynamic_runtime_thunk-{ASanArchSuffix}.lib"));
}
}
if (Target.WindowsPlatform.bEnableUndefinedBehaviorSanitizer)
{
// With clang we seemingly need to explicitly pass the .lib's to link against for UBSan.
if (Target.WindowsPlatform.Compiler.IsClang())
{
DirectoryReference UBSanRuntimeDir;
string UBSanArchSuffix;
if (EnvVars.Architecture == UnrealArch.X64)
{
VersionNumber ClangVersion = Target.WindowsPlatform.Compiler == WindowsCompiler.Intel ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
UBSanRuntimeDir = DirectoryReference.Combine(EnvVars.CompilerDir, "lib", "clang", $"{ClangVersion.Components[0]}", "lib", "windows");
//UBSanRuntimeDir = DirectoryReference.Combine(EnvVars.ToolChainDir, "lib", "x64");
UBSanArchSuffix = "x86_64";
}
else
{
throw new BuildException("Unsupported build architecture for Undefined Behavior Sanitizer");
}
string UBSanRuntimeLib = $"clang_rt.ubsan_standalone-{UBSanArchSuffix}.lib";
LinkEnvironment.Libraries.Add(FileReference.Combine(UBSanRuntimeDir, UBSanRuntimeLib));
}
}
}
protected virtual void AppendLibArguments(LinkEnvironment LinkEnvironment, List<string> Arguments)
{
// Prevents the linker from displaying its logo for each invocation.
Arguments.Add("/NOLOGO");
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Add("/errorReport:prompt");
//
// PC
//
if (UseWindowsArchitecture(LinkEnvironment.Platform))
{
Arguments.Add($"/MACHINE:{WindowsExports.GetArchitectureName(Target.WindowsPlatform.Architecture)}");
{
if (LinkEnvironment.bIsBuildingConsoleApplication)
{
Arguments.Add("/SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Add("/SUBSYSTEM:WINDOWS");
}
}
}
//
// Shipping & LTCG
//
if (LinkEnvironment.bAllowLTCG || LinkEnvironment.Configuration == CppConfiguration.Shipping)
{
// Use link-time code generation.
Arguments.Add("/LTCG");
}
}
private VCCompileAction CreateBaseCompileAction(CppCompileEnvironment CompileEnvironment)
{
VCCompileAction BaseCompileAction = new VCCompileAction(EnvVars);
// TODO: Revisit this code. We want to use d1trimfile to make outputs machine independent.
// but want to make sure we're not causing any frustration for devs (since __FILE__ will show a relative path with lines below)
// Also need to find the equivalent for clang
#if false
if (!Target.WindowsPlatform.Compiler.IsClang())
{
foreach (DirectoryItem rootPath in rootPaths)
{
string pathName = rootPath.FullName;
if (pathName.Contains(' '))
BaseCompileAction.Arguments.Add($"\"/d1trimfile:{pathName}\\\"");
else
BaseCompileAction.Arguments.Add($"/d1trimfile:{pathName}\\");
}
}
#endif
BaseCompileAction.RootPaths = CompileEnvironment.RootPaths;
// Add additional response files
BaseCompileAction.AdditionalResponseFiles.AddRange(CompileEnvironment.AdditionalResponseFiles);
AppendCLArguments_Global(CompileEnvironment, BaseCompileAction.Arguments);
BaseCompileAction.bIsAnalyzing = Target.StaticAnalyzer != StaticAnalyzer.None && !CompileEnvironment.bDisableStaticAnalysis && !(Target.WindowsPlatform.Compiler.IsClang() && CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create);
BaseCompileAction.bWarningsAsErrors = CompileEnvironment.bWarningsAsErrors;
// Add include paths to the argument list.
BaseCompileAction.IncludePaths.AddRange(CompileEnvironment.UserIncludePaths);
BaseCompileAction.SystemIncludePaths.AddRange(CompileEnvironment.SystemIncludePaths);
if (!CompileEnvironment.bHasSharedResponseFile)
{
BaseCompileAction.SystemIncludePaths.AddRange(EnvVars.IncludePaths);
}
// Remember the architecture
BaseCompileAction.Architecture = CompileEnvironment.Architecture;
// Add preprocessor definitions to the argument list.
BaseCompileAction.Definitions.AddRange(CompileEnvironment.Definitions);
// Add the force included headers
BaseCompileAction.ForceIncludeFiles.AddRange(CompileEnvironment.ForceIncludeFiles);
// If we're using precompiled headers, set that up now
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
FileItem IncludeHeader = FileItem.GetItemByFileReference(CompileEnvironment.PrecompiledHeaderIncludeFilename!);
BaseCompileAction.ForceIncludeFiles.Insert(0, IncludeHeader);
BaseCompileAction.UsingPchFile = CompileEnvironment.PrecompiledHeaderFile;
BaseCompileAction.PchThroughHeaderFile = IncludeHeader;
if (Target.WindowsPlatform.Compiler.IsMSVC() && CompileEnvironment.PrecompiledHeaderFile != null && !CompileEnvironment.bUseHeaderUnitsForPch && Target.StaticAnalyzer == StaticAnalyzer.Default && !CompileEnvironment.bDisableStaticAnalysis)
{
BaseCompileAction.AdditionalPrerequisiteItems.Add(FileItem.GetItemByFileReference(new FileReference(CompileEnvironment.PrecompiledHeaderFile.FullName + "ast")));
}
}
if (EnvVars.Compiler.IsClang() && CompileEnvironment.bPGOOptimize)
{
BaseCompileAction.AdditionalPrerequisiteItems.Add(FileItem.GetItemByFileReference(GetClangProfDataFilename(CompileEnvironment)));
}
// Generate the timing info
if (CompileEnvironment.bPrintTimingInfo || Target.WindowsPlatform.bCompilerTrace)
{
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
if (CompileEnvironment.bPrintTimingInfo)
{
BaseCompileAction.Arguments.Add("/Bt+ /d2cgsummary");
}
BaseCompileAction.Arguments.Add("/d1reportTime");
}
}
// MSVC uses multiple threads when compiling CPP files, so the "weight" is more than 1
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
// If deterministic is enabled, MSVC does not use multiple threads
if (!CompileEnvironment.bDeterministic && !CompileEnvironment.bPreprocessOnly)
{
BaseCompileAction.Weight = Target.MSVCCompileActionWeight;
// If we are building without unity files the multithread part balances out with the start/exit of all the actions
if (!CompileEnvironment.bUseUnity)
{
BaseCompileAction.Weight = 1.0f + (BaseCompileAction.Weight - 1.0f) * 0.5f;
}
}
}
else if (Target.WindowsPlatform.Compiler.IsClang())
{
BaseCompileAction.Weight = Target.ClangCompileActionWeight;
}
// Don't farm out creation of precompiled headers as it is the critical path task.
BaseCompileAction.bCanExecuteRemotely =
CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create ||
CompileEnvironment.bAllowRemotelyCompiledPCHs
;
// When compiling with SN-DBS, modules that contain a #import must be built locally
BaseCompileAction.bCanExecuteRemotelyWithSNDBS = BaseCompileAction.bCanExecuteRemotely && !CompileEnvironment.bBuildLocallyWithSNDBS;
if (Target.bAllowUbaCompression)
{
BaseCompileAction.ToolChainVersion = $"{BaseCompileAction.ToolChainVersion} Compressed";
}
// Calculate cache bucket. We want to use as few buckets as possible per build but we also
// want to make sure that the buckets are not getting too big.
BaseCompileAction.CacheBucket = GetCacheBucket(Target, (!Target.bUseVFS && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None) ? CompileEnvironment.RootPaths : null);
return BaseCompileAction;
}
public override FileItem? CopyDebuggerVisualizer(FileItem SourceFile, DirectoryReference IntermediateDirectory, IActionGraphBuilder Graph)
{
if (SourceFile.HasExtension(".natvis") || SourceFile.HasExtension(".natstepfilter") || SourceFile.HasExtension(".natjmc"))
{
FileReference IntermediateFile = FileReference.Combine(IntermediateDirectory, SourceFile.Name);
if (!Unreal.IsFileInstalled(IntermediateFile))
{
FileItem Item = FileItem.GetItemByFileReference(IntermediateFile);
Graph.CreateCopyAction(SourceFile, Item);
return Item; // Only return the item if we are responsible for copying it, for addition to makefile/manifests
}
}
return null;
}
public override FileItem? LinkDebuggerVisualizer(FileItem SourceFile, DirectoryReference IntermediateDirectory)
{
if (SourceFile.HasExtension(".natvis") || SourceFile.HasExtension(".natstepfilter") || SourceFile.HasExtension(".natjmc"))
{
FileItem IntermediateFile = FileItem.GetItemByFileReference(FileReference.Combine(IntermediateDirectory, SourceFile.Name));
return IntermediateFile;
}
return null;
}
protected override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable<FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph)
{
if (Target.StaticAnalyzer != StaticAnalyzer.None && CompileEnvironment.bDisableStaticAnalysis)
{
return new CPPOutput();
}
VCCompileAction BaseCompileAction = CreateBaseCompileAction(CompileEnvironment);
// Create a compile action for each source file.
List<VCCompileAction> Actions = new List<VCCompileAction>();
foreach (FileItem SourceFile in InputFiles)
{
VCCompileAction CompileAction = new VCCompileAction(BaseCompileAction);
CompileAction.SourceFile = SourceFile;
bool bIsPlainCFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".C";
bool bIsHeaderFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".H";
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Generate a CPP File that just includes the precompiled header.
string PrecompiledHeaderIncludeFilenameString = CompileEnvironment.PrecompiledHeaderIncludeFilename!.GetFileName();
string PchCppFile = $"// Compiler: {EnvVars.CompilerVersion}\n#include \"{PrecompiledHeaderIncludeFilenameString}\"\r\n";
CompileAction.SourceFile = FileItem.GetItemByFileReference(CompileEnvironment.PrecompiledHeaderIncludeFilename!.ChangeExtension(".cpp"));
Graph.CreateIntermediateTextFile(CompileAction.SourceFile, PchCppFile);
// Add the precompiled header file to the produced items list.
string PchExtension = CompileEnvironment.bUseHeaderUnitsForPch ? ".ifc" : ".pch";
CompileAction.CreatePchFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, SourceFile.Location.GetFileName() + PchExtension));
CompileAction.PchThroughHeaderFile = FileItem.GetItemByFileReference(CompileEnvironment.PrecompiledHeaderIncludeFilename);
if (Target.WindowsPlatform.Compiler.IsMSVC() && !CompileEnvironment.bUseHeaderUnitsForPch && Target.StaticAnalyzer == StaticAnalyzer.Default && !CompileEnvironment.bDisableStaticAnalysis)
{
CompileAction.AdditionalProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, SourceFile.Location.GetFileName() + PchExtension + "ast")));
}
// If we're creating a PCH that will be used to compile source files for a library, we need
// the compiled modules to retain a reference to PCH's module, so that debugging information
// will be included in the library. This is also required to avoid linker warning "LNK4206"
// when linking an application that uses this library.
if (CompileEnvironment.bIsBuildingLibrary)
{
// NOTE: The symbol name we use here is arbitrary, and all that matters is that it is
// unique per PCH module used in our library
string FakeUniquePCHSymbolName = CompileEnvironment.PrecompiledHeaderIncludeFilename.GetFileNameWithoutExtension();
CompileAction.Arguments.Add($"/Yl{FakeUniquePCHSymbolName}");
}
}
string FileName = SourceFile.Name;
if (CompileEnvironment.CollidingNames != null && CompileEnvironment.CollidingNames.Contains(SourceFile))
{
string HashString = ContentHash.MD5(SourceFile.AbsolutePath.Substring(Unreal.RootDirectory.FullName.Length)).GetHashCode().ToString("X4");
FileName = Path.GetFileNameWithoutExtension(FileName) + "_" + HashString + Path.GetExtension(FileName);
}
if (CompileEnvironment.bPreprocessOnly)
{
CompileAction.PreprocessedFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, FileName + ".i"));
CompileAction.ResponseFile = FileItem.GetItemByFileReference(GetResponseFileName(CompileEnvironment, CompileAction.PreprocessedFile));
}
else if (Target.WindowsPlatform.Compiler.IsClang() && Target.StaticAnalyzer == StaticAnalyzer.Default && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create)
{
// Clang analysis does not actually create an object, use the dependency list as the response filename
string DependencyListFilename = FileName + ".d";
FileItem DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, DependencyListFilename));
CompileAction.ResponseFile = FileItem.GetItemByFileReference(GetResponseFileName(CompileEnvironment, DependencyListFile));
}
else
{
// Add the object file to the produced item list.
string ObjectLeafFilename = FileName + ".obj";
FileItem ObjectFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, ObjectLeafFilename));
CompileAction.ObjectFile = ObjectFile;
CompileAction.ResponseFile = FileItem.GetItemByFileReference(GetResponseFileName(CompileEnvironment, ObjectFile));
if (Target.WindowsPlatform.ObjSrcMapFile != null)
{
using (StreamWriter Writer = File.AppendText(Target.WindowsPlatform.ObjSrcMapFile))
{
Writer.WriteLine($"\"{ObjectLeafFilename}\" -> \"{SourceFile.AbsolutePath}\"");
}
}
if (IsDynamicDebuggingEnabled)
{
CompileAction.AdditionalProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, FileName + ".alt.obj")));
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && CompileEnvironment.bWithAssembly && SourceFile.HasExtension(".cpp"))
{
CompileAction.AssemblyFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, FileName + ".asm"));
}
// Experimental: support for JSON output of timing data
if (Target.WindowsPlatform.Compiler.IsClang() && (Target.bPrintToolChainTimingInfo || Target.WindowsPlatform.bClangTimeTrace))
{
CompileAction.Arguments.Add("-ftime-trace");
CompileAction.AdditionalProducedItems.Add(FileItem.GetItemByFileReference(ObjectFile.Location.ChangeExtension(".json")));
}
}
{
CompileAction.ArtifactMode = ArtifactMode.Enabled;
if (CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None)
{
if (!Target.bUseVFS)
{
// Unfortunately we require matching absolute paths for pch to be cached if not using vfs
CompileAction.ArtifactMode |= ArtifactMode.AbsolutePath;
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
CompileAction.Arguments.Add("-Xclang -fno-pch-timestamp"); // This is needed to prevent check on timestamp stored inside pch
CompileAction.Arguments.Add("-Xclang -fvalidate-ast-input-files-content"); // Validate PCH inputs by content if mtime check fails
}
}
}
// Create PDB files if we were configured to do that.
if (CompileEnvironment.bUsePDBFiles || CompileEnvironment.bSupportEditAndContinue)
{
FileReference PDBLocation;
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
// All files using the same PCH are required to share the same PDB that was used when compiling the PCH
PDBLocation = CompileEnvironment.PrecompiledHeaderFile!.Location.ChangeExtension(".pdb");
// Enable synchronous file writes, since we'll be modifying the existing PDB
CompileAction.Arguments.Add("/FS");
}
else if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Files creating a PCH use a PDB per file.
PDBLocation = FileReference.Combine(OutputDir, CompileEnvironment.PrecompiledHeaderIncludeFilename!.GetFileName() + ".pdb");
// Enable synchronous file writes, since we'll be modifying the existing PDB
CompileAction.Arguments.Add("/FS");
}
else if (!bIsPlainCFile)
{
// Ungrouped C++ files use a PDB per file.
PDBLocation = FileReference.Combine(OutputDir, FileName + ".pdb");
}
else
{
// Group all plain C files that doesn't use PCH into the same PDB
PDBLocation = FileReference.Combine(OutputDir, "MiscPlainC.pdb");
}
// Specify the PDB file that the compiler should write to.
CompileAction.Arguments.Add($"/Fd\"{NormalizeCommandLinePath(PDBLocation, CompileEnvironment.RootPaths)}\"");
// Don't allow remote execution when PDB files are enabled; we need to modify the same files. XGE works around this by generating separate
// PDB files per agent, but this functionality is only available with the Visual C++ extension package (via the VCCompiler=true tool option).
CompileAction.bCanExecuteRemotely = false;
}
// Add C or C++ specific compiler arguments.
if (bIsPlainCFile)
{
AppendCLArguments_C(CompileEnvironment, CompileAction.Arguments);
}
else if (bIsHeaderFile)
{
AppendCLArguments_H(CompileEnvironment, CompileAction.Arguments);
}
else
{
AppendCLArguments_CPP(CompileEnvironment, CompileAction.Arguments);
}
// Add additional arguments to the argument list, must be the final arguments added
if (!String.IsNullOrEmpty(CompileEnvironment.AdditionalArguments))
{
CompileAction.Arguments.Add(CompileEnvironment.AdditionalArguments);
}
if (Target.WindowsPlatform.Compiler.IsClang())
{
// If we are using the AutoRTFM compiler, we make the compile action depend on the version of the compiler itself.
// This lets us update the compiler (which might not cause a version update of the compiler, which instead tracks
// the LLVM versioning scheme that Clang uses), but ensure that we rebuild the source if the compiler has changed.
if (CompileEnvironment.bUseAutoRTFMCompiler)
{
FileReference? CompilerPath = GetCppCompilerPath();
if (null != CompilerPath)
{
CompileAction.AdditionalPrerequisiteItems.Add(FileItem.GetItemByFileReference(CompilerPath));
}
}
}
List<FileItem>? InlinedFiles;
if (CompileEnvironment.FileInlineGenCPPMap.TryGetValue(SourceFile, out InlinedFiles))
{
CompileAction.AdditionalPrerequisiteItems.AddRange(InlinedFiles);
}
CompileAction.AdditionalPrerequisiteItems.AddRange(CompileEnvironment.AdditionalPrerequisites);
if (SourceFile.HasExtension(".ixx"))
{
FileItem IfcFile = FileItem.GetItemByFileReference(FileReference.Combine(GetModuleInterfaceDir(OutputDir), SourceFile.Location.ChangeExtension(".ifc").GetFileName()));
CompileAction.Arguments.Add("/interface");
CompileAction.Arguments.Add($"/ifcOutput \"{IfcFile.Location}\"");
CompileAction.CompiledModuleInterfaceFile = IfcFile;
FileItem IfcDepsFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, FileName + ".md.json"));
VCCompileAction CompileDepsAction = new VCCompileAction(CompileAction);
CompileDepsAction.ActionType = ActionType.GatherModuleDependencies;
CompileDepsAction.ResponseFile = FileItem.GetItemByFileReference(GetResponseFileName(CompileEnvironment, IfcDepsFile));
CompileDepsAction.ObjectFile = null;
CompileDepsAction.DependencyListFile = IfcDepsFile;
CompileDepsAction.Arguments.Add($"/sourceDependencies:directives \"{IfcDepsFile.Location}\"");
CompileDepsAction.AdditionalPrerequisiteItems.Add(SourceFile);
CompileDepsAction.AdditionalPrerequisiteItems.AddRange(CompileEnvironment.AdditionalPrerequisites);
CompileDepsAction.AdditionalProducedItems.Add(IfcDepsFile);
Graph.AddAction(CompileDepsAction);
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
CompileDepsAction.WriteResponseFile(Graph, Logger);
}
CompileAction.ActionType = ActionType.CompileModuleInterface;
CompileAction.AdditionalPrerequisiteItems.Add(IfcDepsFile); // Force the dependencies file into the action graph
CompileAction.AdditionalProducedItems.Add(IfcFile);
CompileAction.CompiledModuleInterfaceFile = IfcFile;
}
if ((Target.bPrintToolChainTimingInfo || Target.WindowsPlatform.bCompilerTrace) && Target.WindowsPlatform.Compiler.IsMSVC())
{
CompileAction.ForceClFilter = true;
CompileAction.TimingFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.timing"));
GenerateParseTimingInfoAction(SourceFile, CompileAction.TimingFile, Graph);
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && !CompileAction.ForceClFilter)
{
CompileAction.DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.dep.json"));
}
else if (Target.WindowsPlatform.Compiler.IsClang())
{
CompileAction.DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.d"));
}
else
{
CompileAction.DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.txt"));
CompileAction.bShowIncludes = Target.bShowIncludes;
}
// Write cl errors and warnings to a file
if (Target.WindowsPlatform.Compiler.IsMSVC() && Target.WindowsPlatform.bWriteSarif)
{
if (Target.StaticAnalyzer == StaticAnalyzer.Default && !CompileEnvironment.bDisableStaticAnalysis)
{
CompileAction.AnalyzeLogFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.sa.sarif"));
}
CompileAction.ExperimentalLogFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, $"{FileName}.sarif"));
}
// Pch are not compatible between different architectures (msvc pch is just a memory dump)
if (Target.WindowsPlatform.Compiler.IsMSVC() && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None)
{
CompileAction.bCanExecuteInUBACrossArchitecture = false;
}
// Allow derived toolchains to make further changes
ModifyFinalCompileAction(CompileAction, CompileEnvironment, SourceFile, OutputDir, ModuleName);
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
CompileAction.WriteResponseFile(Graph, Logger);
}
// Must be added after response file is created just to make sure it ends up on the command line and not in the response file
if (Target.bMergeModules)
{
// EXTRACTEXPORTS can only be interpreted by UBA.. so this action won't build outside uba
CompileAction.Arguments.Add("/EXTRACTEXPORTS");
FileItem SymFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, FileName + ".exi"));
CompileAction.AdditionalProducedItems.Add(SymFile);
}
CompileAction.bIsAnalyzing = Target.StaticAnalyzer != StaticAnalyzer.None && !CompileEnvironment.bDisableStaticAnalysis && !(Target.WindowsPlatform.Compiler.IsClang() && CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create);
CompileAction.bWarningsAsErrors = CompileEnvironment.bWarningsAsErrors;
// Update the output
Graph.AddAction(CompileAction);
Actions.Add(CompileAction);
}
CPPOutput Result = new CPPOutput();
Result.ObjectFiles.AddRange(Actions.Where(x => x.ObjectFile != null || x.PreprocessedFile != null).Select(x => x.ObjectFile != null ? x.ObjectFile! : x.PreprocessedFile!));
// Clang static analysis doesn't create object files, so treat the dependency list file as the output
if (Target.WindowsPlatform.Compiler.IsClang() && Target.StaticAnalyzer == StaticAnalyzer.Default)
{
Result.ObjectFiles.AddRange(Actions.Where(x => x.DependencyListFile != null).Select(x => x.DependencyListFile!));
}
Result.CompiledModuleInterfaces.AddRange(Actions.Where(x => x.CompiledModuleInterfaceFile != null).Select(x => x.CompiledModuleInterfaceFile!));
Result.PrecompiledHeaderFile = Actions.Select(x => x.CreatePchFile).Where(x => x != null).FirstOrDefault();
return Result;
}
protected virtual void ModifyFinalCompileAction(VCCompileAction CompileAction, CppCompileEnvironment CompileEnvironment, FileItem SourceFile, DirectoryReference OutputDir, string ModuleName)
{
}
private Action GenerateParseTimingInfoAction(FileItem SourceFile, FileItem TimingFile, IActionGraphBuilder Graph)
{
FileItem TimingJsonFile = FileItem.GetItemByPath(Path.ChangeExtension(TimingFile.AbsolutePath, ".cta"));
string ParseTimingArguments = $"-TimingFile=\"{TimingFile}\"";
if (Target.bParseTimingInfoForTracing)
{
ParseTimingArguments += " -Tracing";
}
Action ParseTimingInfoAction = Graph.CreateRecursiveAction<ParseMsvcTimingInfoMode>(ActionType.ParseTimingInfo, ParseTimingArguments);
ParseTimingInfoAction.StatusDescription = Path.GetFileName(TimingFile.AbsolutePath);
ParseTimingInfoAction.bCanExecuteRemotely = true;
ParseTimingInfoAction.bCanExecuteRemotelyWithSNDBS = true;
ParseTimingInfoAction.PrerequisiteItems.Add(SourceFile);
ParseTimingInfoAction.PrerequisiteItems.Add(TimingFile);
ParseTimingInfoAction.ProducedItems.Add(TimingJsonFile);
return ParseTimingInfoAction;
}
public override void FinalizeOutput(ReadOnlyTargetRules Target, TargetMakefileBuilder MakefileBuilder)
{
if (Target.bPGOOptimize)
{
// Win64 PGO folder is Windows, the rest match the platform name
string PGOPlatform = Target.Platform == UnrealTargetPlatform.Win64 ? "Windows" : Target.Platform.ToString();
DirectoryReference PGODirectory = DirectoryReference.Combine(Target.ProjectFile?.Directory ?? Unreal.WritableEngineDirectory, "Platforms", PGOPlatform, "Build", "PGO");
MakefileBuilder.Makefile.InternalDependencies.Add(FileItem.GetItemByFileReference(FileReference.Combine(PGODirectory, "PGOProfileCompilerInfo.txt")));
}
if (Target.bPrintToolChainTimingInfo || Target.WindowsPlatform.bCompilerTrace)
{
TargetMakefile Makefile = MakefileBuilder.Makefile;
List<FileItem> TimingJsonFiles = new List<FileItem>();
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
List<IExternalAction> ParseTimingActions = Makefile.Actions.Where(x => x.ActionType == ActionType.ParseTimingInfo).ToList();
TimingJsonFiles = ParseTimingActions.SelectMany(a => a.ProducedItems.Where(i => i.HasExtension(".cta"))).ToList();
}
else if (Target.WindowsPlatform.Compiler.IsClang())
{
List<IExternalAction> CompileActions = Makefile.Actions.Where(x => x.ActionType == ActionType.Compile && x.ProducedItems.Any(i => i.HasExtension(".json"))).ToList();
TimingJsonFiles = CompileActions.SelectMany(a => a.ProducedItems.Where(i => i.HasExtension(".json"))).ToList();
}
Makefile.OutputItems.AddRange(TimingJsonFiles);
// Handing generating aggregate timing information if we compiled more than one file.
if (TimingJsonFiles.Count > 1)
{
// Generate the file manifest for the aggregator.
if (!DirectoryReference.Exists(Makefile.ProjectIntermediateDirectory))
{
DirectoryReference.CreateDirectory(Makefile.ProjectIntermediateDirectory);
}
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
FileReference ManifestFile = FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}TimingManifest.txt");
File.WriteAllLines(ManifestFile.FullName, TimingJsonFiles.Select(f => f.AbsolutePath));
FileReference ExpectedCompileTimeFile = FileReference.FromString(Path.Combine(Makefile.ProjectIntermediateDirectory.FullName, $"{Target.Name}.json"));
List<string> ActionArgs = new List<string>()
{
$"-Name={Target.Name}",
$"-ManifestFile={ManifestFile.FullName}",
$"-CompileTimingFile={ExpectedCompileTimeFile}",
};
Action AggregateTimingInfoAction = MakefileBuilder.CreateRecursiveAction<AggregateParsedTimingInfo>(ActionType.ParseTimingInfo, String.Join(" ", ActionArgs));
AggregateTimingInfoAction.StatusDescription = $"Aggregating {TimingJsonFiles.Count} Timing File(s)";
AggregateTimingInfoAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
FileItem AggregateOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.cta"));
AggregateTimingInfoAction.ProducedItems.Add(AggregateOutputFile);
Makefile.OutputItems.Add(AggregateOutputFile);
}
else if (Target.WindowsPlatform.Compiler.IsClang())
{
FileReference ManifestFile = FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}TimingManifest.csv");
File.WriteAllLines(ManifestFile.FullName, TimingJsonFiles.Select(f => f.FullName.Remove(f.FullName.Length - ".json".Length)));
FileItem AggregateOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.trace.csv"));
FileItem HeadersOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.headers.csv"));
List<string> AggregateActionArgs = new List<string>()
{
$"-ManifestFile={ManifestFile.FullName}",
$"-AggregateFile={AggregateOutputFile.FullName}",
$"-HeadersFile={HeadersOutputFile.FullName}",
};
Action AggregateTimingInfoAction = MakefileBuilder.CreateRecursiveAction<AggregateClangTimingInfo>(ActionType.ParseTimingInfo, String.Join(" ", AggregateActionArgs));
AggregateTimingInfoAction.StatusDescription = $"Aggregating {TimingJsonFiles.Count} Timing File(s)";
AggregateTimingInfoAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
AggregateTimingInfoAction.ProducedItems.Add(AggregateOutputFile);
AggregateTimingInfoAction.ProducedItems.Add(HeadersOutputFile);
Makefile.OutputItems.AddRange(AggregateTimingInfoAction.ProducedItems);
FileItem ArchiveOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.traces.zip"));
List<string> ArchiveActionArgs = new List<string>()
{
$"-ManifestFile={ManifestFile.FullName}",
$"-ArchiveFile={ArchiveOutputFile.FullName}",
};
Action ArchiveTimingInfoAction = MakefileBuilder.CreateRecursiveAction<AggregateClangTimingInfo>(ActionType.ParseTimingInfo, String.Join(" ", ArchiveActionArgs));
ArchiveTimingInfoAction.StatusDescription = $"Archiving {TimingJsonFiles.Count} Timing File(s)";
ArchiveTimingInfoAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
ArchiveTimingInfoAction.ProducedItems.Add(ArchiveOutputFile);
Makefile.OutputItems.AddRange(ArchiveTimingInfoAction.ProducedItems);
// Extract CompileScore data from traces
FileReference ScoreDataExtractor = FileReference.Combine(Unreal.RootDirectory, "Engine", "Extras", "ThirdPartyNotUE", "CompileScore", "ScoreDataExtractor.exe");
if (OperatingSystem.IsWindows() && FileReference.Exists(ScoreDataExtractor))
{
FileItem CompileScoreOutput = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor"));
Action CompileScoreExtractorAction = MakefileBuilder.CreateAction(ActionType.ParseTimingInfo);
CompileScoreExtractorAction.WorkingDirectory = Unreal.EngineSourceDirectory;
CompileScoreExtractorAction.StatusDescription = $"Extracting CompileScore";
CompileScoreExtractorAction.bCanExecuteRemotely = false;
CompileScoreExtractorAction.bCanExecuteRemotelyWithSNDBS = false;
CompileScoreExtractorAction.bCanExecuteInUBA = false; // TODO: Unknown if supported
CompileScoreExtractorAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
CompileScoreExtractorAction.CommandPath = ScoreDataExtractor;
CompileScoreExtractorAction.CommandArguments = $"-clang -verbosity 0 -timelinepack 1000000 -extract -i \"{NormalizeCommandLinePath(Makefile.ProjectIntermediateDirectory)}\" -o \"{NormalizeCommandLinePath(CompileScoreOutput)}\"";
CompileScoreExtractorAction.ProducedItems.Add(CompileScoreOutput);
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.gbl")));
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.incl")));
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.t0000")));
Makefile.OutputItems.AddRange(CompileScoreExtractorAction.ProducedItems);
}
}
}
}
OptionalUpdateSxSManifest(Target, MakefileBuilder);
}
protected void OptionalUpdateSxSManifest(ReadOnlyTargetRules Target, TargetMakefileBuilder MakefileBuilder)
{
if (Target.WindowsPlatform.ManifestFile != null && !Target.bShouldCompileAsDLL && !Target.Architecture.bIsX64)
{
string AssemblyName = Target.Architecture.ToString().ToLower();
// collect the DLLs that should be included in the SxS manifest
DirectoryReference ManifestDir = DirectoryReference.Combine(MakefileBuilder.Makefile.ExecutableFile.Directory, AssemblyName);
IEnumerable<FileItem> DLLs = MakefileBuilder.Makefile.OutputItems.Where( Item =>
Item.HasExtension(".dll") &&
Item.Location.IsUnderDirectory(ManifestDir)
)
.Distinct();
// load or create the intermediate SxS manifest
bool bDirty = false;
DirectoryReference BaseDir = Target.ProjectFile?.Directory ?? Unreal.EngineDirectory;
FileReference IntermediateManifest = FileReference.Combine( BaseDir, "Intermediate", "SxSManifest", AssemblyName, AssemblyName + ".manifest");
FileReference TemplateManifest = FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "Resources", AssemblyName, AssemblyName + ".manifest");
XDocument XmlManifest;
if (FileReference.Exists(IntermediateManifest))
{
XmlManifest = XDocument.Load(IntermediateManifest.FullName);
}
else
{
XmlManifest = XDocument.Load(TemplateManifest.FullName);
bDirty = true;
}
// update & save the intermediate SxS manifest
if (XmlManifest.Root != null && XmlManifest.Root.Name.LocalName == "assembly")
{
XNamespace XmlNs = XmlManifest.Root.Name.Namespace;
HashSet<string> ExistingDLLNames = [.. XmlManifest.Root.Elements(XmlNs+"file").Select( X => X.Attribute("name")!.Value )];
foreach (FileItem DLL in DLLs)
{
string DLLName = DLL.Location.MakeRelativeTo(ManifestDir);
if (!ExistingDLLNames.Contains(DLLName))
{
XmlManifest.Root.Add( new XElement(XmlNs+"file", new XAttribute("name", DLLName ) ) );
bDirty = true;
}
}
}
if (bDirty)
{
DirectoryReference.CreateDirectory(IntermediateManifest.Directory);
XmlManifest.Save(IntermediateManifest.FullName);
}
// include the intermediate SxS manifest as a dependency
MakefileBuilder.Makefile.InternalDependencies.Add( FileItem.GetItemByFileReference(IntermediateManifest) );
}
}
public override void PrepareRuntimeDependencies(List<RuntimeDependency> RuntimeDependencies, Dictionary<FileReference, FileReference> TargetFileToSourceFile, DirectoryReference ExeDir)
{
// If ASan is enabled we need to copy the companion helper libraries from the MSVC tools bin folder to the
// target executable folder.
if (Target.WindowsPlatform.bEnableAddressSanitizer)
{
DirectoryReference ASanRuntimeDir;
string ASanArchSuffix;
if (EnvVars.Architecture == UnrealArch.X64)
{
ASanRuntimeDir = DirectoryReference.Combine(EnvVars.ToolChainDir, "bin", "Hostx64", "x64");
ASanArchSuffix = "x86_64";
}
else
{
throw new BuildException("Unsupported build architecture for Address Sanitizer");
}
string ASanRuntimeDLL = $"clang_rt.asan_dynamic-{ASanArchSuffix}.dll";
string ASanDebugRuntimeDLL = $"clang_rt.asan_dbg_dynamic-{ASanArchSuffix}.dll";
RuntimeDependencies.Add(new RuntimeDependency(FileReference.Combine(ExeDir, ASanRuntimeDLL), StagedFileType.NonUFS));
TargetFileToSourceFile[FileReference.Combine(ExeDir, ASanRuntimeDLL)] = FileReference.Combine(ASanRuntimeDir, ASanRuntimeDLL);
if (Target.bDebugBuildsActuallyUseDebugCRT)
{
RuntimeDependencies.Add(new RuntimeDependency(FileReference.Combine(ExeDir, ASanDebugRuntimeDLL), StagedFileType.NonUFS));
TargetFileToSourceFile[FileReference.Combine(ExeDir, ASanDebugRuntimeDLL)] = FileReference.Combine(ASanRuntimeDir, ASanDebugRuntimeDLL);
}
}
// copy PGO support binaries for Windows from $(VC_PGO_RunTime_Dir)
if (Target.bPGOProfile && Target.Platform.IsInGroup(UnrealPlatformGroup.Windows) && !Target.WindowsPlatform.Compiler.IsClang())
{
string[] PGOFiles = {
"pgort140.dll",
"pgosweep.exe",
"mspdbcore.dll",
"pgomgr.exe"
};
foreach (string PGOFile in PGOFiles)
{
FileReference SrcFile = EnvVars.Architecture.bIsX64
? FileReference.Combine(EnvVars.ToolChainDir, "bin", "Hostx64", "x64", PGOFile)
: FileReference.Combine(EnvVars.ToolChainDir, "bin", "arm64", PGOFile);
FileReference DstFile = FileReference.Combine(ExeDir, PGOFile);
TargetFileToSourceFile[DstFile] = SrcFile;
RuntimeDependencies.Add(new RuntimeDependency(DstFile, StagedFileType.NonUFS));
}
}
// add a dependency on the SxS manifest
if (Target.WindowsPlatform.ManifestFile != null && !Target.bShouldCompileAsDLL && !Target.Architecture.bIsX64)
{
string AssemblyName = Target.Architecture.ToString().ToLower();
DirectoryReference BaseDir = Target.ProjectFile?.Directory ?? Unreal.EngineDirectory;
FileReference SrcFile = FileReference.Combine( BaseDir, "Intermediate", "SxSManifest", AssemblyName, AssemblyName + ".manifest"); // created in OptionalUpdateSxSManifest during a post-build step
FileReference DstFile = FileReference.Combine(ExeDir, AssemblyName, AssemblyName + ".manifest");
TargetFileToSourceFile[DstFile] = SrcFile;
RuntimeDependencies.Add(new RuntimeDependency(DstFile, StagedFileType.NonUFS));
}
}
public virtual FileReference GetApplicationIcon(FileReference? ProjectFile)
{
if (Target.WindowsPlatform.ApplicationIcon != null)
{
return new FileReference(Target.WindowsPlatform.ApplicationIcon);
}
return WindowsPlatform.GetWindowsApplicationIcon(ProjectFile);
}
protected virtual bool UseWindowsArchitecture(UnrealTargetPlatform Platform)
{
return Platform.IsInGroup(UnrealPlatformGroup.Windows);
}
public override CPPOutput CompileRCFiles(CppCompileEnvironment CompileEnvironment, IEnumerable<FileItem> InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph)
{
CPPOutput Result = new CPPOutput();
CppRootPaths RootPaths = CompileEnvironment.RootPaths;
foreach (FileItem RCFile in InputFiles)
{
Action CompileAction = Graph.CreateAction(ActionType.Compile);
CompileAction.RootPaths = CompileEnvironment.RootPaths;
CompileAction.CommandDescription = "Resource";
CompileAction.WorkingDirectory = Unreal.EngineSourceDirectory;
CompileAction.CommandPath = EnvVars.ResourceCompilerPath;
CompileAction.StatusDescription = Path.GetFileName(RCFile.AbsolutePath);
CompileAction.PrerequisiteItems.UnionWith(CompileEnvironment.ForceIncludeFiles);
CompileAction.PrerequisiteItems.UnionWith(CompileEnvironment.AdditionalPrerequisites);
CompileAction.ArtifactMode = ArtifactMode.Enabled;
CompileAction.CacheBucket = GetCacheBucket(Target, null);
// Resource tool can run remotely if possible
CompileAction.bCanExecuteRemotely = true;
CompileAction.bCanExecuteRemotelyWithSNDBS = false; // no tool template for SN-DBS results in warnings
List<string> Arguments = new List<string>();
// Suppress header spew
Arguments.Add("/nologo");
// If we're compiling for 64-bit Windows, also add the _WIN64 definition to the resource
// compiler so that we can switch on that in the .rc file using #ifdef.
AddDefinition(Arguments, "_WIN64");
// Language
Arguments.Add("/l 0x409");
// Include paths. Don't use AddIncludePath() here, since it uses the full path and exceeds the max command line length.
foreach (DirectoryReference IncludePath in CompileEnvironment.UserIncludePaths)
{
Arguments.Add($"/I \"{NormalizeCommandLinePath(IncludePath, RootPaths)}\"");
}
// System include paths.
foreach (DirectoryReference SystemIncludePath in CompileEnvironment.SystemIncludePaths)
{
Arguments.Add($"/I \"{NormalizeCommandLinePath(SystemIncludePath, RootPaths)}\"");
}
foreach (DirectoryReference SystemIncludePath in EnvVars.IncludePaths)
{
Arguments.Add($"/I \"{NormalizeCommandLinePath(SystemIncludePath, RootPaths)}\"");
}
// Preprocessor definitions.
foreach (string Definition in CompileEnvironment.Definitions)
{
if (!Definition.Contains("_API"))
{
AddDefinition(Arguments, Definition);
}
}
// Figure the icon to use. We can only use a custom icon when compiling to a project-specific intermediate directory (and not for the shared editor executable, for example).
FileReference IconFile;
if (Target.ProjectFile != null && !CompileEnvironment.bUseSharedBuildEnvironment)
{
IconFile = GetApplicationIcon(Target.ProjectFile);
}
else
{
IconFile = GetApplicationIcon(null);
}
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(IconFile));
// Setup the compile environment, setting the icon to use via a macro. This is used in Default.rc2.
AddDefinition(Arguments, $"BUILD_ICON_FILE_NAME=\"\\\"{NormalizeCommandLinePath(IconFile, RootPaths).Replace("/", "\\\\")}\\\"\"");
// Apply the target settings for the resources
if (!CompileEnvironment.bUseSharedBuildEnvironment)
{
if (!String.IsNullOrEmpty(Target.WindowsPlatform.CompanyName))
{
AddDefinition(Arguments, $"PROJECT_COMPANY_NAME={SanitizeMacroValue(Target.WindowsPlatform.CompanyName)}");
}
if (!String.IsNullOrEmpty(Target.WindowsPlatform.CopyrightNotice))
{
AddDefinition(Arguments, $"PROJECT_COPYRIGHT_STRING={SanitizeMacroValue(Target.WindowsPlatform.CopyrightNotice)}");
}
if (!String.IsNullOrEmpty(Target.WindowsPlatform.ProductName))
{
AddDefinition(Arguments, $"PROJECT_PRODUCT_NAME={SanitizeMacroValue(Target.WindowsPlatform.ProductName)}");
}
if (Target.ProjectFile != null)
{
AddDefinition(Arguments, $"PROJECT_PRODUCT_IDENTIFIER={SanitizeMacroValue(Target.ProjectFile.GetFileNameWithoutExtension())}");
}
}
// Add the RES file to the produced item list.
FileItem CompiledResourceFile = FileItem.GetItemByFileReference(
FileReference.Combine(
OutputDir,
Path.GetFileName(RCFile.AbsolutePath) + ".res"
)
);
CompileAction.ProducedItems.Add(CompiledResourceFile);
Arguments.Add($"/fo \"{NormalizeCommandLinePath(CompiledResourceFile, RootPaths)}\"");
Result.ObjectFiles.Add(CompiledResourceFile);
// Add the RC file as a prerequisite of the action.
Arguments.Add($"\"{NormalizeCommandLinePath(RCFile, RootPaths)}\"");
// Create a response file for the resource compilier
FileItem ResponseFile = FileItem.GetItemByFileReference(GetResponseFileName(CompileEnvironment, CompiledResourceFile));
Graph.CreateIntermediateTextFile(ResponseFile, Arguments);
CompileAction.PrerequisiteItems.Add(ResponseFile);
/* rc.exe currently errors when using a response file
string ResponseFileString = NormalizeCommandLinePath(ResponseFile);
// cl.exe can't handle response files with a path longer than 260 characters, and relative paths can push it over the limit
if (!System.IO.Path.IsPathRooted(ResponseFileString) && System.IO.Path.Combine(CompileAction.WorkingDirectory.FullName, ResponseFileString).Length > 260)
{
ResponseFileString = ResponseFile.FullName;
}
CompileAction.CommandArguments = $"@{Utils.MakePathSafeToUseWithCommandLine(ResponseFileString)}";
*/
CompileAction.CommandArguments = String.Join(" ", Arguments);
// Add the C++ source file and its included files to the prerequisite item list.
CompileAction.PrerequisiteItems.Add(RCFile);
}
return Result;
}
public override void GenerateTypeLibraryHeader(CppCompileEnvironment CompileEnvironment, ModuleRules.TypeLibrary TypeLibrary, FileReference OutputFile, FileReference? OutputHeader, IActionGraphBuilder Graph)
{
CppRootPaths RootPaths = CompileEnvironment.RootPaths;
FileItem TypeLibraryFile = FileItem.GetItemByPath(TypeLibrary.FileName);
// Create the input file
StringBuilder Contents = new StringBuilder();
Contents.AppendLine("#include <windows.h>");
Contents.AppendLine("#include <unknwn.h>");
Contents.AppendLine();
Contents.AppendFormat("#import \"{0}\"", NormalizeCommandLinePath(TypeLibraryFile, RootPaths).Replace('\\', '/'));
if (!String.IsNullOrEmpty(TypeLibrary.Attributes))
{
Contents.Append(' ');
Contents.Append(TypeLibrary.Attributes);
}
Contents.AppendLine();
FileItem InputFile = Graph.CreateIntermediateTextFile(OutputFile.ChangeExtension(".cpp"), Contents.ToString());
// Build the argument list for the compiler
FileItem ObjectFile = FileItem.GetItemByFileReference(OutputFile.ChangeExtension(".obj"));
List<string> Arguments = new List<string>
{
$"\"{NormalizeCommandLinePath(InputFile, RootPaths)}\"",
"/c",
"/nologo",
$"/Fo\"{NormalizeCommandLinePath(ObjectFile, RootPaths)}\""
};
foreach (DirectoryReference IncludePath in CompileEnvironment.UserIncludePaths)
{
AddIncludePath(Arguments, IncludePath, EnvVars.ToolChain, RootPaths);
}
foreach (DirectoryReference IncludePath in CompileEnvironment.SystemIncludePaths)
{
AddSystemIncludePath(Arguments, IncludePath, EnvVars.ToolChain, RootPaths);
}
foreach (DirectoryReference IncludePath in EnvVars.IncludePaths)
{
AddSystemIncludePath(Arguments, IncludePath, EnvVars.ToolChain, RootPaths);
}
FileItem ResponseFile = Graph.CreateIntermediateTextFile(OutputFile.ChangeExtension(ResponseExt), Arguments);
// Build the command for touching the output file(s) to update the time stamp
string OutfileTlhFullName = NormalizeCommandLinePath(OutputFile, RootPaths).Replace('/', '\\'); // Must be backward slash
string TouchTlhActionCommand = $"if exist \"{OutfileTlhFullName}\" ( copy /b \"{OutfileTlhFullName}\" + NUL \"{OutfileTlhFullName}\">nul )";
string OutfileTliFullName = NormalizeCommandLinePath(OutputFile.ChangeExtension(".tli"), RootPaths).Replace('/', '\\'); // Must be backward slash
string TouchTliActionCommand = $"if exist \"{OutfileTliFullName}\" ( copy /b \"{OutfileTliFullName}\" + NUL \"{OutfileTliFullName}\">nul )";
// Build the batch file
StringBuilder BatchFileContents = new();
BatchFileContents.AppendLine("@echo off");
BatchFileContents.AppendLine($"\"{NormalizeCommandLinePath(EnvVars.ToolchainCompilerPath, RootPaths)}\" @\"{NormalizeCommandLinePath(ResponseFile, RootPaths)}\"");
BatchFileContents.AppendLine(TouchTlhActionCommand);
BatchFileContents.AppendLine(TouchTliActionCommand);
FileItem BatchFile = Graph.CreateIntermediateTextFile(OutputFile.ChangeExtension(".bat"), BatchFileContents.ToString());
// Create the batch file action that will compile then touch the output file
Action CompileAction = Graph.CreateAction(ActionType.Compile);
CompileAction.CommandDescription = "GenerateTLH";
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(EnvVars.ToolchainCompilerPath));
CompileAction.PrerequisiteItems.Add(TypeLibraryFile);
CompileAction.PrerequisiteItems.Add(InputFile);
CompileAction.PrerequisiteItems.Add(ResponseFile);
CompileAction.PrerequisiteItems.Add(BatchFile);
CompileAction.ProducedItems.Add(ObjectFile);
CompileAction.ProducedItems.Add(FileItem.GetItemByFileReference(OutputFile));
if (OutputHeader != null)
CompileAction.ProducedItems.Add(FileItem.GetItemByFileReference(OutputHeader));
CompileAction.DeleteItems.Add(FileItem.GetItemByFileReference(OutputFile));
CompileAction.DeleteItems.Add(FileItem.GetItemByFileReference(OutputFile.ChangeExtension(".tli"))); // May not be created, depending on Attributes
CompileAction.StatusDescription = TypeLibrary.Header;
CompileAction.WorkingDirectory = Unreal.EngineSourceDirectory;
CompileAction.CommandPath = BuildHostPlatform.Current.Shell;
CompileAction.CommandArguments = $"/C \"{BatchFile}\"";
CompileAction.CommandVersion = EnvVars.ToolChainVersion.ToString();
CompileAction.bShouldOutputStatusDescription = false;
CompileAction.bCanExecuteRemotely = false; // Incompatible with remote distribution
CompileAction.RootPaths = CompileEnvironment.RootPaths;
CompileAction.ArtifactMode = ArtifactMode.Enabled;
CompileAction.CacheBucket = GetCacheBucket(Target, null);
}
public override IEnumerable<string> GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
AppendCLArguments_Global(new(CompileEnvironment), Arguments);
return Arguments;
}
public override IEnumerable<string> GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
AppendCLArguments_CPP(CompileEnvironment, Arguments);
return Arguments;
}
public override IEnumerable<string> GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
AppendCLArguments_C(CompileEnvironment, Arguments);
return Arguments;
}
public override CppCompileEnvironment CreateSharedResponseFile(CppCompileEnvironment CompileEnvironment, FileReference OutResponseFile, IActionGraphBuilder Graph)
{
CppCompileEnvironment NewCompileEnvironment = new CppCompileEnvironment(CompileEnvironment);
List<string> Arguments = new List<string>();
foreach (DirectoryReference IncludePath in NewCompileEnvironment.UserIncludePaths)
{
AddIncludePath(Arguments, IncludePath, Target.WindowsPlatform.Compiler, NewCompileEnvironment.RootPaths);
}
foreach (DirectoryReference IncludePath in NewCompileEnvironment.SystemIncludePaths)
{
AddSystemIncludePath(Arguments, IncludePath, Target.WindowsPlatform.Compiler, NewCompileEnvironment.RootPaths);
}
foreach (DirectoryReference IncludePath in EnvVars.IncludePaths)
{
AddSystemIncludePath(Arguments, IncludePath, Target.WindowsPlatform.Compiler, NewCompileEnvironment.RootPaths);
}
// Stash shared include paths for validation purposes
NewCompileEnvironment.SharedUserIncludePaths = new(NewCompileEnvironment.UserIncludePaths);
NewCompileEnvironment.SharedSystemIncludePaths = new(NewCompileEnvironment.SystemIncludePaths);
NewCompileEnvironment.SharedSystemIncludePaths.UnionWith(EnvVars.IncludePaths);
NewCompileEnvironment.UserIncludePaths.Clear();
NewCompileEnvironment.SystemIncludePaths.Clear();
FileItem FileItem = FileItem.GetItemByFileReference(OutResponseFile);
Graph.CreateIntermediateTextFile(FileItem, Arguments);
NewCompileEnvironment.AdditionalResponseFiles.Add(FileItem);
NewCompileEnvironment.bHasSharedResponseFile = true;
return NewCompileEnvironment;
}
public override void CreateSpecificFileAction(CppCompileEnvironment CompileEnvironment, DirectoryReference SourceDir, DirectoryReference OutputDir, IActionGraphBuilder Graph)
{
// This is not supported for now.. If someone wants it we can implement it
if (CompileEnvironment.Architectures.bIsMultiArch)
{
return;
}
VCCompileAction BaseCompileAction = CreateBaseCompileAction(CompileEnvironment);
AppendCLArguments_CPP(CompileEnvironment, BaseCompileAction.Arguments);
BaseCompileAction.AdditionalPrerequisiteItems.AddRange(CompileEnvironment.AdditionalPrerequisites); // Primarily for ispc.generated.h files
Graph.AddAction(new VcSpecificFileAction(SourceDir, OutputDir, BaseCompileAction, CompileEnvironment));
}
/// <summary>
/// Macros passed via the command line have their quotes stripped, and are tokenized before being re-stringized by the compiler. This conversion
/// back and forth is normally ok, but certain characters such as single quotes must always be paired. Remove any such characters here.
/// </summary>
/// <param name="Value">The macro value</param>
/// <returns>The sanitized value</returns>
static string SanitizeMacroValue(string Value)
{
StringBuilder Result = new StringBuilder(Value.Length);
for (int Idx = 0; Idx < Value.Length; Idx++)
{
if (Value[Idx] != '\'' && Value[Idx] != '\"')
{
Result.Append(Value[Idx]);
}
}
return Result.ToString();
}
public override FileItem[] LinkImportLibrary(LinkEnvironment LinkEnvironment, IActionGraphBuilder Graph)
{
if (LinkEnvironment.bIsCrossReferenced)
{
return LinkAllFiles(LinkEnvironment, true, Graph);
}
// by default do nothing
return Array.Empty<FileItem>();
}
public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
{
CppRootPaths RootPaths = LinkEnvironment.RootPaths;
// Remove when we fix so pgo linking run in uba
if (LinkEnvironment.bPGOOptimize || LinkEnvironment.bPGOProfile)
{
RootPaths = new(RootPaths);
RootPaths.bUseVfs = false;
}
if (LinkEnvironment.bIsBuildingDotNetAssembly)
{
return FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
}
bool bIsBuildingLibraryOrImportLibrary = LinkEnvironment.bIsBuildingLibrary || bBuildImportLibraryOnly;
// Get link arguments.
List<string> Arguments = new List<string>();
if (bIsBuildingLibraryOrImportLibrary)
{
AppendLibArguments(LinkEnvironment, Arguments);
}
else
{
AppendLinkArguments(LinkEnvironment, Arguments);
}
if (Target.WindowsPlatform.Compiler.IsMSVC() && LinkEnvironment.bPrintTimingInfo)
{
Arguments.Add("/time+");
}
if (IsDynamicDebuggingEnabled)
{
Arguments.Add("/dynamicdeopt");
}
// Rad linker should not be used for import libs
if (Target.WindowsPlatform.bAllowRadLinker && !bBuildImportLibraryOnly)
{
// Saves space in type table in pdb
Arguments.Add("/RAD_PDB_HASH_TYPE_NAMES:lenient");
Arguments.Add("/RAD_SHARED_THREAD_POOL");
//Arguments.Add("/RAD_WRITE_TEMP_FILES");
// Speeds up linking - TODO: disabled because of windows issues
//Arguments.Add("/rad_large_pages");
// Speeds up linking. We don't want this enabled on shipping since it will affect security
if (!LinkEnvironment.bIsBuildingDLL && !LinkEnvironment.bIsBuildingLibrary && LinkEnvironment.Configuration != CppConfiguration.Shipping)
{
Arguments.Add("/fixed");
}
}
// If we're only building an import library, add the '/DEF' option that tells the LIB utility
// to simply create a .LIB file and .EXP file, and don't bother validating imports
if (bBuildImportLibraryOnly)
{
Arguments.Add("/DEF");
// Ensure that the import library references the correct filename for the linked binary.
Arguments.Add($"/NAME:\"{LinkEnvironment.OutputFilePath.GetFileName()}\"");
// Ignore warnings about object files with no public symbols.
Arguments.Add("/IGNORE:4221");
}
if (!bIsBuildingLibraryOrImportLibrary)
{
// Delay-load these DLLs.
foreach (string DelayLoadDLL in LinkEnvironment.DelayLoadDLLs.Distinct())
{
Arguments.Add($"/DELAYLOAD:\"{DelayLoadDLL}\"");
}
// Pass the module definition file to the linker if we have one
if (LinkEnvironment.ModuleDefinitionFile != null && LinkEnvironment.ModuleDefinitionFile.Length > 0)
{
Arguments.Add($"/DEF:\"{LinkEnvironment.ModuleDefinitionFile}\"");
}
}
// Set up the library paths for linking this binary
if (bBuildImportLibraryOnly)
{
// When building an import library, ignore all the libraries included via embedded #pragma lib declarations.
// We shouldn't need them to generate exports.
Arguments.Add("/NODEFAULTLIB");
}
else if (!LinkEnvironment.bIsBuildingLibrary)
{
// Add the library paths to the argument list.
foreach (DirectoryReference LibraryPath in LinkEnvironment.SystemLibraryPaths)
{
Arguments.Add($"/LIBPATH:\"{NormalizeCommandLinePath(LibraryPath, RootPaths)}\"");
}
foreach (DirectoryReference LibraryPath in EnvVars.LibraryPaths)
{
Arguments.Add($"/LIBPATH:\"{NormalizeCommandLinePath(LibraryPath, RootPaths)}\"");
}
// Add the excluded default libraries to the argument list.
foreach (string ExcludedLibrary in LinkEnvironment.ExcludedLibraries)
{
Arguments.Add($"/NODEFAULTLIB:\"{ExcludedLibrary}\"");
}
}
// Enable function level hot-patching
if (!bBuildImportLibraryOnly && Target.WindowsPlatform.bCreateHotpatchableImage)
{
Arguments.Add("/FUNCTIONPADMIN:6"); // For some reason, not providing the number causes full linking to happen all the time
}
// For targets that are cross-referenced, we don't want to write a LIB file during the link step as that
// file will clobber the import library we went out of our way to generate during an earlier step. This
// file is not needed for our builds, but there is no way to prevent MSVC from generating it when
// linking targets that have exports. We don't want this to clobber our LIB file and invalidate the
// existing timstamp, so instead we simply emit it with a different name
FileReference? ImportLibraryFilePath = null;
if (LinkEnvironment.bIsCrossReferenced && !bBuildImportLibraryOnly)
{
Arguments.Add("/NOIMPLIB");
if (!Target.WindowsPlatform.Compiler.IsClang())
{
Arguments.Add("/NOEXP"); // This compiler flag does not exist on lld-link.exe.. it skips the writing of the .exp file
}
}
else if (Target.bShouldCompileAsDLL)
{
ImportLibraryFilePath = FileReference.Combine(LinkEnvironment.OutputDirectory!, LinkEnvironment.OutputFilePath.GetFileNameWithoutExtension() + ".lib");
}
else
{
ImportLibraryFilePath = FileReference.Combine(LinkEnvironment.IntermediateDirectory!, LinkEnvironment.OutputFilePath.GetFileNameWithoutExtension() + ".lib");
}
FileItem OutputFile;
if (bBuildImportLibraryOnly)
{
OutputFile = FileItem.GetItemByFileReference(ImportLibraryFilePath!);
}
else
{
OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
}
List<FileItem> ProducedItems = new List<FileItem>();
ProducedItems.Add(OutputFile);
List<FileItem> PrerequisiteItems = new List<FileItem>();
// Add the input files to a response file, and pass the response file on the command-line.
List<string> InputFileNames = new List<string>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
InputFileNames.Add(String.Format("\"{0}\"", NormalizeCommandLinePath(InputFile, RootPaths)));
PrerequisiteItems.Add(InputFile);
}
if (!bIsBuildingLibraryOrImportLibrary)
{
foreach (FileReference Library in LinkEnvironment.Libraries)
{
InputFileNames.Add(String.Format("\"{0}\"", NormalizeCommandLinePath(Library, RootPaths)));
PrerequisiteItems.Add(FileItem.GetItemByFileReference(Library));
}
foreach (string SystemLibrary in LinkEnvironment.SystemLibraries)
{
InputFileNames.Add(String.Format("\"{0}\"", SystemLibrary));
}
foreach (FileItem NatvisFile in LinkEnvironment.DebuggerVisualizerFiles)
{
PrerequisiteItems.Add(NatvisFile);
Arguments.Add(String.Format("/NATVIS:\"{0}\"",
NormalizeCommandLinePath(NatvisFile, RootPaths)));
}
if (Target.WindowsPlatform.ManifestFile != null && !LinkEnvironment.bIsBuildingDLL)
{
PrerequisiteItems.Add(FileItem.GetItemByPath(Target.WindowsPlatform.ManifestFile));
}
}
Arguments.AddRange(InputFileNames);
// Add the output file to the command-line.
Arguments.Add($"/OUT:\"{NormalizeCommandLinePath(OutputFile, RootPaths)}\"");
// For import libraries and exports generated by cross-referenced builds, we don't track output files. VS 15.3+ doesn't touch timestamps for libs
// and exp files with no modifications, breaking our dependency checking, but incremental linking will fall back to a full link if we delete it.
// Since all DLLs are typically marked as cross referenced now anyway, we can just ignore this file to allow incremental linking to work.
if (LinkEnvironment.bHasExports && !LinkEnvironment.bIsBuildingLibrary && !LinkEnvironment.bIsCrossReferenced && !Target.WindowsPlatform.bAllowClangLinker)
{
FileReference ExportFilePath = ImportLibraryFilePath!.ChangeExtension(".exp");
FileItem ExportFile = FileItem.GetItemByFileReference(ExportFilePath);
ProducedItems.Add(ExportFile);
}
if (!bIsBuildingLibraryOrImportLibrary)
{
// There is anything to export
if (LinkEnvironment.bHasExports && !LinkEnvironment.bIsBuildingLibrary && !LinkEnvironment.bIsCrossReferenced)
{
// Write the import library to the output directory for nFringe support.
FileItem ImportLibraryFile = FileItem.GetItemByFileReference(ImportLibraryFilePath!);
Arguments.Add($"/IMPLIB:\"{NormalizeCommandLinePath(ImportLibraryFilePath!, RootPaths!)}\"");
// Like the export file above, don't add the import library as a produced item when it's cross referenced.
if (!LinkEnvironment.bIsCrossReferenced)
{
ProducedItems.Add(ImportLibraryFile);
}
}
if (LinkEnvironment.bCreateDebugInfo)
{
// Write the PDB file to the output directory.
{
FileReference? PDBFilePath = FileReference.Combine(LinkEnvironment.OutputDirectory!, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".pdb");
// If stripping private symbols, write the full pdb as .full.pdb
if (Target.WindowsPlatform.bStripPrivateSymbols)
{
FileItem StrippedPDBFile = FileItem.GetItemByFileReference(PDBFilePath);
Arguments.Add($"/PDBSTRIPPED:\"{NormalizeCommandLinePath(StrippedPDBFile, RootPaths)}\"");
ProducedItems.Add(StrippedPDBFile);
PDBFilePath = FileReference.Combine(LinkEnvironment.OutputDirectory!, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".full.pdb");
}
FileItem PDBFile = FileItem.GetItemByFileReference(PDBFilePath);
Arguments.Add($"/PDB:\"{NormalizeCommandLinePath(PDBFilePath, RootPaths)}\"");
ProducedItems.Add(PDBFile);
}
// Write the MAP file to the output directory.
if (LinkEnvironment.bCreateMapFile)
{
FileReference MAPFilePath = FileReference.Combine(LinkEnvironment.OutputDirectory!, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".map");
FileItem MAPFile = FileItem.GetItemByFileReference(MAPFilePath);
Arguments.Add($"/MAP:\"{NormalizeCommandLinePath(MAPFilePath)}\"");
ProducedItems.Add(MAPFile);
// Export a list of object file paths, so we can locate the object files referenced by the map file
ExportObjectFilePaths(LinkEnvironment, Path.ChangeExtension(MAPFilePath.FullName, ".objpaths"), EnvVars);
}
}
// Add the additional arguments specified by the environment.
if (!String.IsNullOrEmpty(LinkEnvironment.AdditionalArguments))
{
Arguments.Add(LinkEnvironment.AdditionalArguments.Trim());
}
}
// Add any forced references to functions
foreach (string IncludeFunction in LinkEnvironment.IncludeFunctions)
{
Arguments.Add($"/INCLUDE:{IncludeFunction}");
}
// Allow the toolchain to adjust/process the link arguments
ModifyFinalLinkArguments(LinkEnvironment, Arguments, bBuildImportLibraryOnly);
// Add training data as a prerequisite for pgo optimize
if (EnvVars.Compiler.IsMSVC() && LinkEnvironment.bPGOOptimize && LinkEnvironment.PGOFilenamePrefix != null && LinkEnvironment.OutputFilePath.FullName.EndsWith(".exe"))
{
// training data files are copied to the output directory in ModifyFinalLinkArguments() -> PreparePGOFilesMsvc()
PrerequisiteItems.AddRange(DirectoryReference.EnumerateFiles(LinkEnvironment.OutputDirectory!, $"{LinkEnvironment.PGOFilenamePrefix}*.pgd").Select(FileItem.GetItemByFileReference));
PrerequisiteItems.AddRange(DirectoryReference.EnumerateFiles(LinkEnvironment.OutputDirectory!, $"{LinkEnvironment.PGOFilenamePrefix}*.pgc").Select(FileItem.GetItemByFileReference));
}
if (IsDynamicDebuggingEnabled)
{
HashSet<string> exts = [".dll", ".exe", ".exp", ".lib", ".map", ".pdb"];
List<FileItem> additional = [.. ProducedItems.Where(x => exts.Contains(x.Location.GetExtension()))
.Select(x => FileItem.GetItemByFileReference(x.Location.ChangeExtension($".alt{x.Location.GetExtension()}")))
];
ProducedItems.AddRange(additional);
}
// Create a response file for the linker, unless we're generating IntelliSense data
FileReference ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile);
if (!ProjectFileGenerator.bGenerateProjectFiles)
{
FileItem ResponseFile = Graph.CreateIntermediateTextFile(ResponseFileName, Arguments);
PrerequisiteItems.Add(ResponseFile);
}
// Create an action that invokes the linker.
Action LinkAction = Graph.CreateAction(ActionType.Link);
LinkAction.RootPaths = LinkEnvironment.RootPaths;
string ReadableArch = UnrealArchitectureConfig.ForPlatform(LinkEnvironment.Platform).ConvertToReadableArchitecture(LinkEnvironment.Architecture);
LinkAction.CommandDescription += $"Link [{ReadableArch}]";
LinkAction.WorkingDirectory = Unreal.EngineSourceDirectory;
if (bIsBuildingLibraryOrImportLibrary)
{
LinkAction.CommandPath = EnvVars.LibraryManagerPath;
LinkAction.CommandArguments = $"/LIB @\"{NormalizeCommandLinePath(ResponseFileName, RootPaths)}\"";
}
else
{
LinkAction.CommandPath = EnvVars.LinkerPath;
LinkAction.CommandArguments = $"@\"{NormalizeCommandLinePath(ResponseFileName, RootPaths)}\"";
}
LinkAction.CommandVersion = EnvVars.ToolChainVersion.ToString();
LinkAction.ProducedItems.UnionWith(ProducedItems);
LinkAction.PrerequisiteItems.UnionWith(PrerequisiteItems);
LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
LinkAction.ArtifactMode = ArtifactMode.Enabled;
// VS 15.3+ does not touch lib files if they do not contain any modifications, but we need to ensure the timestamps are updated to avoid repeatedly building them.
if (bBuildImportLibraryOnly || (LinkEnvironment.bHasExports && !bIsBuildingLibraryOrImportLibrary))
{
LinkAction.DeleteItems.UnionWith(LinkAction.ProducedItems.Where(x => x.Location.HasExtension(".lib") || x.Location.HasExtension(".exp")));
}
// Delete PDB files for all produced items, since incremental updates are slower than full ones.
if (!LinkEnvironment.bUseIncrementalLinking)
{
LinkAction.DeleteItems.UnionWith(LinkAction.ProducedItems.Where(x => x.Location.HasExtension(".pdb") || x.Location.HasExtension(".full.pdb")));
}
// Tell the action that we're building an import library here and it should conditionally be
// ignored as a prerequisite for other actions
LinkAction.bProducesImportLibrary = bBuildImportLibraryOnly || LinkEnvironment.bIsBuildingDLL;
// Allow remote linking. Note that this may be overriden by the executor (eg. XGE.bAllowRemoteLinking)
if (LinkAction.bProducesImportLibrary || LinkEnvironment.bIsBuildingDLL)
{
LinkAction.bCanExecuteRemotely = true;
}
if (Target.WindowsPlatform.Compiler.IsClang() && Target.WindowsPlatform.bAllowClangLinker && LinkEnvironment.bAllowLTCG)
{
// Set the weight to number of logical cores as lld can max out the available cores
LinkAction.Weight = Utils.GetLogicalProcessorCount();
// Disallow remote to prevent this long running action from running on an agent if remote linking is enabled
LinkAction.bCanExecuteRemotely = false;
}
if (LinkEnvironment.bPGOOptimize || LinkEnvironment.bPGOProfile)
{
LinkAction.bCanExecuteInUBA = false; // Disabled for now. Should revisit to see why it is not working
}
// Create link repro if requested, this argument is intentionally not added to the response file
if (Target.WindowsPlatform.LinkReproDir != null)
{
DirectoryReference LinkReproRoot = new DirectoryReference(Target.WindowsPlatform.LinkReproDir);
DirectoryReference LinkReproPath = DirectoryReference.Combine(LinkReproRoot, OutputFile.Name);
DirectoryReference.CreateDirectory(LinkReproPath);
LinkAction.CommandArguments = $"{LinkAction.CommandArguments} /LINKREPRO:{Utils.MakePathSafeToUseWithCommandLine(LinkReproPath.FullName)}";
LinkAction.bCanExecuteRemotely = false;
LinkAction.bCanExecuteInUBA = false;
}
if (Target.bAllowUbaCompression)
{
LinkAction.CommandVersion = $"{LinkAction.CommandVersion} Compressed";
}
LinkAction.CacheBucket = GetCacheBucket(Target, null);
Logger.LogDebug(" Linking: {StatusDescription}", LinkAction.StatusDescription);
Logger.LogDebug(" Command: {CommandArguments}", LinkAction.CommandArguments);
return OutputFile;
}
protected bool PreparePGOFilesMsvc(LinkEnvironment LinkEnvironment)
{
if (LinkEnvironment.bPGOOptimize && LinkEnvironment.OutputFilePath.FullName.EndsWith(".exe"))
{
if (!Directory.Exists(LinkEnvironment.PGODirectory))
{
Logger.LogWarning("\"{PGODir}\" does not exist", LinkEnvironment.PGODirectory);
return false;
}
// The linker expects the .pgd and any .pgc files to be in the output directory.
// Copy the files there and make them writable...
Logger.LogInformation("...copying the profile guided optimization files to output directory...");
// prefer a PGD file that matches the output file
string PGDFile = Path.Combine(LinkEnvironment.PGODirectory!, LinkEnvironment.PGOFilenamePrefix + ".pgd");
string[] PGCFiles = Array.Empty<string>();
bool bUsingMergedPGD = false;
// check if we are using a pre-merged pgd file, if so use it instead
if (LinkEnvironment.PGOMergedFilenamePrefix != null)
{
string MergedPGDFile = Path.Combine(LinkEnvironment.PGODirectory!, LinkEnvironment.PGOMergedFilenamePrefix + ".pgd");
if (File.Exists(MergedPGDFile))
{
// Only use the merged pgd file if it actually exists, otherwise keep the default behavior
PGDFile = MergedPGDFile;
bUsingMergedPGD = true;
Environment.SetEnvironmentVariable("PGOMGR", "/nowarn:188", EnvironmentVariableTarget.Process);
}
else
{
Logger.LogWarning("The specified merged .pgd file \"{MergedPgdFile}\" was not found.", LinkEnvironment.PGOMergedFilenamePrefix);
}
}
if (!bUsingMergedPGD)
{
if (!File.Exists(PGDFile))
{
string[] PGDFiles = Directory.GetFiles(LinkEnvironment.PGODirectory!, "*.pgd");
if (PGDFiles.Length > 1)
{
throw new BuildException("More than one .pgd file found in \"{0}\" and \"{1}\" not found ", LinkEnvironment.PGODirectory,
PGDFile);
}
else if (PGDFiles.Length == 0)
{
Logger.LogWarning("No .pgd files found in \"{PgoDir}\".", LinkEnvironment.PGODirectory);
return false;
}
PGDFile = PGDFiles.First();
}
PGCFiles = Directory.GetFiles(LinkEnvironment.PGODirectory!, "*.pgc");
if (PGCFiles.Length == 0)
{
Logger.LogWarning("No .pgc files found in \"{PgoDir}\".", LinkEnvironment.PGODirectory);
return false;
}
}
// Make sure the destination directory exists!
Directory.CreateDirectory(LinkEnvironment.OutputDirectory!.FullName);
// Copy the .pgd to the linker output directory, renaming it to match the PGO filename prefix.
string DestPGDFile = Path.Combine(LinkEnvironment.OutputDirectory.FullName, LinkEnvironment.PGOFilenamePrefix + ".pgd");
Logger.LogInformation("{Source} -> {Target}", PGDFile, DestPGDFile);
File.Copy(PGDFile, DestPGDFile, true);
File.SetAttributes(DestPGDFile, FileAttributes.Normal);
// Copy the *!n.pgc files (where n is an integer), renaming them to match the PGO filename prefix and ensuring they are numbered sequentially
int PGCFileIndex = 0;
foreach (string SrcFilePath in PGCFiles)
{
string DestFileName = String.Format("{0}!{1}.pgc", LinkEnvironment.PGOFilenamePrefix, ++PGCFileIndex);
string DestFilePath = Path.Combine(LinkEnvironment.OutputDirectory.FullName, DestFileName);
Logger.LogInformation("{Source} -> {Target}", SrcFilePath, DestFilePath);
File.Copy(SrcFilePath, DestFilePath, true);
File.SetAttributes(DestFilePath, FileAttributes.Normal);
}
}
return true;
}
static readonly string[] PGOIgnoredLinkerSwitch =
{
"/USEPROFILE",
"/GENPROFILE",
"/FASTGENPROFILE",
"/OUT:",
"/PDB:",
"/PDBSTRIPPED:",
"/NOLOGO",
"/LIBPATH:",
"/errorReport:",
"/d2:",
"/NATVIS:",
"/LINKREPRO:",
"/experimental:deterministic"
};
private IEnumerable<string> RemovePGOIgnoredSwitches(IEnumerable<string> SourceArguments)
{
return SourceArguments.Where(Argument => Argument.StartsWith("/") && !PGOIgnoredLinkerSwitch.Any(Switch => Argument.StartsWith(Switch, StringComparison.OrdinalIgnoreCase)));
}
protected virtual void AddPGOLinkArguments(LinkEnvironment LinkEnvironment, List<string> Arguments)
{
bool bPGOOptimize = LinkEnvironment.bPGOOptimize;
bool bPGOProfile = LinkEnvironment.bPGOProfile;
// Write the compiler and version used to generate the PGO profile data
// We may want to use this to ensure compatibility
if (bPGOProfile)
{
VersionNumber CompilerVersion = EnvVars.Compiler.IsIntel() ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
string[] CompilerVersionLines = { EnvVars.Compiler.ToString(), CompilerVersion.ToString() };
string CompilerVersionFilename = Path.Combine(LinkEnvironment.PGODirectory!, "PGOProfileCompilerInfo.txt");
Utils.WriteFileIfChanged(new FileReference(CompilerVersionFilename), CompilerVersionLines, Logger);
}
if (Target.WindowsPlatform.Compiler.IsMSVC())
{
if (bPGOOptimize || bPGOProfile)
{
// serialize the linker arguments for comparing /USEPROFILE and /GENPROFILE
IEnumerable<string> PGOArguments = RemovePGOIgnoredSwitches(Arguments);
string PGOProfileLinkArgsFile = Path.Combine(LinkEnvironment.PGODirectory!, "PGOProfileLinkArgs.txt");
if (bPGOProfile)
{
// save the latest linker arguments for /GENPROFILE alongside the PGO data
Utils.WriteFileIfChanged(new FileReference(PGOProfileLinkArgsFile), PGOArguments, Logger);
}
else if (bPGOOptimize && Target.WindowsPlatform.bIgnoreStalePGOData && File.Exists(PGOProfileLinkArgsFile))
{
// compare latest linker arguments for /USEPROFILE to the last known /GENPROFILE and disable /USEPROFILE if they are different to avoid LNK1268
IEnumerable<string> PGOProfileLinkArgs = RemovePGOIgnoredSwitches(File.ReadAllLines(PGOProfileLinkArgsFile));
IEnumerable<string> AddedPGOArgs = PGOArguments.Except(PGOProfileLinkArgs);
IEnumerable<string> RemovedPGOArgs = PGOProfileLinkArgs.Except(PGOArguments);
if (AddedPGOArgs.Any() || RemovedPGOArgs.Any())
{
Logger.LogWarning("PGO Profile and PGO Optimize linker arguments do not match. Profile Guided Optimization will be disabled for {Config} until new PGO Profile data is generated.", Target.Configuration.ToString());
bPGOOptimize = false;
if (AddedPGOArgs.Any())
{
Logger.LogInformation("Added PGO args:");
foreach (string Arg in AddedPGOArgs)
{
Logger.LogInformation(" {Arg}", Arg);
}
}
if (RemovedPGOArgs.Any())
{
Logger.LogInformation("Removed PGO args:");
foreach (string Arg in RemovedPGOArgs)
{
Logger.LogInformation(" {Arg}", Arg);
}
}
}
}
}
// If PGO was requested, apply Link-time code generation even if PGO was disabled due to mismatched args
if (LinkEnvironment.bPGOOptimize || LinkEnvironment.bPGOProfile)
{
if (!Arguments.Contains("/LTCG"))
{
Arguments.Add("/LTCG");
}
Log.TraceInformationOnce("Enabling Link-time code generation (LTCG) as Profile Guided Optimization (PGO) was requested. Linking will take a while.");
}
if (bPGOOptimize)
{
if (PreparePGOFilesMsvc(LinkEnvironment))
{
Arguments.Add("/USEPROFILE");
Log.TraceInformationOnce("Enabling using Profile Guided Optimization (PGO). Linking will take a while.");
}
else
{
Logger.LogWarning("Unable to prepare Profile Guided Optimization (PGO) files. Using PGO Optimize build will be disabled.");
bPGOOptimize = false;
}
}
else if (bPGOProfile)
{
if (Target.WindowsPlatform.bUseFastGenProfile)
{
Log.TraceInformationOnce("Enabling generating Fastgen Profile Guided Optimization (PGO). Linking will take a while.");
Arguments.Add("/FASTGENPROFILE");
}
else
{
if (Target.WindowsPlatform.bPGONoExtraCounters)
{
Log.TraceInformationOnce("Enabling generating Profile Guided Optimization No Extra Counters (PGO). Linking will take a while.");
Arguments.Add("/GENPROFILE:NOTRACKEH");
}
else
{
Log.TraceInformationOnce("Enabling generating Profile Guided Optimization (PGO). Linking will take a while.");
Arguments.Add("/GENPROFILE");
}
}
}
}
else // Clang
{
if (LinkEnvironment.bAllowLTCG)
{
if (Target.WindowsPlatform.bAllowClangLinker)
{
Log.TraceInformationOnce("Enabling Link-time optimization. Linking will take a while.");
// lld should consider logical cores when determining how many threads to use
Arguments.Add("/opt:lldltojobs=all");
// ThinLTO incremental cache
DirectoryReference? ThinLTOCacheDir = DirectoryReference.FromString(LinkEnvironment.ThinLTOCacheDirectory);
if (ThinLTOCacheDir != null)
{
Arguments.Add($"/lldltocache:\"{ThinLTOCacheDir}\"");
}
}
else
{
Log.TraceWarningOnce("Link-time optimization requires Clang linker.");
}
}
// Link arguments used for PGO on Clang/Intel compiler
if (bPGOOptimize)
{
if (Target.WindowsPlatform.bSampleBasedPGO)
{
Log.TraceInformationOnce("Enabling using Sample-Based Profile Guided Optimization (SPGO). Linking will take a while.");
}
else
{
Log.TraceInformationOnce("Enabling using Profile Guided Optimization (PGO). Linking will take a while.");
}
}
else if (bPGOProfile)
{
if (Target.WindowsPlatform.bSampleBasedPGO)
{
Log.TraceInformationOnce("Enabling generating Sample-based Profile Guided Optimization (SPGO). Linking will take a while.");
if (Target.WindowsPlatform.Compiler.IsIntel())
{
Arguments.Add("-profile-sample-generate");
}
Arguments.Add($"/dwodir:\"{LinkEnvironment.PGODirectory!}\"");
}
else
{
Log.TraceInformationOnce("Enabling generating Profile Guided Optimization (PGO). Linking will take a while.");
}
}
}
}
protected virtual void ModifyFinalLinkArguments(LinkEnvironment LinkEnvironment, List<string> Arguments, bool bBuildImportLibraryOnly)
{
// do not strip the PDB path from the executable if we want to remotely debug it (otherwise VS can't find the symbols)
if (LinkEnvironment.bCreateDebugInfo &&
Target.WindowsPlatform.bEnableExperimentalRemoteDebugging &&
Target.Type != TargetType.Editor &&
Target.Configuration != UnrealTargetConfiguration.Shipping &&
Target.Platform.IsInGroup(UnrealPlatformGroup.Windows))
{
Arguments.Remove("/PDBALTPATH:%_PDB%");
}
// IMPLEMENT_MODULE_ is not required - it only exists to ensure developers add an IMPLEMENT_MODULE() declaration in code. These are always removed for PGO so that adding/removing a module won't invalidate PGC data.
Arguments.RemoveAll(Argument => Argument.StartsWith("/INCLUDE:IMPLEMENT_MODULE_"));
AddPGOLinkArguments(LinkEnvironment, Arguments);
}
private void ExportObjectFilePaths(LinkEnvironment LinkEnvironment, string FileName, VCEnvironment EnvVars)
{
// Write the list of object file directories
HashSet<DirectoryReference> ObjectFileDirectories = new HashSet<DirectoryReference>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
ObjectFileDirectories.Add(InputFile.Location.Directory);
}
foreach (FileReference Library in LinkEnvironment.Libraries)
{
ObjectFileDirectories.Add(Library.Directory);
}
foreach (DirectoryReference LibraryPath in LinkEnvironment.SystemLibraryPaths)
{
ObjectFileDirectories.Add(LibraryPath);
}
foreach (string LibraryPath in (Environment.GetEnvironmentVariable("LIB") ?? "").Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
{
ObjectFileDirectories.Add(new DirectoryReference(LibraryPath));
}
foreach (DirectoryReference LibraryPath in EnvVars.LibraryPaths)
{
ObjectFileDirectories.Add(LibraryPath);
}
Directory.CreateDirectory(Path.GetDirectoryName(FileName)!);
File.WriteAllLines(FileName, ObjectFileDirectories.Select(x => x.FullName).OrderBy(x => x).ToArray());
}
/// <summary>
/// Gets the default include paths for the given platform.
/// </summary>
[SupportedOSPlatform("windows")]
public static string GetVCIncludePaths(UnrealTargetPlatform Platform, WindowsCompiler Compiler, string? CompilerVersion, string? ToolchainVersion, ILogger Logger)
{
// Make sure we've got the environment variables set up for this target
VCEnvironment EnvVars = VCEnvironment.Create(Compiler, WindowsCompiler.Default, Platform, UnrealArch.X64, CompilerVersion, ToolchainVersion, null, null, false, false, false, Logger);
// Also add any include paths from the INCLUDE environment variable. MSVC is not necessarily running with an environment that
// matches what UBT extracted from the vcvars*.bat using SetEnvironmentVariablesFromBatchFile(). We'll use the variables we
// extracted to populate the project file's list of include paths
// @todo projectfiles: Should we only do this for VC++ platforms?
StringBuilder IncludePaths = new StringBuilder();
foreach (DirectoryReference IncludePath in EnvVars.IncludePaths)
{
IncludePaths.AppendFormat("{0};", IncludePath);
}
return IncludePaths.ToString();
}
public override string GetExtraLinkFileExtension()
{
return "obj";
}
public override void SetUpGlobalEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
base.SetUpGlobalEnvironment(Target, GlobalCompileEnvironment, GlobalLinkEnvironment);
GlobalCompileEnvironment.RootPaths.AddFolderName(CppRootPathFolder.Compiler, EnvVars.Compiler.IsMSVC() ? "MSVC" : EnvVars.Compiler.ToString());
GlobalLinkEnvironment.RootPaths.AddFolderName(CppRootPathFolder.Compiler, EnvVars.Compiler.IsMSVC() ? "MSVC" : EnvVars.Compiler.ToString());
GlobalCompileEnvironment.RootPaths[CppRootPathFolder.Compiler] = EnvVars.CompilerDir;
GlobalLinkEnvironment.RootPaths[CppRootPathFolder.Compiler] = EnvVars.CompilerDir;
if (EnvVars.CompilerDir != EnvVars.ToolChainDir)
{
GlobalCompileEnvironment.RootPaths.AddFolderName(CppRootPathFolder.Toolchain, EnvVars.ToolChain.IsMSVC() ? "MSVC" : EnvVars.ToolChain.ToString());
GlobalLinkEnvironment.RootPaths.AddFolderName(CppRootPathFolder.Toolchain, EnvVars.ToolChain.IsMSVC() ? "MSVC" : EnvVars.ToolChain.ToString());
GlobalCompileEnvironment.RootPaths[CppRootPathFolder.Toolchain] = EnvVars.ToolChainDir;
GlobalLinkEnvironment.RootPaths[CppRootPathFolder.Toolchain] = EnvVars.ToolChainDir;
}
GlobalCompileEnvironment.RootPaths[CppRootPathFolder.WinSDK] = EnvVars.WindowsSdkDir;
GlobalLinkEnvironment.RootPaths[CppRootPathFolder.WinSDK] = EnvVars.WindowsSdkDir;
// This is used by uba to create one input key for all files under dirs
EpicGames.UBA.Utils.RegisterPathHash(EnvVars.WindowsSdkDir.FullName, EnvVars.WindowsSdkVersion.ToString());
EpicGames.UBA.Utils.RegisterPathHash(EnvVars.CompilerDir.FullName, EnvVars.CompilerVersion.ToString());
if (EnvVars.Compiler != EnvVars.ToolChain)
{
EpicGames.UBA.Utils.RegisterPathHash(EnvVars.ToolChainDir.FullName, EnvVars.ToolChainVersion.ToString());
}
// Validate PGO data
if (EnvVars.Compiler.IsClang() && GlobalLinkEnvironment.bPGOOptimize && Target.WindowsPlatform.bIgnoreStalePGOData)
{
string CompilerVersionFilename = Path.Combine(GlobalLinkEnvironment.PGODirectory!, "PGOProfileCompilerInfo.txt");
if (File.Exists(CompilerVersionFilename))
{
string[] ProfileCompilerVersionLines = File.ReadAllLines(CompilerVersionFilename);
if (ProfileCompilerVersionLines.Length >= 2 & Enum.TryParse(ProfileCompilerVersionLines[0].Trim(), false, out WindowsCompiler ProfileCompiler) && VersionNumber.TryParse(ProfileCompilerVersionLines[1].Trim(), out VersionNumber? ProfileVersion))
{
VersionNumber CompilerVersion = EnvVars.Compiler.IsIntel() ? MicrosoftPlatformSDK.GetClangVersionForIntelCompiler(EnvVars.CompilerPath) : EnvVars.CompilerVersion;
if (ProfileCompiler.IsClang() && ProfileVersion.Components[0] > CompilerVersion.Components[0])
{
Logger.LogWarning("PGO Profile major LLVM version is newer than PGO Optimize LLVM major version. Profile Guided Optimization will be disabled for {Config} until new PGO Profile data is generated.", Target.Configuration.ToString());
Logger.LogInformation("Profile: {ProfileCompiler} ({ProfileVersion}) Optimize: {OptimizeCompiler} ({OptimizeVersion})", ProfileCompiler, ProfileVersion, EnvVars.Compiler, CompilerVersion);
GlobalCompileEnvironment.bPGOOptimize = false;
GlobalLinkEnvironment.bPGOOptimize = false;
throw new BuildException("test");
}
}
}
}
}
public override void ModifyBuildProducts(ReadOnlyTargetRules Target, UEBuildBinary Binary, IEnumerable<string> Libraries, IEnumerable<UEBuildBundleResource> BundleResources, Dictionary<FileReference, BuildProductType> BuildProducts)
{
if (Binary.Type == UEBuildBinaryType.DynamicLinkLibrary)
{
if (Target.bShouldCompileAsDLL)
{
BuildProducts.Add(FileReference.Combine(Binary.OutputDir, Binary.OutputFilePath.GetFileNameWithoutExtension() + ".lib"), BuildProductType.BuildResource);
}
else
{
BuildProducts.Add(FileReference.Combine(Binary.IntermediateDirectory, Binary.OutputFilePath.GetFileNameWithoutExtension() + ".lib"), BuildProductType.BuildResource);
}
}
if (Binary.Type == UEBuildBinaryType.Executable && Target.bCreateMapFile)
{
foreach (FileReference OutputFilePath in Binary.OutputFilePaths)
{
BuildProducts.Add(FileReference.Combine(OutputFilePath.Directory, OutputFilePath.GetFileNameWithoutExtension() + ".map"), BuildProductType.MapFile);
BuildProducts.Add(FileReference.Combine(OutputFilePath.Directory, OutputFilePath.GetFileNameWithoutExtension() + ".objpaths"), BuildProductType.MapFile);
}
}
if (IsDynamicDebuggingEnabled)
{
foreach (KeyValuePair<FileReference, BuildProductType> item in BuildProducts.ToList())
{
BuildProducts.Add(item.Key.ChangeExtension($".alt{item.Key.GetExtension()}"), item.Value);
}
}
}
}
}