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

1468 lines
60 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
/// <summary>
/// Common option flags for the Clang toolchains.
/// Usage of these flags is currently inconsistent between various toolchains.
/// </summary>
[Flags]
enum ClangToolChainOptions
{
/// <summary>
/// No custom options
/// </summary>
None = 0,
/// <summary>
/// Enable address sanitzier
/// </summary>
EnableAddressSanitizer = 1 << 0,
/// <summary>
/// Enable hardware address sanitzier
/// </summary>
EnableHWAddressSanitizer = 1 << 1,
/// <summary>
/// Enable thread sanitizer
/// </summary>
EnableThreadSanitizer = 1 << 2,
/// <summary>
/// Enable undefined behavior sanitizer
/// </summary>
EnableUndefinedBehaviorSanitizer = 1 << 3,
/// <summary>
/// Enable minimal undefined behavior sanitizer
/// </summary>
EnableMinimalUndefinedBehaviorSanitizer = 1 << 4,
/// <summary>
/// Enable memory sanitizer
/// </summary>
EnableMemorySanitizer = 1 << 5,
/// <summary>
/// Enable Shared library for the Sanitizers otherwise defaults to Statically linked
/// </summary>
EnableSharedSanitizer = 1 << 6,
/// <summary>
/// Enables link time optimization (LTO). Link times will significantly increase.
/// </summary>
EnableLinkTimeOptimization = 1 << 7,
/// <summary>
/// Enable thin LTO
/// </summary>
EnableThinLTO = 1 << 8,
/// <summary>
/// Enable tuning of debug info for LLDB
/// </summary>
TuneDebugInfoForLLDB = 1 << 10,
/// <summary>
/// Whether or not to preserve the portable symbol file produced by dump_syms
/// </summary>
PreservePSYM = 1 << 11,
/// <summary>
/// (Apple toolchains) Whether we're outputting a dylib instead of an executable
/// </summary>
OutputDylib = 1 << 12,
/// <summary>
/// Enables the creation of custom symbol files used for runtime symbol resolution.
/// </summary>
GenerateSymbols = 1 << 13,
/// <summary>
/// Enables dead code/data stripping and common code folding.
/// </summary>
EnableDeadStripping = 1 << 14,
/// <summary>
/// Indicates that the target is a moduler build i.e. Target.LinkType == TargetLinkType.Modular
/// </summary>
ModularBuild = 1 << 15,
/// <summary>
/// Disable Dump Syms step for faster iteration
/// </summary>
DisableDumpSyms = 1 << 16,
/// <summary>
/// Indicates that the AutoRTFM Clang compiler should be used instead of the standard clang compiler
/// </summary>
UseAutoRTFMCompiler = 1 << 17,
/// <summary>
/// Enable LibFuzzer
/// </summary>
EnableLibFuzzer = 1 << 18,
/// <summary>
/// Modify code generation to help with debugging optimized builds e.g. by extending lifetimes of local variables.
/// It may slightly reduce performance. Thus, it's meant to be used during development only.
/// Supported only on some platforms.
/// </summary>
OptimizeForDebugging = 1 << 19,
/// <summary>
/// Enables compressing the debug sections if the platform supports this
/// </summary>
CompressDebugFile = 1 << 20,
}
abstract class ClangToolChain : ISPCToolChain
{
protected class ClangToolChainInfo
{
protected ILogger Logger { get; init; }
public DirectoryReference? BasePath { get; init; }
public FileReference Clang { get; init; }
public FileReference Archiver { get; init; }
public Version ClangVersion => LazyClangVersion.Value;
public string ClangVersionString => LazyClangVersionString.Value;
public string ArchiverVersionString => LazyArchiverVersionString.Value;
readonly Lazy<Version> LazyClangVersion;
readonly Lazy<string> LazyClangVersionString;
readonly Lazy<string> LazyArchiverVersionString;
/// <summary>
/// Constructor for ClangToolChainInfo
/// </summary>
/// <param name="BasePath">The base path to the clang sdk root, if available</param>
/// <param name="Clang">The path to the compiler</param>
/// <param name="Archiver">The path to the archiver</param>
/// <param name="Logger">Logging interface</param>
public ClangToolChainInfo(DirectoryReference? BasePath, FileReference Clang, FileReference Archiver, ILogger Logger)
{
this.Logger = Logger;
this.BasePath = BasePath;
this.Clang = Clang;
this.Archiver = Archiver;
LazyClangVersion = new Lazy<Version>(() => QueryClangVersion());
LazyClangVersionString = new Lazy<string>(() => QueryClangVersionString());
LazyArchiverVersionString = new Lazy<string>(() => QueryArchiverVersionString());
}
/// <summary>
/// Lazily query the clang version. Will only be executed once.
/// </summary>
/// <returns>The version of clang</returns>
/// <exception cref="BuildException"></exception>
protected virtual Version QueryClangVersion()
{
Match MatchClangVersion = Regex.Match(ClangVersionString, @"clang version (?<full>(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+))");
if (MatchClangVersion.Success && Version.TryParse(MatchClangVersion.Groups["full"].Value, out Version? ClangVersion))
{
return ClangVersion;
}
throw new BuildException($"Failed to query the Clang version number! Got: {ClangVersionString}");
}
/// <summary>
/// Lazily query the clang version output. Will only be executed once.
/// </summary>
/// <returns>The standard output when running Clang --version</returns>
protected virtual string QueryClangVersionString() => Utils.RunLocalProcessAndReturnStdOut(Clang.FullName, "--version", null);
/// <summary>
/// Lazily query the archiver version output. Will only be executed once.
/// </summary>
/// <returns>The standard output when running Archiver --version</returns>
protected virtual string QueryArchiverVersionString() => Utils.RunLocalProcessAndReturnStdOut(Archiver.FullName, "--version", null);
}
// The Clang version being used to compile
Lazy<ClangToolChainInfo> LazyInfo;
protected ClangToolChainInfo Info => LazyInfo.Value;
protected ClangToolChainOptions Options;
protected bool bOptimizeForDebugging => Options.HasFlag(ClangToolChainOptions.OptimizeForDebugging);
// Dummy define to work around clang compilation related to the windows maximum path length limitation
protected static string ClangDummyDefine;
protected const int ClangCmdLineMaxSize = 32 * 1024;
protected const int ClangCmdlineDangerZone = 30 * 1024;
// Target settings
protected bool PreprocessDepends = false;
protected bool ShowIncludes = false;
protected StaticAnalyzer StaticAnalyzer = StaticAnalyzer.None;
protected StaticAnalyzerMode StaticAnalyzerMode = StaticAnalyzerMode.Deep;
protected StaticAnalyzerOutputType StaticAnalyzerOutputType = StaticAnalyzerOutputType.Text;
protected float CompileActionWeight = 1.0f;
static ClangToolChain()
{
const string DummyStart = "-D \"DUMMY_DEFINE";
const string DummyEnd = "\"";
ClangDummyDefine = DummyStart + "_".PadRight(ClangCmdLineMaxSize - ClangCmdlineDangerZone - DummyStart.Length - DummyEnd.Length, 'X') + DummyEnd;
}
public ClangToolChain(ClangToolChainOptions InOptions, ILogger InLogger)
: base(InLogger)
{
Options = InOptions;
LazyInfo = new Lazy<ClangToolChainInfo>(() => { return GetToolChainInfo(); }); // Don't change to => GetToolChainInfo().. it doesnt produce correct code
}
/// <summary>
/// Create toolchain info. Do not call this function, use "Info" to get results
/// </summary>
protected abstract ClangToolChainInfo GetToolChainInfo();
public override void GetExternalDependencies(HashSet<FileItem> ExternalDependencies)
{
base.GetExternalDependencies(ExternalDependencies);
ExternalDependencies.Add(FileItem.GetItemByFileReference(Info.Clang));
ExternalDependencies.Add(FileItem.GetItemByFileReference(Info.Archiver));
}
public override FileReference? GetCppCompilerPath()
{
return LazyInfo.Value.Clang;
}
public override void SetUpGlobalEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment GlobalCompileEnvironment, LinkEnvironment GlobalLinkEnvironment)
{
base.SetUpGlobalEnvironment(Target, GlobalCompileEnvironment, GlobalLinkEnvironment);
if (LazyInfo.Value.BasePath != null)
{
GlobalCompileEnvironment.RootPaths.AddFolderName(CppRootPathFolder.PlatformSDK, $"{Target.Platform}SDK");
GlobalLinkEnvironment.RootPaths.AddFolderName(CppRootPathFolder.PlatformSDK, $"{Target.Platform}SDK");
GlobalCompileEnvironment.RootPaths[CppRootPathFolder.PlatformSDK] = LazyInfo.Value.BasePath;
GlobalLinkEnvironment.RootPaths[CppRootPathFolder.PlatformSDK] = LazyInfo.Value.BasePath;
string sdkVersion = UEBuildPlatformSDK.GetSDKForPlatform(Target.Platform.ToString())?.GetInstalledVersion() ?? "0";
string pathHash = $"{Target.Platform}-{sdkVersion}-{LazyInfo.Value.ClangVersion}";
FileReference versionFile = FileReference.Combine(LazyInfo.Value.BasePath, "..", "Version.txt"); // Optional AutoSDK version, may not exist
if (FileReference.Exists(versionFile))
{
string contents = FileReference.ReadAllText(versionFile).ReplaceLineEndings("-").Trim();
pathHash = $"{pathHash}-{contents}";
}
EpicGames.UBA.Utils.RegisterPathHash(LazyInfo.Value.BasePath.FullName, pathHash);
}
PreprocessDepends = Target.bPreprocessDepends;
ShowIncludes = Target.bShowIncludes;
StaticAnalyzer = Target.StaticAnalyzer;
StaticAnalyzerMode = Target.StaticAnalyzerMode;
StaticAnalyzerOutputType = Target.StaticAnalyzerOutputType;
CompileActionWeight = Target.ClangCompileActionWeight;
}
public override void FinalizeOutput(ReadOnlyTargetRules Target, TargetMakefileBuilder MakefileBuilder)
{
if (Target.bPrintToolChainTimingInfo && Target.bParseTimingInfoForTracing)
{
TargetMakefile Makefile = MakefileBuilder.Makefile;
List<IExternalAction> CompileActions = Makefile.Actions.Where(x => x.ActionType == ActionType.Compile && x.ProducedItems.Any(i => i.HasExtension(".json"))).ToList();
List<FileItem> TimingJsonFiles = CompileActions.SelectMany(a => a.ProducedItems.Where(i => i.HasExtension(".json"))).ToList();
// Handing generating aggregate timing information if we compiled more than one file.
if (TimingJsonFiles.Count > 0)
{
// Generate the file manifest for the aggregator.
FileReference ManifestFile = FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}TimingManifest.csv");
if (!DirectoryReference.Exists(ManifestFile.Directory))
{
DirectoryReference.CreateDirectory(ManifestFile.Directory);
}
File.WriteAllLines(ManifestFile.FullName, TimingJsonFiles.Select(f => f.FullName.Remove(f.FullName.Length - ".json".Length)));
FileItem AggregateOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.trace.csv"));
FileItem HeadersOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.headers.csv"));
List<string> AggregateActionArgs = new List<string>()
{
$"-ManifestFile={ManifestFile.FullName}",
$"-AggregateFile={AggregateOutputFile.FullName}",
$"-HeadersFile={HeadersOutputFile.FullName}",
};
Action AggregateTimingInfoAction = MakefileBuilder.CreateRecursiveAction<AggregateClangTimingInfo>(ActionType.ParseTimingInfo, String.Join(" ", AggregateActionArgs));
AggregateTimingInfoAction.StatusDescription = $"Aggregating {TimingJsonFiles.Count} Timing File(s)";
AggregateTimingInfoAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
AggregateTimingInfoAction.ProducedItems.Add(AggregateOutputFile);
AggregateTimingInfoAction.ProducedItems.Add(HeadersOutputFile);
Makefile.OutputItems.AddRange(AggregateTimingInfoAction.ProducedItems);
FileItem ArchiveOutputFile = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.traces.zip"));
List<string> ArchiveActionArgs = new List<string>()
{
$"-ManifestFile={ManifestFile.FullName}",
$"-ArchiveFile={ArchiveOutputFile.FullName}",
};
Action ArchiveTimingInfoAction = MakefileBuilder.CreateRecursiveAction<AggregateClangTimingInfo>(ActionType.ParseTimingInfo, String.Join(" ", ArchiveActionArgs));
ArchiveTimingInfoAction.StatusDescription = $"Archiving {TimingJsonFiles.Count} Timing File(s)";
ArchiveTimingInfoAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
ArchiveTimingInfoAction.ProducedItems.Add(ArchiveOutputFile);
Makefile.OutputItems.AddRange(ArchiveTimingInfoAction.ProducedItems);
// Extract CompileScore data from traces
FileReference ScoreDataExtractor = FileReference.Combine(Unreal.RootDirectory, "Engine", "Extras", "ThirdPartyNotUE", "CompileScore", "ScoreDataExtractor.exe");
if (OperatingSystem.IsWindows() && FileReference.Exists(ScoreDataExtractor))
{
FileItem CompileScoreOutput = FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor"));
Action CompileScoreExtractorAction = MakefileBuilder.CreateAction(ActionType.ParseTimingInfo);
CompileScoreExtractorAction.WorkingDirectory = Unreal.EngineSourceDirectory;
CompileScoreExtractorAction.StatusDescription = $"Extracting CompileScore";
CompileScoreExtractorAction.bCanExecuteRemotely = false;
CompileScoreExtractorAction.bCanExecuteRemotelyWithSNDBS = false;
CompileScoreExtractorAction.bCanExecuteInUBA = false; // TODO: Unknown if supported
CompileScoreExtractorAction.PrerequisiteItems.UnionWith(TimingJsonFiles);
CompileScoreExtractorAction.CommandPath = ScoreDataExtractor;
CompileScoreExtractorAction.CommandArguments = $"-clang -verbosity 0 -timelinepack 1000000 -extract -i \"{NormalizeCommandLinePath(Makefile.ProjectIntermediateDirectory)}\" -o \"{NormalizeCommandLinePath(CompileScoreOutput)}\"";
CompileScoreExtractorAction.ProducedItems.Add(CompileScoreOutput);
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.gbl")));
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.incl")));
CompileScoreExtractorAction.ProducedItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(Makefile.ProjectIntermediateDirectory, $"{Target.Name}.scor.t0000")));
Makefile.OutputItems.AddRange(CompileScoreExtractorAction.ProducedItems);
}
}
}
}
/// <summary>
/// Sanitizes a preprocessor definition argument if needed.
/// </summary>
/// <param name="Definition">A string in the format "foo=bar" or "foo".</param>
/// <returns>An escaped string</returns>
protected virtual string EscapePreprocessorDefinition(string Definition)
{
// By default don't modify preprocessor definition, handle in platform overrides.
return Definition;
}
/// <summary>
/// Checks if compiler version matches the requirements
/// </summary>
protected bool CompilerVersionGreaterOrEqual(int Major, int Minor, int Patch) => Info.ClangVersion >= new Version(Major, Minor, Patch);
/// <summary>
/// Checks if compiler version matches the requirements
/// </summary>
protected bool CompilerVersionLessThan(int Major, int Minor, int Patch) => Info.ClangVersion < new Version(Major, Minor, Patch);
protected bool IsPreprocessing(CppCompileEnvironment CompileEnvironment) =>
CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create
&& CompileEnvironment.bPreprocessOnly;
protected bool IsAnalyzing(CppCompileEnvironment CompileEnvironment) =>
StaticAnalyzer == StaticAnalyzer.Default
&& CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create
&& !CompileEnvironment.bDisableStaticAnalysis;
protected bool ShouldSkipCompile(CppCompileEnvironment CompileEnvironment) =>
StaticAnalyzer == StaticAnalyzer.Default
&& CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create
&& CompileEnvironment.bDisableStaticAnalysis;
protected virtual void GetCppStandardCompileArgument(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// https://clang.llvm.org/cxx_status.html
switch (CompileEnvironment.CppStandard)
{
case CppStandardVersion.Cpp20:
Arguments.Add("-std=c++20");
break;
case CppStandardVersion.Cpp23:
Arguments.Add("-std=c++23");
break;
case CppStandardVersion.Latest:
Arguments.Add("-std=c++2c");
break;
default:
throw new BuildException($"Unsupported C++ standard type set: {CompileEnvironment.CppStandard}");
}
if (CompileEnvironment.bEnableCoroutines)
{
if (!CompileEnvironment.bEnableExceptions)
{
Arguments.Add("-Wno-coroutine-missing-unhandled-exception");
}
}
if (CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None && CompilerVersionGreaterOrEqual(11, 0, 0))
{
// Validate PCH inputs by content if mtime check fails
Arguments.Add("-Xclang -fno-pch-timestamp"); // This is needed to prevent check on timestamp stored inside pch
Arguments.Add("-fpch-validate-input-files-content");
}
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");
}
}
protected virtual void GetCStandardCompileArgument(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
switch (CompileEnvironment.CStandard)
{
case CStandardVersion.None:
break;
case CStandardVersion.C89:
Arguments.Add("-std=c89");
break;
case CStandardVersion.C99:
Arguments.Add("-std=c99");
break;
case CStandardVersion.C11:
Arguments.Add("-std=c11");
break;
case CStandardVersion.C17:
Arguments.Add("-std=c17");
break;
case CStandardVersion.Latest:
Arguments.Add("-std=c2x");
break;
default:
throw new BuildException($"Unsupported C standard type set: {CompileEnvironment.CStandard}");
}
if (CompileEnvironment.bEnableAutoRTFMInstrumentation)
{
Arguments.Add("-fautortfm");
if (CompileEnvironment.bEnableAutoRTFMVerification)
{
Arguments.Add("-fautortfm-verify");
}
}
}
protected virtual void GetCompileArguments_H(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
GetCompileArguments_CPP(CompileEnvironment, Arguments);
ClangWarnings.GetHeaderDisabledWarnings(Arguments);
}
protected virtual void GetCompileArguments_CPP(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-x c++");
GetCppStandardCompileArgument(CompileEnvironment, Arguments);
}
protected virtual void GetCompileArguments_C(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-x c");
GetCStandardCompileArgument(CompileEnvironment, Arguments);
}
protected virtual void GetCompileArguments_MM(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-x objective-c++");
GetCppStandardCompileArgument(CompileEnvironment, Arguments);
}
protected virtual void GetCompileArguments_M(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-x objective-c");
GetCStandardCompileArgument(CompileEnvironment, Arguments);
}
protected virtual void GetCompileArguments_PCH(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-x c++-header");
if (CompilerVersionGreaterOrEqual(11, 0, 0))
{
Arguments.Add("-fpch-instantiate-templates");
}
GetCppStandardCompileArgument(CompileEnvironment, Arguments);
}
// Conditionally enable (default disabled) generation of information about every class with virtual functions for use by the C++ runtime type identification features
// (`dynamic_cast' and `typeid'). If you don't use those parts of the language, you can save some space by using -fno-rtti.
// Note that exception handling uses the same information, but it will generate it as needed.
protected virtual string GetRTTIFlag(CppCompileEnvironment CompileEnvironment)
{
return CompileEnvironment.bUseRTTI ? "-frtti" : "-fno-rtti";
}
protected virtual string GetUserIncludePathArgument(DirectoryReference IncludePath, CppCompileEnvironment CompileEnvironment)
{
return $"-I\"{NormalizeCommandLinePath(IncludePath, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetSystemIncludePathArgument(DirectoryReference IncludePath, CppCompileEnvironment CompileEnvironment)
{
return $"-isystem\"{NormalizeCommandLinePath(IncludePath, CompileEnvironment.RootPaths)}\"";
}
protected virtual DirectoryReference GetResourceDir(CppCompileEnvironment CompileEnvironment)
{
return DirectoryReference.Combine(Info.BasePath!, "lib", "clang", Info.ClangVersion.Major.ToString());
}
protected virtual void GetCompileArguments_IncludePaths(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.AddRange(CompileEnvironment.UserIncludePaths.Select(IncludePath => GetUserIncludePathArgument(IncludePath, CompileEnvironment)));
Arguments.AddRange(CompileEnvironment.SystemIncludePaths.Select(IncludePath => GetSystemIncludePathArgument(IncludePath, CompileEnvironment)));
if (ShowIncludes)
{
Arguments.Add("-H");
}
}
protected virtual string GetPreprocessorDefinitionArgument(string Definition)
{
return $"-D\"{EscapePreprocessorDefinition(Definition)}\"";
}
protected virtual void GetCompileArguments_PreprocessorDefinitions(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.AddRange(CompileEnvironment.Definitions.Select(Definition => GetPreprocessorDefinitionArgument(Definition)));
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create && StaticAnalyzer == StaticAnalyzer.Default)
{
Arguments.Add(GetPreprocessorDefinitionArgument("__clang_analyzer__"));
}
}
protected virtual string GetForceIncludeFileArgument(FileReference ForceIncludeFile, CppCompileEnvironment CompileEnvironment)
{
return $"-include \"{NormalizeCommandLinePath(ForceIncludeFile, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetForceIncludeFileArgument(FileItem ForceIncludeFile, CppCompileEnvironment CompileEnvironment)
{
return GetForceIncludeFileArgument(ForceIncludeFile.Location, CompileEnvironment);
}
protected virtual string GetIncludePCHFileArgument(FileReference IncludePCHFile, CppCompileEnvironment CompileEnvironment)
{
return $"-include-pch \"{NormalizeCommandLinePath(IncludePCHFile, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetIncludePCHFileArgument(FileItem IncludePCHFile, CppCompileEnvironment CompileEnvironment)
{
return GetIncludePCHFileArgument(IncludePCHFile.Location, CompileEnvironment);
}
protected virtual void GetCompileArguments_ForceInclude(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Add the precompiled header file's path to the force include path
// This needs to be before the other force include paths to ensure clang uses it instead of the source header file.
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
Arguments.Add(GetIncludePCHFileArgument(CompileEnvironment.PrecompiledHeaderFile!, CompileEnvironment));
}
else if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create && CompileEnvironment.ParentPCHInstance != null)
{
Arguments.Add(GetIncludePCHFileArgument(CompileEnvironment.ParentPrecompiledHeaderFile!, CompileEnvironment));
}
Arguments.AddRange(CompileEnvironment.ForceIncludeFiles.Select(ForceIncludeFile => GetForceIncludeFileArgument(ForceIncludeFile, CompileEnvironment)));
}
protected virtual string GetSourceFileArgument(FileItem SourceFile, CppCompileEnvironment CompileEnvironment)
{
return $"\"{NormalizeCommandLinePath(SourceFile, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetOutputFileArgument(FileItem OutputFile, CppRootPaths RootPaths)
{
return $"-o \"{NormalizeCommandLinePath(OutputFile, RootPaths)}\"";
}
protected virtual string GetOutputFileArgument(FileItem OutputFile, CppCompileEnvironment CompileEnvironment)
{
return GetOutputFileArgument(OutputFile, CompileEnvironment.RootPaths);
}
protected virtual string GetDepencenciesListFileArgument(FileItem DependencyListFile, CppCompileEnvironment CompileEnvironment)
{
return $"-MD -MF\"{NormalizeCommandLinePath(DependencyListFile, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetPreprocessDepencenciesListFileArgument(FileItem DependencyListFile, CppCompileEnvironment CompileEnvironment)
{
return $"-M -MF\"{NormalizeCommandLinePath(DependencyListFile, CompileEnvironment.RootPaths)}\"";
}
protected virtual string GetResponseFileArgument(FileItem ResponseFile, CppRootPaths RootPaths)
{
return $"@\"{NormalizeCommandLinePath(ResponseFile, RootPaths)}\"";
}
/// <summary>
/// Common compile arguments that control which warnings are enabled.
/// https://clang.llvm.org/docs/DiagnosticsReference.html
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_WarningsAndErrors(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-Wall"); // https://clang.llvm.org/docs/DiagnosticsReference.html#wall
Arguments.Add("-Werror"); // https://clang.llvm.org/docs/UsersManual.html#cmdoption-werror
ClangWarnings.GetEnabledWarnings(Arguments);
ClangWarnings.GetDisabledWarnings(CompileEnvironment,
StaticAnalyzer,
new VersionNumber(Info.ClangVersion.Major, Info.ClangVersion.Minor, Info.ClangVersion.Build),
Arguments);
// always use absolute paths for errors, this can help IDEs go to the error properly
Arguments.Add("-fdiagnostics-absolute-paths"); // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fdiagnostics-absolute-paths
// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-cla-fcolor-diagnostics
if (Log.ColorConsoleOutput)
{
Arguments.Add("-fdiagnostics-color");
}
// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fdiagnostics-format
if (OperatingSystem.IsWindows())
{
Arguments.Add("-fdiagnostics-format=msvc");
}
// Set the output directory for crashes
// https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fcrash-diagnostics-dir
DirectoryReference? CrashDiagnosticDirectory = DirectoryReference.FromString(CompileEnvironment.CrashDiagnosticDirectory);
if (CrashDiagnosticDirectory != null)
{
if (DirectoryReference.Exists(CrashDiagnosticDirectory))
{
Arguments.Add($"-fcrash-diagnostics-dir=\"{NormalizeCommandLinePath(CrashDiagnosticDirectory)}\"");
}
else
{
Log.TraceWarningOnce("CrashDiagnosticDirectory has been specified but directory \"{CrashDiagnosticDirectory}\" does not exist. Compiler argument \"-fcrash-diagnostics-dir\" has been discarded.", CrashDiagnosticDirectory);
}
}
}
/// <summary>
/// Compile arguments for FP semantics
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_FPSemantics(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
switch (CompileEnvironment.FPSemantics)
{
case FPSemanticsMode.Default: // Default to precise FP semantics.
case FPSemanticsMode.Precise:
// Clang defaults to -ffp-contract=on, which allows fusing multiplications and additions into FMAs.
Arguments.Add("-ffp-contract=off");
break;
case FPSemanticsMode.Imprecise:
Arguments.Add("-ffast-math");
break;
default:
throw new BuildException($"Unsupported FP semantics: {CompileEnvironment.FPSemantics}");
}
}
/// <summary>
/// Compile arguments for optimization settings, such as profile guided optimization and link time optimization
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_Optimizations(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Profile Guided Optimization (PGO) and Link Time Optimization (LTO)
if (CompileEnvironment.bPGOOptimize)
{
Log.TraceInformationOnce("Enabling Profile Guided Optimization (PGO). Linking will take a while.");
DirectoryReference? PGODir = DirectoryReference.FromString(CompileEnvironment.PGODirectory!);
Arguments.Add($"-fprofile-instr-use=\"{NormalizeCommandLinePath(DirectoryReference.Combine(PGODir!, CompileEnvironment.PGOFilenamePrefix!), CompileEnvironment.RootPaths)}\"");
}
else if (CompileEnvironment.bPGOProfile)
{
// Always enable LTO when generating PGO profile data.
Log.TraceInformationOnce("Enabling Profile Guided Instrumentation (PGI). Linking will take a while.");
Arguments.Add("-fprofile-generate");
}
if (!CompileEnvironment.bUseInlining)
{
Arguments.Add("-fno-inline-functions");
}
if (CompilerVersionGreaterOrEqual(12, 0, 0))
{
// 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");
}
}
/// <summary>
/// Compile arguments for architecture specific settings
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_Architecture(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// architecture (all but None are AVX)
if (CompileEnvironment.Architecture == UnrealArch.X64 && CompileEnvironment.MinCpuArchX64 != MinimumCpuArchitectureX64.None)
{
// The binary created will be targeting AVX instructions. Machines without AVX support will crash on any AVX instructions if they run this compilation unit.
// 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.
Arguments.Add("-DPLATFORM_ENABLE_VECTORINTRINSICS=1");
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");
// Inform Unreal code that we have sse2, sse4, and AVX, both available to compile and available to run
Arguments.Add("-DPLATFORM_MAYBE_HAS_AVX=1");
// By setting the ALWAYS_HAS defines, we we direct Unreal code to skip cpuid checks to verify that the running hardware supports sse/avx.
Arguments.Add("-DPLATFORM_ALWAYS_HAS_AVX=1");
}
if (CompileEnvironment.MinCpuArchX64 >= MinimumCpuArchitectureX64.AVX2)
{
Arguments.Add("-mavx2");
Arguments.Add("-DPLATFORM_ALWAYS_HAS_AVX_2=1");
}
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");
Arguments.Add("-DPLATFORM_ALWAYS_HAS_AVX_512=1");
}
}
// Treat chars as signed on arm64
if (CompileEnvironment.Architecture == UnrealArch.Arm64)
{
Arguments.Add("-fsigned-char");
switch (CompileEnvironment.MinArm64CpuTarget)
{
case Arm64TargetCpuType.Graviton2:
Arguments.Add("-march=armv8.2-a+fp16+rcpc+dotprod+crypto");
Arguments.Add("-mtune=neoverse-n1");
break;
case Arm64TargetCpuType.Graviton3:
Arguments.Add("-mcpu=neoverse-512tvb+bf16+rng+crypto");
Arguments.Add("-mtune=neoverse-v1");
break;
}
}
}
/// <summary>
/// Compile arguments for debug settings
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_Debugging(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Optionally enable exception handling (off by default since it generates extra code needed to propagate exceptions)
if (CompileEnvironment.bEnableExceptions)
{
Arguments.Add("-fexceptions");
Arguments.Add("-DPLATFORM_EXCEPTIONS_DISABLED=0");
}
else
{
Arguments.Add("-fno-exceptions");
Arguments.Add("-DPLATFORM_EXCEPTIONS_DISABLED=1");
}
if (bOptimizeForDebugging)
{
Arguments.Add("-fextend-lifetimes");
}
}
/// <summary>
/// Compile arguments for sanitizers
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompilerArguments_Sanitizers(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// ASan
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer))
{
Arguments.Add("-fsanitize=address");
Arguments.Add("-fsanitize-recover=address");
}
// TSan
if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
{
Arguments.Add("-fsanitize=thread");
}
// UBSan
if (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer))
{
Arguments.Add("-fsanitize=undefined");
}
// MSan
if (Options.HasFlag(ClangToolChainOptions.EnableMemorySanitizer))
{
Arguments.Add("-fsanitize=memory");
}
// LibFuzzer
if (Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
{
Arguments.Add("-fsanitize=fuzzer");
}
}
/// <summary>
/// Additional compile arguments.
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_AdditionalArgs(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
if (!String.IsNullOrWhiteSpace(CompileEnvironment.AdditionalArguments))
{
Arguments.Add(CompileEnvironment.AdditionalArguments);
}
}
/// <summary>
/// Compile arguments for running clang-analyze.
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_Analyze(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
Arguments.Add("-Wno-unused-command-line-argument");
// Enable the static analyzer with default checks.
Arguments.Add("--analyze");
// Deprecated in LLVM 15
if (CompilerVersionLessThan(15, 0, 0))
{
// Make sure we check inside nested blocks (e.g. 'if ((foo = getchar()) == 0) {}')
Arguments.Add("-Xclang -analyzer-opt-analyze-nested-blocks");
}
// Write out a pretty web page with navigation to understand how the analysis was derived if HTML is enabled.
Arguments.Add($"-Xclang -analyzer-output={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 (StaticAnalyzerMode == StaticAnalyzerMode.Shallow)
{
Arguments.Add("-Xclang -analyzer-config -Xclang mode=shallow");
}
VersionNumber ClangVersion = new VersionNumber(Info.ClangVersion.Major, Info.ClangVersion.Minor, Info.ClangVersion.Build);
if (CompileEnvironment.StaticAnalyzerCheckers.Count > 0)
{
// Disable all default checks
Arguments.Add("--analyzer-no-default-checks");
// Only enable specific checkers.
foreach (string Checker in CompileEnvironment.StaticAnalyzerCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, ClangVersion)))
{
Arguments.Add($"-Xclang -analyzer-checker -Xclang {Checker}");
}
}
else
{
// Disable default checks.
foreach (string Checker in CompileEnvironment.StaticAnalyzerDisabledCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, ClangVersion)))
{
Arguments.Add($"-Xclang -analyzer-disable-checker -Xclang {Checker}");
}
// Enable additional non-default checks.
foreach (string Checker in CompileEnvironment.StaticAnalyzerAdditionalCheckers.Where(x => ClangWarnings.IsAvailableAnalyzerChecker(x, ClangVersion)))
{
Arguments.Add($"-Xclang -analyzer-checker -Xclang {Checker}");
}
}
}
/// <summary>
/// Common compile arguments for all files in a module.
/// Override and call base.GetCompileArguments_Global() in derived classes.
/// </summary>
///
/// <param name="CompileEnvironment"></param>
/// <param name="Arguments"></param>
protected virtual void GetCompileArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// build up the commandline common to C and C++
Arguments.Add("-c");
Arguments.Add("-pipe");
if (CompileEnvironment.Architecture.bIsX64)
{
// UE5 minspec is 4.2
Arguments.Add("-msse4.2");
}
// Add include paths to the argument list.
GetCompileArguments_IncludePaths(CompileEnvironment, Arguments);
// Add preprocessor definitions to the argument list.
GetCompileArguments_PreprocessorDefinitions(CompileEnvironment, Arguments);
// Add warning and error flags to the argument list.
GetCompileArguments_WarningsAndErrors(CompileEnvironment, Arguments);
// Add FP semantics flags to the argument list.
GetCompileArguments_FPSemantics(CompileEnvironment, Arguments);
// Add optimization flags to the argument list.
GetCompileArguments_Optimizations(CompileEnvironment, Arguments);
// Add architecture flags to the argument list.
GetCompileArguments_Architecture(CompileEnvironment, Arguments);
// Add debugging flags to the argument list.
GetCompileArguments_Debugging(CompileEnvironment, Arguments);
// Add sanitizer flags to the argument list.
GetCompilerArguments_Sanitizers(CompileEnvironment, Arguments);
// Add analysis flags to the argument list.
if (IsAnalyzing(CompileEnvironment))
{
GetCompileArguments_Analyze(CompileEnvironment, Arguments);
}
}
protected virtual string GetFileNameFromExtension(string AbsolutePath, string Extension)
{
return Path.GetFileName(AbsolutePath) + Extension;
}
/// <summary>
/// Compile arguments for specific files in a module. Also updates Action and CPPOutput results.
/// </summary>
/// <param name="CompileEnvironment"></param>
/// <param name="SourceFile"></param>
/// <param name="OutputDir"></param>
/// <param name="Arguments"></param>
/// <param name="CompileAction"></param>
/// <param name="CompileResult"></param>
/// <returns>Path to the target file (such as .o)</returns>
protected virtual FileItem GetCompileArguments_FileType(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, DirectoryReference OutputDir, List<string> Arguments, Action CompileAction, CPPOutput CompileResult)
{
// Add the additional response files
foreach (FileItem AdditionalResponseFile in CompileEnvironment.AdditionalResponseFiles)
{
Arguments.Add(GetResponseFileArgument(AdditionalResponseFile, CompileEnvironment.RootPaths));
}
// Add force include paths to the argument list.
GetCompileArguments_ForceInclude(CompileEnvironment, Arguments);
// Add the C++ source file and its included files to the prerequisite item list.
CompileAction.PrerequisiteItems.UnionWith(CompileEnvironment.ForceIncludeFiles);
CompileAction.PrerequisiteItems.UnionWith(CompileEnvironment.AdditionalPrerequisites);
CompileAction.PrerequisiteItems.Add(SourceFile);
List<FileItem>? InlinedFiles;
if (CompileEnvironment.FileInlineGenCPPMap.TryGetValue(SourceFile, out InlinedFiles))
{
CompileAction.PrerequisiteItems.UnionWith(InlinedFiles);
}
string Extension = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant();
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
GetCompileArguments_PCH(CompileEnvironment, Arguments);
}
else if (Extension == ".C")
{
// Compile the file as C code.
GetCompileArguments_C(CompileEnvironment, Arguments);
}
else if (Extension == ".MM")
{
// Compile the file as Objective-C++ code.
GetCompileArguments_MM(CompileEnvironment, Arguments);
}
else if (Extension == ".M")
{
// Compile the file as Objective-C code.
GetCompileArguments_M(CompileEnvironment, Arguments);
}
else if (Extension == ".H")
{
// Compile the file as C++ code with some additional arguments
GetCompileArguments_H(CompileEnvironment, Arguments);
}
else
{
// Compile the file as C++ code.
GetCompileArguments_CPP(CompileEnvironment, Arguments);
}
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(CompileEnvironment.PrecompiledHeaderIncludeFilename!));
PrecompiledHeaderInstance? PCHInstance = CompileEnvironment.PCHInstance;
while (PCHInstance != null)
{
CompileAction.PrerequisiteItems.Add(PCHInstance.Output.GetPrecompiledHeaderFile(CompileEnvironment.Architecture)!);
CompileAction.PrerequisiteItems.Add(PCHInstance.HeaderFile!);
PCHInstance = PCHInstance.ParentPCHInstance;
}
}
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);
}
FileItem OutputFile;
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
OutputFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ".gch")));
CompileResult.PrecompiledHeaderFile = OutputFile;
PrecompiledHeaderInstance? ParentPCHInstance = CompileEnvironment.ParentPCHInstance;
while (ParentPCHInstance != null)
{
CompileAction.PrerequisiteItems.Add(ParentPCHInstance.Output.GetPrecompiledHeaderFile(CompileEnvironment.Architecture)!);
CompileAction.PrerequisiteItems.Add(ParentPCHInstance.HeaderFile!);
ParentPCHInstance = ParentPCHInstance.ParentPCHInstance;
}
}
else if (IsPreprocessing(CompileEnvironment))
{
OutputFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ".i")));
CompileResult.ObjectFiles.Add(OutputFile);
// Clang does EITHER pre-process or object file.
Arguments.Add("-E"); // Only run the preprocessor
Arguments.Add("-fuse-line-directives"); // Use #line in preprocessed output
}
else if (IsAnalyzing(CompileEnvironment))
{
// Clang analysis does not actually create an object, use the dependency list as the response filename
OutputFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ".d")));
CompileResult.ObjectFiles.Add(OutputFile);
}
else
{
string ObjectFileExtension = (CompileEnvironment.AdditionalArguments != null && CompileEnvironment.AdditionalArguments.Contains("-emit-llvm")) ? ".bc" : ".o";
OutputFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ObjectFileExtension)));
CompileResult.ObjectFiles.Add(OutputFile);
}
// Add profdata as a prerequisite for pgo optimize
if (CompileEnvironment.bPGOOptimize && CompileEnvironment.PGODirectory != null && CompileEnvironment.PGOFilenamePrefix != null)
{
DirectoryReference PGODir = new DirectoryReference(CompileEnvironment.PGODirectory);
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(FileReference.Combine(PGODir, CompileEnvironment.PGOFilenamePrefix)));
}
// FPassPlugin
foreach (string FPassPlugin in CompileEnvironment.FPassPlugins)
{
FileReference? FPassPluginFile = FileReference.FromString(FPassPlugin);
if (FPassPluginFile != null && FileReference.Exists(FPassPluginFile))
{
Arguments.Add($"-fpass-plugin=\"{NormalizeCommandLinePath(FPassPluginFile, CompileEnvironment.RootPaths)}\"");
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(FPassPluginFile));
}
}
// Add the output file to the produced item list.
CompileAction.ProducedItems.Add(OutputFile);
// Add the source file path to the command-line.
Arguments.Add(GetSourceFileArgument(SourceFile, CompileEnvironment));
// Generate the included header dependency list
if (!PreprocessDepends)
{
FileItem DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ".d")));
Arguments.Add(GetDepencenciesListFileArgument(DependencyListFile, CompileEnvironment));
CompileAction.DependencyListFile = DependencyListFile;
CompileAction.ProducedItems.Add(DependencyListFile);
}
if (!IsAnalyzing(CompileEnvironment))
{
// Generate the timing info
if (CompileEnvironment.bPrintTimingInfo)
{
FileItem TraceFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, GetFileNameFromExtension(FileName, ".json")));
Arguments.Add("-ftime-trace");
CompileAction.ProducedItems.Add(TraceFile);
}
// Add the parameters needed to compile the output file to the command-line.
Arguments.Add(GetOutputFileArgument(OutputFile, CompileEnvironment));
}
// Add additional arguments to the argument list, must be the final arguments added
GetCompileArguments_AdditionalArgs(CompileEnvironment, Arguments);
return OutputFile;
}
protected virtual List<string> ExpandResponseFileContents(List<string> ResponseFileContents)
{
// This code is optimized for the scenario where ResponseFile has no variables to expand
List<string> NewList = ResponseFileContents;
for (int I = 0; I != NewList.Count; ++I)
{
string Line = ResponseFileContents[I];
string NewLine = Utils.ExpandVariables(Line);
if (NewLine == Line)
{
continue;
}
lock (ResponseFileContents)
{
if (NewList == ResponseFileContents)
{
NewList = new List<string>(ResponseFileContents);
}
}
NewList[I] = NewLine;
}
return NewList;
}
public override IEnumerable<string> GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
GetCompileArguments_Global(new CppCompileEnvironment(CompileEnvironment), Arguments);
return Arguments;
}
public override IEnumerable<string> GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
GetCompileArguments_CPP(CompileEnvironment, Arguments);
return Arguments;
}
public override IEnumerable<string> GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment)
{
List<string> Arguments = new();
GetCompileArguments_C(CompileEnvironment, Arguments);
return Arguments;
}
public override CppCompileEnvironment CreateSharedResponseFile(CppCompileEnvironment CompileEnvironment, FileReference OutResponseFile, IActionGraphBuilder Graph)
{
CppCompileEnvironment NewCompileEnvironment = new CppCompileEnvironment(CompileEnvironment);
List<string> Arguments = new List<string>();
GetCompileArguments_Global(CompileEnvironment, Arguments);
// Stash shared include paths for validation purposes
NewCompileEnvironment.SharedUserIncludePaths = new(NewCompileEnvironment.UserIncludePaths);
NewCompileEnvironment.SharedSystemIncludePaths = new(NewCompileEnvironment.SystemIncludePaths);
NewCompileEnvironment.UserIncludePaths.Clear();
NewCompileEnvironment.SystemIncludePaths.Clear();
Arguments = ExpandResponseFileContents(Arguments);
FileItem FileItem = FileItem.GetItemByFileReference(OutResponseFile);
Graph.CreateIntermediateTextFile(FileItem, Arguments);
NewCompileEnvironment.AdditionalPrerequisites.Add(FileItem);
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;
}
List<string> GlobalArguments = new();
if (!CompileEnvironment.bHasSharedResponseFile)
{
GetCompileArguments_Global(CompileEnvironment, GlobalArguments);
}
string DummyName = "SingleFile.cpp";
FileItem DummyFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, DummyName));
CPPOutput Result = new();
ClangSpecificFileActionGraphBuilder GraphBuilder = new(Logger);
Action Action = CompileCPPFile(CompileEnvironment, DummyFile, OutputDir, "<Unknown>", GraphBuilder, GlobalArguments, Result);
Action.PrerequisiteItems.RemoveWhere(File => File.Name.Contains(DummyName));
Action.bCanExecuteRemotely = true;
Action.bCanExecuteRemotelyWithSNDBS = Action.bCanExecuteRemotely && !CompileEnvironment.bBuildLocallyWithSNDBS;
Action.Weight = CompileActionWeight;
Graph.AddAction(new ClangSpecificFileAction(SourceDir, OutputDir, Action, GraphBuilder.ContentLines));
}
protected virtual Action CompileCPPFile(CppCompileEnvironment CompileEnvironment, FileItem SourceFile, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph, IReadOnlyCollection<string> GlobalArguments, CPPOutput Result)
{
Action CompileAction = Graph.CreateAction(ActionType.Compile);
CompileAction.RootPaths = CompileEnvironment.RootPaths;
// 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)
{
CompileAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(Info.Clang));
}
CompileAction.Weight = CompileActionWeight;
// copy the global arguments into the file arguments, so GetCompileArguments_FileType can remove entries if needed (special case but can be important)
List<string> FileArguments = new(GlobalArguments);
// Add C or C++ specific compiler arguments and get the target file so we can get the correct output path.
FileItem TargetFile = GetCompileArguments_FileType(CompileEnvironment, SourceFile, OutputDir, FileArguments, CompileAction, Result);
// Creates the path to the response file using the name of the output file and creates its contents.
FileReference ResponseFileName = GetResponseFileName(CompileEnvironment, TargetFile);
List<string> ResponseFileContents = ExpandResponseFileContents(FileArguments);
if (OperatingSystem.IsWindows())
{
// Enables Clang Cmd Line Work Around
// Clang reads the response file and computes the total cmd line length.
// Depending on the length it will then use either cmd line or response file mode.
// Clang currently underestimates the total size of the cmd line, which can trigger the following clang error in cmd line mode:
// >>>> xxx-clang.exe': The filename or extension is too long. (0xCE)
// Clang processes and modifies the response file contents and this makes the final cmd line size hard for us to predict.
// To be conservative we add a dummy define to inflate the response file size and force clang to use the response file mode when we are close to the limit.
int CmdLineLength = Info.Clang.ToString().Length;
foreach (string Line in ResponseFileContents)
{
CmdLineLength += 1 + Line.Length;
}
bool bIsInDangerZone = CmdLineLength >= ClangCmdlineDangerZone && CmdLineLength <= ClangCmdLineMaxSize;
if (bIsInDangerZone)
{
ResponseFileContents.Add(ClangDummyDefine);
}
}
// Adds the response file to the compiler input.
FileItem CompilerResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, ResponseFileContents);
string CommandArguments = GetResponseFileArgument(CompilerResponseFileItem, CompileEnvironment.RootPaths);
if (bMergeModules && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.Create)
{
// EXTRACTEXPORTS can only be interpreted by UBA.. so this action won't build outside uba
CommandArguments += " /EXTRACTEXPORTS";
FileItem SymFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, Path.GetFileName(SourceFile.AbsolutePath) + ".exi"));
CompileAction.ProducedItems.Add(SymFile);
}
CompileAction.CommandArguments = CommandArguments;
CompileAction.PrerequisiteItems.Add(CompilerResponseFileItem);
CompileAction.WorkingDirectory = Unreal.EngineSourceDirectory;
CompileAction.CommandPath = Info.Clang;
CompileAction.CommandVersion = Info.ClangVersionString;
if (bAllowUbaCompression)
{
CompileAction.CommandVersion = $"{CompileAction.CommandVersion} Compressed";
}
CompileAction.CommandDescription = IsPreprocessing(CompileEnvironment) ? "Preprocess" : IsAnalyzing(CompileEnvironment) ? "Analyze" : "Compile";
UnrealArchitectureConfig ArchConfig = UnrealArchitectureConfig.ForPlatform(CompileEnvironment.Platform);
if (ArchConfig.Mode != UnrealArchitectureMode.SingleArchitecture)
{
string ReadableArch = ArchConfig.ConvertToReadableArchitecture(CompileEnvironment.Architecture);
CompileAction.CommandDescription += $" [{ReadableArch}]";
}
CompileAction.StatusDescription = Path.GetFileName(SourceFile.AbsolutePath);
CompileAction.bIsGCCCompiler = true;
// Don't farm out creation of pre-compiled headers as it is the critical path task.
if (CompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
CompileAction.bCanExecuteRemotely = CompileEnvironment.bAllowRemotelyCompiledPCHs;
}
else
{
// Don't farm out preprocess actions since they are more or less only IO
CompileAction.bCanExecuteRemotely = !CompileEnvironment.bPreprocessOnly;
}
CompileAction.ArtifactMode = ArtifactMode.Enabled;
CompileAction.CacheBucket = GetCacheBucket(TargetRules, (!CompileEnvironment.RootPaths.bUseVfs && CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None) ? CompileEnvironment.RootPaths : null);
if (CompileEnvironment.PrecompiledHeaderAction != PrecompiledHeaderAction.None)
{
CompileAction.ArtifactMode |= ArtifactMode.AbsolutePath; // Unfortunately we require matching absolute paths for pch to be cached
}
if (IsAnalyzing(CompileEnvironment) && CompileEnvironment.bWarningsAsErrors)
{
CompileAction.bForceWarningsAsError = true;
CompileAction.bDeleteProducedItemsOnError = true;
}
// Two-pass compile where the preprocessor is run first to output the dependency list
if (PreprocessDepends)
{
Action PrepassAction = Graph.CreateAction(ActionType.Compile);
PrepassAction.RootPaths = CompileAction.RootPaths;
PrepassAction.PrerequisiteItems.UnionWith(CompileAction.PrerequisiteItems);
PrepassAction.PrerequisiteItems.Remove(CompilerResponseFileItem);
PrepassAction.CommandDescription = "Preprocess Depends";
PrepassAction.StatusDescription = CompileAction.StatusDescription;
PrepassAction.bIsGCCCompiler = true;
PrepassAction.bCanExecuteRemotely = false;
PrepassAction.bShouldOutputStatusDescription = true;
PrepassAction.CommandPath = CompileAction.CommandPath;
PrepassAction.CommandVersion = CompileAction.CommandVersion;
PrepassAction.WorkingDirectory = CompileAction.WorkingDirectory;
List<string> PreprocessGlobalArguments = new(GlobalArguments);
List<string> PreprocessFileArguments = new(FileArguments);
PreprocessGlobalArguments.Remove("-c");
FileItem DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, Path.GetFileName(SourceFile.AbsolutePath) + ".d"));
PreprocessFileArguments.Add(GetPreprocessDepencenciesListFileArgument(DependencyListFile, CompileEnvironment));
PrepassAction.DependencyListFile = DependencyListFile;
PrepassAction.ProducedItems.Add(DependencyListFile);
PreprocessFileArguments.Remove("-ftime-trace");
PreprocessFileArguments.Remove(GetOutputFileArgument(CompileAction.ProducedItems.First(), CompileEnvironment));
PrepassAction.DeleteItems.UnionWith(PrepassAction.ProducedItems);
// Gets the target file so we can get the correct output path.
FileItem PreprocessTargetFile = PrepassAction.DependencyListFile;
// Creates the path to the response file using the name of the output file and creates its contents.
FileReference PreprocessResponseFileName = GetResponseFileName(CompileEnvironment, PreprocessTargetFile);
List<string> PreprocessResponseFileContents = new();
PreprocessResponseFileContents.AddRange(PreprocessGlobalArguments);
PreprocessResponseFileContents.AddRange(PreprocessFileArguments);
if (OperatingSystem.IsWindows())
{
int CmdLineLength = Info.Clang.ToString().Length + String.Join(' ', ResponseFileContents).Length;
bool bIsInDangerZone = CmdLineLength >= ClangCmdlineDangerZone && CmdLineLength <= ClangCmdLineMaxSize;
if (bIsInDangerZone)
{
PreprocessResponseFileContents.Add(ClangDummyDefine);
}
}
// Adds the response file to the compiler input.
FileItem PreprocessResponseFileItem = Graph.CreateIntermediateTextFile(PreprocessResponseFileName, PreprocessResponseFileContents);
PrepassAction.PrerequisiteItems.Add(PreprocessResponseFileItem);
PrepassAction.CommandArguments = GetResponseFileArgument(PreprocessResponseFileItem, CompileEnvironment.RootPaths);
CompileAction.DependencyListFile = DependencyListFile;
CompileAction.PrerequisiteItems.Add(DependencyListFile);
}
return CompileAction;
}
protected override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, IEnumerable<FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph)
{
if (ShouldSkipCompile(CompileEnvironment))
{
return new CPPOutput();
}
List<string> GlobalArguments = new();
if (!CompileEnvironment.bHasSharedResponseFile)
{
GetCompileArguments_Global(CompileEnvironment, GlobalArguments);
}
// Create a compile action for each source file.
CPPOutput Result = new CPPOutput();
foreach (FileItem SourceFile in InputFiles)
{
CompileCPPFile(CompileEnvironment, SourceFile, OutputDir, ModuleName, Graph, GlobalArguments, Result);
}
return Result;
}
/// <summary>
/// Used by other tools to get the extra arguments to run vanilla clang for a particular platform.
/// </summary>
/// <param name="ExtraArguments">List of extra arguments to add to.</param>
public virtual void AddExtraToolArguments(IList<string> ExtraArguments)
{
}
}
}