// 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 { /// /// The target being built /// protected ReadOnlyTargetRules Target; /// /// The Visual C++ environment /// 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 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 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"); } } } /// /// Prepares the environment for building /// 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 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); } /// /// Returns the version info for the toolchain. This will be output before building. /// /// String describing the current toolchain public override void GetVersionInfo(List 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 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 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 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 Arguments, FileItem ResponseFile, CppRootPaths RootPaths) { string ResponseFileString = NormalizeCommandLinePath(ResponseFile, RootPaths); Arguments.Add($"@{Utils.MakePathSafeToUseWithCommandLine(ResponseFileString)}"); } public static void AddSourceFile(List Arguments, FileItem SourceFile, CppRootPaths RootPaths) { string SourceFileString = NormalizeCommandLinePath(SourceFile, RootPaths); Arguments.Add(Utils.MakePathSafeToUseWithCommandLine(SourceFileString)); } private static void AddIncludePath(List 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 Arguments, DirectoryReference IncludePath, WindowsCompiler Compiler, CppRootPaths RootPaths) { AddIncludePath(Arguments, IncludePath, Compiler, false, RootPaths); } public static void AddSystemIncludePath(List Arguments, DirectoryReference IncludePath, WindowsCompiler Compiler, CppRootPaths RootPaths) { AddIncludePath(Arguments, IncludePath, Compiler, true, RootPaths); } public static void AddForceIncludeFile(List Arguments, FileItem ForceIncludeFile, CppRootPaths RootPaths) { string ForceIncludeFileString = NormalizeCommandLinePath(ForceIncludeFile, RootPaths); Arguments.Add($"/FI\"{ForceIncludeFileString}\""); } public static void AddCreatePchFile(List 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 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 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 Arguments, FileItem ObjectFile, CppRootPaths RootPaths) { string ObjectFileString = NormalizeCommandLinePath(ObjectFile, RootPaths); Arguments.Add($"/Fo\"{ObjectFileString}\""); } public static void AddAssemblyFile(List 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 Arguments, FileItem AnalyzeLogFile, CppRootPaths RootPaths) { string AnalyzeLogFileString = NormalizeCommandLinePath(AnalyzeLogFile, RootPaths); Arguments.Add("/analyze:log " + Utils.MakePathSafeToUseWithCommandLine(AnalyzeLogFileString)); } public static void AddExperimentalLogFile(List Arguments, FileItem ExperimentalLogFile, CppRootPaths RootPaths) { string ExperimentalLogFileString = NormalizeCommandLinePath(ExperimentalLogFile, RootPaths); Arguments.Add("/experimental:log " + Utils.MakePathSafeToUseWithCommandLine(ExperimentalLogFileString)); } public static void AddSourceDependenciesFile(List Arguments, FileItem SourceDependenciesFile, CppRootPaths RootPaths) { string SourceDependenciesFileString = NormalizeCommandLinePath(SourceDependenciesFile, RootPaths); Arguments.Add("/sourceDependencies " + Utils.MakePathSafeToUseWithCommandLine(SourceDependenciesFileString)); } public static void AddSourceDependsFile(List 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 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 Arguments) { AppendCLArguments_CPP(CompileEnvironment, Arguments); if (Target.WindowsPlatform.Compiler.IsClang()) { ClangWarnings.GetHeaderDisabledWarnings(Arguments); } } protected virtual void AppendCLArguments_CPP(CppCompileEnvironment CompileEnvironment, List 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 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 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 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 '' was not found with '' // 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 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 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 Actions = new List(); 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? 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(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 TimingJsonFiles = new List(); if (Target.WindowsPlatform.Compiler.IsMSVC()) { List 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 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 ActionArgs = new List() { $"-Name={Target.Name}", $"-ManifestFile={ManifestFile.FullName}", $"-CompileTimingFile={ExpectedCompileTimeFile}", }; Action AggregateTimingInfoAction = MakefileBuilder.CreateRecursiveAction(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 AggregateActionArgs = new List() { $"-ManifestFile={ManifestFile.FullName}", $"-AggregateFile={AggregateOutputFile.FullName}", $"-HeadersFile={HeadersOutputFile.FullName}", }; Action AggregateTimingInfoAction = MakefileBuilder.CreateRecursiveAction(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 ArchiveActionArgs = new List() { $"-ManifestFile={ManifestFile.FullName}", $"-ArchiveFile={ArchiveOutputFile.FullName}", }; Action ArchiveTimingInfoAction = MakefileBuilder.CreateRecursiveAction(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 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 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 RuntimeDependencies, Dictionary 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 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 Arguments = new List(); // 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 "); Contents.AppendLine("#include "); 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 Arguments = new List { $"\"{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 GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment) { List Arguments = new(); AppendCLArguments_Global(new(CompileEnvironment), Arguments); return Arguments; } public override IEnumerable GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment) { List Arguments = new(); AppendCLArguments_CPP(CompileEnvironment, Arguments); return Arguments; } public override IEnumerable GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment) { List Arguments = new(); AppendCLArguments_C(CompileEnvironment, Arguments); return Arguments; } public override CppCompileEnvironment CreateSharedResponseFile(CppCompileEnvironment CompileEnvironment, FileReference OutResponseFile, IActionGraphBuilder Graph) { CppCompileEnvironment NewCompileEnvironment = new CppCompileEnvironment(CompileEnvironment); List Arguments = new List(); 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)); } /// /// 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. /// /// The macro value /// The sanitized value 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(); } 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 Arguments = new List(); 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 ProducedItems = new List(); ProducedItems.Add(OutputFile); List PrerequisiteItems = new List(); // Add the input files to a response file, and pass the response file on the command-line. List InputFileNames = new List(); 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 exts = [".dll", ".exe", ".exp", ".lib", ".map", ".pdb"]; List 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(); 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 RemovePGOIgnoredSwitches(IEnumerable SourceArguments) { return SourceArguments.Where(Argument => Argument.StartsWith("/") && !PGOIgnoredLinkerSwitch.Any(Switch => Argument.StartsWith(Switch, StringComparison.OrdinalIgnoreCase))); } protected virtual void AddPGOLinkArguments(LinkEnvironment LinkEnvironment, List 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 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 PGOProfileLinkArgs = RemovePGOIgnoredSwitches(File.ReadAllLines(PGOProfileLinkArgsFile)); IEnumerable AddedPGOArgs = PGOArguments.Except(PGOProfileLinkArgs); IEnumerable 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 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 ObjectFileDirectories = new HashSet(); 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()); } /// /// Gets the default include paths for the given platform. /// [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 Libraries, IEnumerable BundleResources, Dictionary 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 item in BuildProducts.ToList()) { BuildProducts.Add(item.Key.ChangeExtension($".alt{item.Key.GetExtension()}"), item.Value); } } } } }