// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Interface for an action that compiles C++ source code /// interface ICppCompileAction : IExternalAction { /// /// Path to the compiled module interface file /// FileItem? CompiledModuleInterfaceFile { get; } } /// /// Serializer which creates a portable object file and allows caching it /// class VCCompileAction : ICppCompileAction { /// /// The action type /// public ActionType ActionType { get; set; } = ActionType.Compile; /// /// Artifact support for this step /// public ArtifactMode ArtifactMode { get; set; } = ArtifactMode.None; /// /// Path to the compiler /// public FileItem CompilerExe { get; } /// /// The type of compiler being used /// public WindowsCompiler CompilerType { get; } /// /// The version of the compiler being used /// public string CompilerVersion { get; set; } /// /// The version of the toolchain being used (if the compiler is not MSVC) /// public string ToolChainVersion { get; set; } /// /// Source file to compile /// public FileItem? SourceFile { get; set; } /// /// The object file to output /// public FileItem? ObjectFile { get; set; } /// /// The assembly file to output /// public FileItem? AssemblyFile { get; set; } /// /// The output preprocessed file /// public FileItem? PreprocessedFile { get; set; } /// /// The output analyze warning and error log file /// public FileItem? AnalyzeLogFile { get; set; } /// /// The output experimental warning and error log file /// public FileItem? ExperimentalLogFile { get; set; } /// /// The dependency list file /// public FileItem? DependencyListFile { get; set; } /// /// Compiled module interface /// public FileItem? CompiledModuleInterfaceFile { get; set; } /// /// For C++ source files, specifies a timing file used to track timing information. /// public FileItem? TimingFile { get; set; } /// /// Response file for the compiler /// public FileItem? ResponseFile { get; set; } /// /// The precompiled header file /// public FileItem? CreatePchFile { get; set; } /// /// The precompiled header file /// public FileItem? UsingPchFile { get; set; } /// /// The header which matches the PCH /// public FileItem? PchThroughHeaderFile { get; set; } /// /// List of additional response paths /// public List AdditionalResponseFiles { get; } = []; /// /// List of include paths /// public List IncludePaths { get; } = []; /// /// List of system include paths /// public List SystemIncludePaths { get; } = []; /// /// List of macro definitions /// public List Definitions { get; } = []; /// /// List of force included files /// public List ForceIncludeFiles = []; /// /// 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 /// public List AdditionalPrerequisiteItems { get; } = []; /// /// The files that this action produces after completing /// public List AdditionalProducedItems { get; } = []; /// /// Arguments to pass to the compiler /// public List Arguments { get; } = []; /// /// Whether to show included files to the console /// public bool bShowIncludes { get; set; } /// /// Whether to override the normal logic for UsingClFilter and force it on. /// public bool ForceClFilter = false; /// /// Architecture this is compiling for (used for display) /// public UnrealArch Architecture { get; set; } /// /// Whether this compile is static code analysis (used for display) /// public bool bIsAnalyzing { get; set; } public bool bWarningsAsErrors { get; set; } #region Public IAction implementation /// /// Items that should be deleted before running this action /// public List DeleteItems { get; } = []; /// /// Root paths for this action (generally engine root project root, toolchain root, sdk root) /// public CppRootPaths RootPaths { get; set; } /// public bool bCanExecuteRemotely { get; set; } /// public bool bCanExecuteRemotelyWithSNDBS { get; set; } /// public bool bCanExecuteRemotelyWithXGE { get; set; } = true; /// public bool bCanExecuteInUBA { get; set; } = true; /// public bool bCanExecuteInUBACrossArchitecture { get; set; } = true; /// public bool bUseActionHistory => true; /// public bool bIsHighPriority => CreatePchFile != null; /// public double Weight { get; set; } = 1.0; /// public uint CacheBucket { get; set; } /// public bool bShouldOutputLog { get; set; } = true; #endregion #region Implementation of IAction IEnumerable 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.bIsClangCompiler => false; bool IExternalAction.bDeleteProducedItemsOnError => CompilerType.IsClang() && bIsAnalyzing && bWarningsAsErrors; bool IExternalAction.bForceWarningsAsError => CompilerType.IsClang() && bIsAnalyzing && bWarningsAsErrors; bool IExternalAction.bProducesImportLibrary => false; string IExternalAction.StatusDescription => SourceFile?.Location.GetFileName() ?? "Compiling"; bool IExternalAction.bShouldOutputStatusDescription => CompilerType.IsClang(); /// IEnumerable IExternalAction.PrerequisiteItems { get { if (ResponseFile != null) { yield return ResponseFile; } if (SourceFile != null) { yield return SourceFile; } if (UsingPchFile != null) { yield return UsingPchFile; } foreach (FileItem additionalResponseFile in AdditionalResponseFiles) { yield return additionalResponseFile; } foreach (FileItem forceIncludeFile in ForceIncludeFiles) { yield return forceIncludeFile; } foreach (FileItem additionalPrerequisiteItem in AdditionalPrerequisiteItems) { yield return additionalPrerequisiteItem; } } } /// IEnumerable 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; } } } /// /// Whether to use cl-filter /// bool UsingClFilter => ForceClFilter || (DependencyListFile != null && !DependencyListFile.HasExtension(".json") && !DependencyListFile.HasExtension(".d")); /// FileReference IExternalAction.CommandPath => UsingClFilter ? FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "cl-filter", "cl-filter.exe") : CompilerExe.Location; /// string IExternalAction.CommandArguments => UsingClFilter ? GetClFilterArguments() : GetClArguments(); /// string IExternalAction.CommandVersion => CompilerType.IsMSVC() ? CompilerVersion : $"{CompilerType} {CompilerVersion} MSVC {ToolChainVersion}"; #endregion /// /// Constructor /// /// Compiler executable public VCCompileAction(VCEnvironment environment) { CompilerExe = FileItem.GetItemByFileReference(environment.CompilerPath); CompilerType = environment.Compiler; CompilerVersion = environment.CompilerVersion.ToString(); ToolChainVersion = environment.ToolChainVersion.ToString(); RootPaths = new(); } /// /// Copy constructor /// /// Action to copy from public VCCompileAction(VCCompileAction inAction) { ActionType = inAction.ActionType; ArtifactMode = inAction.ArtifactMode; CompilerExe = inAction.CompilerExe; CompilerType = inAction.CompilerType; CompilerVersion = inAction.CompilerVersion; 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; AdditionalResponseFiles = [.. inAction.AdditionalResponseFiles]; IncludePaths = [.. inAction.IncludePaths]; SystemIncludePaths = [.. inAction.SystemIncludePaths]; Definitions = [.. inAction.Definitions]; ForceIncludeFiles = [.. inAction.ForceIncludeFiles]; Arguments = [.. 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 = [.. inAction.AdditionalPrerequisiteItems]; AdditionalProducedItems = [.. inAction.AdditionalProducedItems]; DeleteItems = [.. inAction.DeleteItems]; RootPaths = new(inAction.RootPaths); } /// /// Serialize a cache handler from an archive /// /// Reader to serialize from public VCCompileAction(BinaryArchiveReader reader) { ActionType = (ActionType)reader.ReadInt(); ArtifactMode = (ArtifactMode)reader.ReadByte(); CompilerExe = reader.ReadFileItem()!; CompilerType = (WindowsCompiler)reader.ReadInt(); CompilerVersion = reader.ReadString()!; 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(); 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); } /// public void Write(BinaryArchiveWriter writer) { writer.WriteInt((int)ActionType); writer.WriteByte((byte)ArtifactMode); writer.WriteFileItem(CompilerExe); writer.WriteInt((int)CompilerType); writer.WriteString(CompilerVersion); 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.WriteList(AdditionalResponseFiles, writer.WriteFileItem); writer.WriteList(IncludePaths, writer.WriteCompactDirectoryReference); writer.WriteList(SystemIncludePaths, writer.WriteCompactDirectoryReference); writer.WriteList(Definitions, writer.WriteString); writer.WriteList(ForceIncludeFiles, writer.WriteFileItem); writer.WriteList(Arguments, writer.WriteString); 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, writer.WriteFileItem); writer.WriteList(AdditionalProducedItems, writer.WriteFileItem); writer.WriteList(DeleteItems, writer.WriteFileItem); RootPaths.Write(writer); } /// /// Writes the response file with the action's arguments /// /// The graph builder /// Logger for output public void WriteResponseFile(IActionGraphBuilder graph, ILogger logger) { if (ResponseFile != null) { graph.CreateIntermediateTextFile(ResponseFile, GetCompilerArguments(logger)); Arguments.Clear(); } } public List GetCompilerArguments(ILogger logger) { List arguments = []; if (SourceFile != null) { VCToolChain.AddSourceFile(arguments, SourceFile, RootPaths); } foreach (FileItem additionalResponseFile in AdditionalResponseFiles) { VCToolChain.AddResponseFile(arguments, additionalResponseFile, 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(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 $"@{Utils.MakePathSafeToUseWithCommandLine(responseFileString)} {String.Join(' ', Arguments)}"; } string GetClFilterArguments() { List arguments = []; string dependencyListFileString = VCToolChain.NormalizeCommandLinePath(DependencyListFile!, RootPaths); arguments.Add($"-dependencies={Utils.MakePathSafeToUseWithCommandLine(dependencyListFileString)}"); if (TimingFile != null) { string timingFileString = VCToolChain.NormalizeCommandLinePath(TimingFile, RootPaths); arguments.Add($"-timing={Utils.MakePathSafeToUseWithCommandLine(timingFileString)}"); } if (bShowIncludes) { arguments.Add("-showincludes"); } arguments.Add($"-compiler={Utils.MakePathSafeToUseWithCommandLine(CompilerExe.AbsolutePath)}"); arguments.Add("--"); arguments.Add(Utils.MakePathSafeToUseWithCommandLine(CompilerExe.AbsolutePath)); arguments.Add(GetClArguments()); arguments.Add("/showIncludes"); return String.Join(' ', arguments); } } /// /// Serializer for instances /// class VCCompileActionSerializer : ActionSerializerBase { /// public override VCCompileAction Read(BinaryArchiveReader reader) { return new(reader); } /// public override void Write(BinaryArchiveWriter writer, VCCompileAction action) { action.Write(writer); } } }