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

692 lines
21 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
/// <summary>
/// Interface for an action that compiles C++ source code
/// </summary>
interface ICppCompileAction : IExternalAction
{
/// <summary>
/// Path to the compiled module interface file
/// </summary>
FileItem? CompiledModuleInterfaceFile { get; }
}
/// <summary>
/// Serializer which creates a portable object file and allows caching it
/// </summary>
class VCCompileAction : ICppCompileAction
{
/// <summary>
/// The action type
/// </summary>
public ActionType ActionType { get; set; } = ActionType.Compile;
/// <summary>
/// Artifact support for this step
/// </summary>
public ArtifactMode ArtifactMode { get; set; } = ArtifactMode.None;
/// <summary>
/// Path to the compiler
/// </summary>
public FileItem CompilerExe { get; }
/// <summary>
/// The type of compiler being used
/// </summary>
public WindowsCompiler CompilerType { get; }
/// <summary>
/// The version of the compiler being used
/// </summary>
public string ToolChainVersion { get; set; }
/// <summary>
/// Source file to compile
/// </summary>
public FileItem? SourceFile { get; set; }
/// <summary>
/// The object file to output
/// </summary>
public FileItem? ObjectFile { get; set; }
/// <summary>
/// The assembly file to output
/// </summary>
public FileItem? AssemblyFile { get; set; }
/// <summary>
/// The output preprocessed file
/// </summary>
public FileItem? PreprocessedFile { get; set; }
/// <summary>
/// The output analyze warning and error log file
/// </summary>
public FileItem? AnalyzeLogFile { get; set; }
/// <summary>
/// The output experimental warning and error log file
/// </summary>
public FileItem? ExperimentalLogFile { get; set; }
/// <summary>
/// The dependency list file
/// </summary>
public FileItem? DependencyListFile { get; set; }
/// <summary>
/// Compiled module interface
/// </summary>
public FileItem? CompiledModuleInterfaceFile { get; set; }
/// <summary>
/// For C++ source files, specifies a timing file used to track timing information.
/// </summary>
public FileItem? TimingFile { get; set; }
/// <summary>
/// Response file for the compiler
/// </summary>
public FileItem? ResponseFile { get; set; }
/// <summary>
/// The precompiled header file
/// </summary>
public FileItem? CreatePchFile { get; set; }
/// <summary>
/// The precompiled header file
/// </summary>
public FileItem? UsingPchFile { get; set; }
/// <summary>
/// The header which matches the PCH
/// </summary>
public FileItem? PchThroughHeaderFile { get; set; }
/// <summary>
/// Response file for the compiler
/// </summary>
public FileItem? VfsOverlayFile { get; set; }
/// <summary>
/// List of additional response paths
/// </summary>
public List<FileItem> AdditionalResponseFiles { get; } = new List<FileItem>();
/// <summary>
/// List of include paths
/// </summary>
public List<DirectoryReference> IncludePaths { get; } = new List<DirectoryReference>();
/// <summary>
/// List of system include paths
/// </summary>
public List<DirectoryReference> SystemIncludePaths { get; } = new List<DirectoryReference>();
/// <summary>
/// List of macro definitions
/// </summary>
public List<string> Definitions { get; } = new List<string>();
/// <summary>
/// List of force included files
/// </summary>
public List<FileItem> ForceIncludeFiles = new List<FileItem>();
/// <summary>
/// Every file this action depends on. These files need to exist and be up to date in order for this action to even be considered
/// </summary>
public List<FileItem> AdditionalPrerequisiteItems { get; } = new List<FileItem>();
/// <summary>
/// The files that this action produces after completing
/// </summary>
public List<FileItem> AdditionalProducedItems { get; } = new List<FileItem>();
/// <summary>
/// Arguments to pass to the compiler
/// </summary>
public List<string> Arguments { get; } = new List<string>();
/// <summary>
/// Whether to show included files to the console
/// </summary>
public bool bShowIncludes { get; set; }
/// <summary>
/// Whether to override the normal logic for UsingClFilter and force it on.
/// </summary>
public bool ForceClFilter = false;
/// <summary>
/// Architecture this is compiling for (used for display)
/// </summary>
public UnrealArch Architecture { get; set; }
/// <summary>
/// Whether this compile is static code analysis (used for display)
/// </summary>
public bool bIsAnalyzing { get; set; }
public bool bWarningsAsErrors { get; set; }
#region Public IAction implementation
/// <summary>
/// Items that should be deleted before running this action
/// </summary>
public List<FileItem> DeleteItems { get; } = new List<FileItem>();
/// <summary>
/// Root paths for this action (generally engine root project root, toolchain root, sdk root)
/// </summary>
public CppRootPaths RootPaths { get; set; }
/// <inheritdoc/>
public bool bCanExecuteRemotely { get; set; }
/// <inheritdoc/>
public bool bCanExecuteRemotelyWithSNDBS { get; set; }
/// <inheritdoc/>
public bool bCanExecuteRemotelyWithXGE { get; set; } = true;
/// <inheritdoc/>
public bool bCanExecuteInUBA { get; set; } = true;
/// <inheritdoc/>
public bool bCanExecuteInUBACrossArchitecture { get; set; } = true;
/// <inheritdoc/>
public bool bUseActionHistory => true;
/// <inheritdoc/>
public bool bIsHighPriority => CreatePchFile != null;
/// <inheritdoc/>
public double Weight { get; set; } = 1.0;
/// <inheritdoc/>
public uint CacheBucket { get; set; }
/// <inheritdoc/>
public bool bShouldOutputLog { get; set; } = true;
#endregion
#region Implementation of IAction
IEnumerable<FileItem> IExternalAction.DeleteItems => DeleteItems;
public DirectoryReference WorkingDirectory => Unreal.EngineSourceDirectory;
string IExternalAction.CommandDescription
{
get
{
if (PreprocessedFile != null)
{
return $"Preprocess [{Architecture}]";
}
else if (bIsAnalyzing)
{
return $"Analyze [{Architecture}]";
}
return $"Compile [{Architecture}]";
}
}
bool IExternalAction.bIsGCCCompiler => false;
bool IExternalAction.bDeleteProducedItemsOnError => CompilerType.IsClang() && bIsAnalyzing && bWarningsAsErrors;
bool IExternalAction.bForceWarningsAsError => CompilerType.IsClang() && bIsAnalyzing && bWarningsAsErrors;
bool IExternalAction.bProducesImportLibrary => false;
string IExternalAction.StatusDescription => (SourceFile == null) ? "Compiling" : SourceFile.Location.GetFileName();
bool IExternalAction.bShouldOutputStatusDescription => CompilerType.IsClang();
/// <inheritdoc/>
IEnumerable<FileItem> IExternalAction.PrerequisiteItems
{
get
{
if (ResponseFile != null)
{
yield return ResponseFile;
}
if (SourceFile != null)
{
yield return SourceFile;
}
if (UsingPchFile != null)
{
yield return UsingPchFile;
}
if (VfsOverlayFile != null)
{
yield return VfsOverlayFile;
}
foreach (FileItem AdditionalResponseFile in AdditionalResponseFiles)
{
yield return AdditionalResponseFile;
}
foreach (FileItem ForceIncludeFile in ForceIncludeFiles)
{
yield return ForceIncludeFile;
}
foreach (FileItem AdditionalPrerequisiteItem in AdditionalPrerequisiteItems)
{
yield return AdditionalPrerequisiteItem;
}
}
}
/// <inheritdoc/>
IEnumerable<FileItem> IExternalAction.ProducedItems
{
get
{
if (ObjectFile != null)
{
yield return ObjectFile;
}
if (AssemblyFile != null)
{
yield return AssemblyFile;
}
if (PreprocessedFile != null)
{
yield return PreprocessedFile;
}
if (AnalyzeLogFile != null)
{
yield return AnalyzeLogFile;
}
if (ExperimentalLogFile != null)
{
yield return ExperimentalLogFile;
}
if (DependencyListFile != null)
{
yield return DependencyListFile;
}
if (TimingFile != null)
{
yield return TimingFile;
}
if (CreatePchFile != null)
{
yield return CreatePchFile;
}
foreach (FileItem AdditionalProducedItem in AdditionalProducedItems)
{
yield return AdditionalProducedItem;
}
}
}
/// <summary>
/// Whether to use cl-filter
/// </summary>
bool UsingClFilter => ForceClFilter || (DependencyListFile != null && !DependencyListFile.HasExtension(".json") && !DependencyListFile.HasExtension(".d"));
/// <inheritdoc/>
FileReference IExternalAction.CommandPath
{
get
{
if (UsingClFilter)
{
return FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "cl-filter", "cl-filter.exe");
}
else
{
return CompilerExe.Location;
}
}
}
/// <inheritdoc/>
string IExternalAction.CommandArguments
{
get
{
if (UsingClFilter)
{
return GetClFilterArguments();
}
else
{
return GetClArguments();
}
}
}
/// <inheritdoc/>
string IExternalAction.CommandVersion => ToolChainVersion;
#endregion
/// <summary>
/// Constructor
/// </summary>
/// <param name="Environment">Compiler executable</param>
public VCCompileAction(VCEnvironment Environment)
{
CompilerExe = FileItem.GetItemByFileReference(Environment.CompilerPath);
CompilerType = Environment.Compiler;
ToolChainVersion = Environment.ToolChainVersion.ToString();
RootPaths = new();
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="InAction">Action to copy from</param>
public VCCompileAction(VCCompileAction InAction)
{
ActionType = InAction.ActionType;
ArtifactMode = InAction.ArtifactMode;
CompilerExe = InAction.CompilerExe;
CompilerType = InAction.CompilerType;
ToolChainVersion = InAction.ToolChainVersion;
SourceFile = InAction.SourceFile;
ObjectFile = InAction.ObjectFile;
AssemblyFile = InAction.AssemblyFile;
PreprocessedFile = InAction.PreprocessedFile;
AnalyzeLogFile = InAction.AnalyzeLogFile;
ExperimentalLogFile = InAction.ExperimentalLogFile;
DependencyListFile = InAction.DependencyListFile;
CompiledModuleInterfaceFile = InAction.CompiledModuleInterfaceFile;
TimingFile = InAction.TimingFile;
ResponseFile = InAction.ResponseFile;
CreatePchFile = InAction.CreatePchFile;
UsingPchFile = InAction.UsingPchFile;
PchThroughHeaderFile = InAction.PchThroughHeaderFile;
VfsOverlayFile = InAction.VfsOverlayFile;
AdditionalResponseFiles = new List<FileItem>(InAction.AdditionalResponseFiles);
IncludePaths = new List<DirectoryReference>(InAction.IncludePaths);
SystemIncludePaths = new List<DirectoryReference>(InAction.SystemIncludePaths);
Definitions = new List<string>(InAction.Definitions);
ForceIncludeFiles = new List<FileItem>(InAction.ForceIncludeFiles);
Arguments = new List<string>(InAction.Arguments);
bShowIncludes = InAction.bShowIncludes;
bCanExecuteRemotely = InAction.bCanExecuteRemotely;
bCanExecuteRemotelyWithSNDBS = InAction.bCanExecuteRemotelyWithSNDBS;
bCanExecuteRemotelyWithXGE = InAction.bCanExecuteRemotelyWithXGE;
Architecture = InAction.Architecture;
bIsAnalyzing = InAction.bIsAnalyzing;
bWarningsAsErrors = InAction.bWarningsAsErrors;
Weight = InAction.Weight;
CacheBucket = InAction.CacheBucket;
AdditionalPrerequisiteItems = new List<FileItem>(InAction.AdditionalPrerequisiteItems);
AdditionalProducedItems = new List<FileItem>(InAction.AdditionalProducedItems);
DeleteItems = new List<FileItem>(InAction.DeleteItems);
RootPaths = new(InAction.RootPaths);
}
/// <summary>
/// Serialize a cache handler from an archive
/// </summary>
/// <param name="Reader">Reader to serialize from</param>
public VCCompileAction(BinaryArchiveReader Reader)
{
ActionType = (ActionType)Reader.ReadInt();
ArtifactMode = (ArtifactMode)Reader.ReadByte();
CompilerExe = Reader.ReadFileItem()!;
CompilerType = (WindowsCompiler)Reader.ReadInt();
ToolChainVersion = Reader.ReadString()!;
SourceFile = Reader.ReadFileItem();
ObjectFile = Reader.ReadFileItem();
AssemblyFile = Reader.ReadFileItem();
PreprocessedFile = Reader.ReadFileItem();
AnalyzeLogFile = Reader.ReadFileItem();
ExperimentalLogFile = Reader.ReadFileItem();
DependencyListFile = Reader.ReadFileItem();
CompiledModuleInterfaceFile = Reader.ReadFileItem();
TimingFile = Reader.ReadFileItem();
ResponseFile = Reader.ReadFileItem();
CreatePchFile = Reader.ReadFileItem();
UsingPchFile = Reader.ReadFileItem();
PchThroughHeaderFile = Reader.ReadFileItem();
VfsOverlayFile = Reader.ReadFileItem();
AdditionalResponseFiles = Reader.ReadList(() => Reader.ReadFileItem())!;
IncludePaths = Reader.ReadList(() => Reader.ReadCompactDirectoryReference())!;
SystemIncludePaths = Reader.ReadList(() => Reader.ReadCompactDirectoryReference())!;
Definitions = Reader.ReadList(() => Reader.ReadString())!;
ForceIncludeFiles = Reader.ReadList(() => Reader.ReadFileItem())!;
Arguments = Reader.ReadList(() => Reader.ReadString())!;
bShowIncludes = Reader.ReadBool();
bCanExecuteRemotely = Reader.ReadBool();
bCanExecuteRemotelyWithSNDBS = Reader.ReadBool();
bCanExecuteRemotelyWithXGE = Reader.ReadBool();
bCanExecuteInUBA = Reader.ReadBool();
bCanExecuteInUBACrossArchitecture = Reader.ReadBool();
Architecture = UnrealArch.Parse(Reader.ReadString()!);
bIsAnalyzing = Reader.ReadBool();
bWarningsAsErrors = Reader.ReadBool();
Weight = Reader.ReadDouble();
CacheBucket = Reader.ReadUnsignedInt();
AdditionalPrerequisiteItems = Reader.ReadList(() => Reader.ReadFileItem())!;
AdditionalProducedItems = Reader.ReadList(() => Reader.ReadFileItem())!;
DeleteItems = Reader.ReadList(() => Reader.ReadFileItem())!;
RootPaths = new(Reader);
}
/// <inheritdoc/>
public void Write(BinaryArchiveWriter Writer)
{
Writer.WriteInt((int)ActionType);
Writer.WriteByte((byte)ArtifactMode);
Writer.WriteFileItem(CompilerExe);
Writer.WriteInt((int)CompilerType);
Writer.WriteString(ToolChainVersion);
Writer.WriteFileItem(SourceFile);
Writer.WriteFileItem(ObjectFile);
Writer.WriteFileItem(AssemblyFile);
Writer.WriteFileItem(PreprocessedFile);
Writer.WriteFileItem(AnalyzeLogFile);
Writer.WriteFileItem(ExperimentalLogFile);
Writer.WriteFileItem(DependencyListFile);
Writer.WriteFileItem(CompiledModuleInterfaceFile);
Writer.WriteFileItem(TimingFile);
Writer.WriteFileItem(ResponseFile);
Writer.WriteFileItem(CreatePchFile);
Writer.WriteFileItem(UsingPchFile);
Writer.WriteFileItem(PchThroughHeaderFile);
Writer.WriteFileItem(VfsOverlayFile);
Writer.WriteList(AdditionalResponseFiles, Item => Writer.WriteFileItem(Item));
Writer.WriteList(IncludePaths, Item => Writer.WriteCompactDirectoryReference(Item));
Writer.WriteList(SystemIncludePaths, Item => Writer.WriteCompactDirectoryReference(Item));
Writer.WriteList(Definitions, Item => Writer.WriteString(Item));
Writer.WriteList(ForceIncludeFiles, Item => Writer.WriteFileItem(Item));
Writer.WriteList(Arguments, Item => Writer.WriteString(Item));
Writer.WriteBool(bShowIncludes);
Writer.WriteBool(bCanExecuteRemotely);
Writer.WriteBool(bCanExecuteRemotelyWithSNDBS);
Writer.WriteBool(bCanExecuteRemotelyWithXGE);
Writer.WriteBool(bCanExecuteInUBA);
Writer.WriteBool(bCanExecuteInUBACrossArchitecture);
Writer.WriteString(Architecture.ToString());
Writer.WriteBool(bIsAnalyzing);
Writer.WriteBool(bWarningsAsErrors);
Writer.WriteDouble(Weight);
Writer.WriteUnsignedInt(CacheBucket);
Writer.WriteList(AdditionalPrerequisiteItems, Item => Writer.WriteFileItem(Item));
Writer.WriteList(AdditionalProducedItems, Item => Writer.WriteFileItem(Item));
Writer.WriteList(DeleteItems, Item => Writer.WriteFileItem(Item));
RootPaths.Write(Writer);
}
/// <summary>
/// Writes the response file with the action's arguments
/// </summary>
/// <param name="Graph">The graph builder</param>
/// <param name="Logger">Logger for output</param>
public void WriteResponseFile(IActionGraphBuilder Graph, ILogger Logger)
{
if (ResponseFile != null)
{
Graph.CreateIntermediateTextFile(ResponseFile, GetCompilerArguments(Logger));
Arguments.Clear();
}
}
public List<string> GetCompilerArguments(ILogger Logger)
{
List<string> Arguments = new List<string>();
if (SourceFile != null)
{
VCToolChain.AddSourceFile(Arguments, SourceFile, RootPaths);
}
foreach (FileItem AdditionalRsp in AdditionalResponseFiles)
{
VCToolChain.AddResponseFile(Arguments, AdditionalRsp, RootPaths);
}
foreach (DirectoryReference IncludePath in IncludePaths)
{
VCToolChain.AddIncludePath(Arguments, IncludePath, CompilerType, RootPaths);
}
foreach (DirectoryReference SystemIncludePath in SystemIncludePaths)
{
VCToolChain.AddSystemIncludePath(Arguments, SystemIncludePath, CompilerType, RootPaths);
}
foreach (string Definition in Definitions)
{
// Escape all quotation marks so that they get properly passed with the command line.
string DefinitionArgument = Definition.Contains('"') ? Definition.Replace("\"", "\\\"") : Definition;
VCToolChain.AddDefinition(Arguments, DefinitionArgument);
}
foreach (FileItem ForceIncludeFile in ForceIncludeFiles)
{
VCToolChain.AddForceIncludeFile(Arguments, ForceIncludeFile, RootPaths);
}
if (CreatePchFile != null)
{
VCToolChain.AddCreatePchFile(Arguments, PchThroughHeaderFile!, CreatePchFile, RootPaths);
}
if (UsingPchFile != null && CompilerType.IsMSVC())
{
VCToolChain.AddUsingPchFile(Arguments, PchThroughHeaderFile!, UsingPchFile, RootPaths);
}
if (PreprocessedFile != null)
{
VCToolChain.AddPreprocessedFile(Arguments, PreprocessedFile, RootPaths);
}
if (ObjectFile != null)
{
VCToolChain.AddObjectFile(Arguments, ObjectFile, RootPaths);
}
if (AssemblyFile != null)
{
VCToolChain.AddAssemblyFile(Arguments, AssemblyFile, RootPaths);
}
if (AnalyzeLogFile != null)
{
VCToolChain.AddAnalyzeLogFile(Arguments, AnalyzeLogFile, RootPaths);
}
if (ExperimentalLogFile != null)
{
VCToolChain.AddExperimentalLogFile(Arguments, ExperimentalLogFile, RootPaths);
}
// A better way to express this? .json is used as output for /sourceDependencies), but .md.json is used as output for /sourceDependencies:directives)
if (DependencyListFile != null && DependencyListFile.HasExtension(".json") && !DependencyListFile.HasExtension(".md.json"))
{
VCToolChain.AddSourceDependenciesFile(Arguments, DependencyListFile, RootPaths);
}
if (DependencyListFile != null && DependencyListFile.HasExtension(".d"))
{
VCToolChain.AddSourceDependsFile(Arguments, DependencyListFile, RootPaths);
}
Arguments.AddRange(this.Arguments);
return Arguments;
}
string GetClArguments()
{
if (ResponseFile == null)
{
return String.Join(' ', Arguments);
}
string ResponseFileString = VCToolChain.NormalizeCommandLinePath(ResponseFile, RootPaths);
// 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(WorkingDirectory.FullName, ResponseFileString).Length > 260)
{
ResponseFileString = ResponseFile.FullName;
}
return String.Format("@{0} {1}", Utils.MakePathSafeToUseWithCommandLine(ResponseFileString), String.Join(' ', Arguments));
}
string GetClFilterArguments()
{
List<string> Arguments = new List<string>();
string DependencyListFileString = VCToolChain.NormalizeCommandLinePath(DependencyListFile!, RootPaths);
Arguments.Add(String.Format("-dependencies={0}", Utils.MakePathSafeToUseWithCommandLine(DependencyListFileString)));
if (TimingFile != null)
{
string TimingFileString = VCToolChain.NormalizeCommandLinePath(TimingFile, RootPaths);
Arguments.Add(String.Format("-timing={0}", Utils.MakePathSafeToUseWithCommandLine(TimingFileString)));
}
if (bShowIncludes)
{
Arguments.Add("-showincludes");
}
Arguments.Add(String.Format("-compiler={0}", Utils.MakePathSafeToUseWithCommandLine(CompilerExe.AbsolutePath)));
Arguments.Add("--");
Arguments.Add(Utils.MakePathSafeToUseWithCommandLine(CompilerExe.AbsolutePath));
Arguments.Add(GetClArguments());
Arguments.Add("/showIncludes");
return String.Join(' ', Arguments);
}
}
/// <summary>
/// Serializer for <see cref="VCCompileAction"/> instances
/// </summary>
class VCCompileActionSerializer : ActionSerializerBase<VCCompileAction>
{
/// <inheritdoc/>
public override VCCompileAction Read(BinaryArchiveReader Reader)
{
return new VCCompileAction(Reader);
}
/// <inheritdoc/>
public override void Write(BinaryArchiveWriter Writer, VCCompileAction Action)
{
Action.Write(Writer);
}
}
}