// 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 { /// /// 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 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; } /// /// Response file for the compiler /// public FileItem? VfsOverlayFile { get; set; } /// /// List of additional response paths /// public List AdditionalResponseFiles { get; } = new List(); /// /// List of include paths /// public List IncludePaths { get; } = new List(); /// /// List of system include paths /// public List SystemIncludePaths { get; } = new List(); /// /// List of macro definitions /// public List Definitions { get; } = new List(); /// /// List of force included files /// public List ForceIncludeFiles = new List(); /// /// 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; } = new List(); /// /// The files that this action produces after completing /// public List AdditionalProducedItems { get; } = new List(); /// /// Arguments to pass to the compiler /// public List Arguments { get; } = new List(); /// /// 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; } = new List(); /// /// 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.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(); /// IEnumerable 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; } } } /// 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 { get { if (UsingClFilter) { return FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "cl-filter", "cl-filter.exe"); } else { return CompilerExe.Location; } } } /// string IExternalAction.CommandArguments { get { if (UsingClFilter) { return GetClFilterArguments(); } else { return GetClArguments(); } } } /// string IExternalAction.CommandVersion => ToolChainVersion; #endregion /// /// Constructor /// /// Compiler executable public VCCompileAction(VCEnvironment Environment) { CompilerExe = FileItem.GetItemByFileReference(Environment.CompilerPath); CompilerType = Environment.Compiler; 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; 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(InAction.AdditionalResponseFiles); IncludePaths = new List(InAction.IncludePaths); SystemIncludePaths = new List(InAction.SystemIncludePaths); Definitions = new List(InAction.Definitions); ForceIncludeFiles = new List(InAction.ForceIncludeFiles); Arguments = new List(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(InAction.AdditionalPrerequisiteItems); AdditionalProducedItems = new List(InAction.AdditionalProducedItems); DeleteItems = new List(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(); 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); } /// 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); } /// /// 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 = new List(); 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 Arguments = new List(); 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); } } /// /// Serializer for instances /// class VCCompileActionSerializer : ActionSerializerBase { /// public override VCCompileAction Read(BinaryArchiveReader Reader) { return new VCCompileAction(Reader); } /// public override void Write(BinaryArchiveWriter Writer, VCCompileAction Action) { Action.Write(Writer); } } }