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

1343 lines
50 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using EpicGames.Core;
using EpicGames.UHT.Utils;
using Microsoft.Extensions.Logging;
using OpenTracing;
using OpenTracing.Util;
using UnrealBuildBase;
using UnrealBuildTool.Modes;
namespace UnrealBuildTool
{
static class UHTModuleTypeExtensions
{
public static UHTModuleType? EngineModuleTypeFromHostType(ModuleHostType ModuleType)
{
switch (ModuleType)
{
case ModuleHostType.Program:
return UHTModuleType.Program;
case ModuleHostType.Runtime:
case ModuleHostType.RuntimeNoCommandlet:
case ModuleHostType.RuntimeAndProgram:
case ModuleHostType.CookedOnly:
case ModuleHostType.ServerOnly:
case ModuleHostType.ClientOnly:
case ModuleHostType.ClientOnlyNoCommandlet:
return UHTModuleType.EngineRuntime;
case ModuleHostType.Developer:
case ModuleHostType.DeveloperTool:
return UHTModuleType.EngineDeveloper;
case ModuleHostType.Editor:
case ModuleHostType.EditorNoCommandlet:
case ModuleHostType.EditorAndProgram:
return UHTModuleType.EngineEditor;
case ModuleHostType.UncookedOnly:
return UHTModuleType.EngineUncooked;
default:
return null;
}
}
public static UHTModuleType? GameModuleTypeFromHostType(ModuleHostType ModuleType)
{
switch (ModuleType)
{
case ModuleHostType.Program:
return UHTModuleType.Program;
case ModuleHostType.Runtime:
case ModuleHostType.RuntimeNoCommandlet:
case ModuleHostType.RuntimeAndProgram:
case ModuleHostType.CookedOnly:
case ModuleHostType.ServerOnly:
case ModuleHostType.ClientOnly:
case ModuleHostType.ClientOnlyNoCommandlet:
return UHTModuleType.GameRuntime;
case ModuleHostType.Developer:
case ModuleHostType.DeveloperTool:
return UHTModuleType.GameDeveloper;
case ModuleHostType.Editor:
case ModuleHostType.EditorNoCommandlet:
case ModuleHostType.EditorAndProgram:
return UHTModuleType.GameEditor;
case ModuleHostType.UncookedOnly:
return UHTModuleType.GameUncooked;
default:
return null;
}
}
}
/// <summary>
/// Information about a module that needs to be passed to UnrealHeaderTool for code generation
/// </summary>
class UHTModuleInfo
{
/// <summary>
/// Module name
/// </summary>
public string ModuleName;
/// <summary>
/// Path to the module rules file
/// </summary>
public FileReference ModuleRulesFile;
/// <summary>
/// Paths to all potential module source directories (with platform extension directories added in)
/// </summary>
public DirectoryReference[] ModuleDirectories;
/// <summary>
/// The include search path for generated headers to include other headers
/// </summary>
public DirectoryReference[] ModuleIncludePaths;
/// <summary>
/// Module type
/// </summary>
public string ModuleType;
/// <summary>
/// Overridden package module type to add more flags
/// </summary>
public string OverrideModuleType;
/// <summary>
/// Public UObject headers found in the Classes directory (legacy)
/// </summary>
public List<FileItem> PublicUObjectClassesHeaders;
/// <summary>
/// Public headers with UObjects
/// </summary>
public List<FileItem> PublicUObjectHeaders;
/// <summary>
/// Internal headers with UObjects
/// </summary>
public List<FileItem> InternalUObjectHeaders;
/// <summary>
/// Private headers with UObjects
/// </summary>
public List<FileItem> PrivateUObjectHeaders;
/// <summary>
/// Directory containing generated code
/// </summary>
public DirectoryItem GeneratedCodeDirectory;
/// <summary>
/// Collection of the module's public defines
/// </summary>
public List<string> PublicDefines;
/// <summary>
/// Path and base filename, without extension, of the .gen files
/// </summary>
public string? GeneratedCPPFilenameBase;
/// <summary>
/// Version of code generated by UHT
/// </summary>
public EGeneratedCodeVersion GeneratedCodeVersion;
/// <summary>
/// Whether this module is read-only
/// </summary>
public bool bIsReadOnly;
/// <summary>
/// Path of the verse generated types
/// </summary>
public string VersePath;
/// <summary>
/// Scope of the verse definitions
/// </summary>
public VerseScope VerseScope;
/// <summary>
/// Mount point for UE definitions
/// </summary>
public string VerseMountPoint;
/// <summary>
/// If true, verse scripts were found
/// </summary>
public bool HasVerse;
/// <summary>
/// If true, autogenerated functions of USTRUCTS are always exported
/// </summary>
public bool AlwaysExportStructs;
/// <summary>
/// If true, autogenerated functions of UENUMS are always exported
/// </summary>
public bool AlwaysExportEnums;
public UHTModuleInfo(
string ModuleName,
FileReference ModuleRulesFile,
DirectoryReference[] ModuleDirectories,
DirectoryReference[] ModuleIncludePaths,
UHTModuleType ModuleType,
DirectoryItem GeneratedCodeDirectory,
EGeneratedCodeVersion GeneratedCodeVersion,
bool bIsReadOnly,
ModuleRules.PackageOverrideType OverrideType,
string VersePath,
VerseScope VerseScope,
bool HasVerse,
string VerseMountPoint,
bool AlwaysExportStructs,
bool AlwaysExportEnums)
{
this.ModuleName = ModuleName;
this.ModuleRulesFile = ModuleRulesFile;
this.ModuleDirectories = ModuleDirectories;
this.ModuleIncludePaths = ModuleIncludePaths;
this.ModuleType = ModuleType.ToString();
OverrideModuleType = OverrideType.ToString();
PublicUObjectClassesHeaders = new List<FileItem>();
PublicUObjectHeaders = new List<FileItem>();
InternalUObjectHeaders = new List<FileItem>();
PrivateUObjectHeaders = new List<FileItem>();
PublicDefines = new List<string>();
this.GeneratedCodeDirectory = GeneratedCodeDirectory;
this.GeneratedCodeVersion = GeneratedCodeVersion;
this.bIsReadOnly = bIsReadOnly;
this.VersePath = VersePath;
this.VerseScope = VerseScope;
this.HasVerse = HasVerse;
this.VerseMountPoint = VerseMountPoint;
this.AlwaysExportStructs = AlwaysExportStructs;
this.AlwaysExportEnums = AlwaysExportEnums;
}
public UHTModuleInfo(BinaryArchiveReader Reader)
{
ModuleName = Reader.ReadString()!;
ModuleRulesFile = Reader.ReadFileReference();
ModuleDirectories = Reader.ReadArray<DirectoryReference>(Reader.ReadDirectoryReferenceNotNull)!;
ModuleIncludePaths = Reader.ReadArray<DirectoryReference>(Reader.ReadDirectoryReferenceNotNull)!;
ModuleType = Reader.ReadString()!;
OverrideModuleType = Reader.ReadString()!;
PublicUObjectClassesHeaders = Reader.ReadList(() => Reader.ReadFileItem())!;
PublicUObjectHeaders = Reader.ReadList(() => Reader.ReadFileItem())!;
InternalUObjectHeaders = Reader.ReadList(() => Reader.ReadFileItem())!;
PrivateUObjectHeaders = Reader.ReadList(() => Reader.ReadFileItem())!;
GeneratedCPPFilenameBase = Reader.ReadString();
GeneratedCodeDirectory = Reader.ReadDirectoryItem()!;
GeneratedCodeVersion = (EGeneratedCodeVersion)Reader.ReadInt();
bIsReadOnly = Reader.ReadBool();
PublicDefines = Reader.ReadList(() => Reader.ReadString())!;
VersePath = Reader.ReadString()!;
VerseScope = (VerseScope)Reader.ReadInt();
HasVerse = Reader.ReadBool();
VerseMountPoint = Reader.ReadString()!;
AlwaysExportStructs = Reader.ReadBool();
AlwaysExportEnums = Reader.ReadBool();
}
public void Write(BinaryArchiveWriter Writer)
{
Writer.WriteString(ModuleName);
Writer.WriteFileReference(ModuleRulesFile);
Writer.WriteArray<DirectoryReference>(ModuleDirectories, Writer.WriteDirectoryReference);
Writer.WriteArray<DirectoryReference>(ModuleIncludePaths, Writer.WriteDirectoryReference);
Writer.WriteString(ModuleType);
Writer.WriteString(OverrideModuleType);
Writer.WriteList(PublicUObjectClassesHeaders, Item => Writer.WriteFileItem(Item));
Writer.WriteList(PublicUObjectHeaders, Item => Writer.WriteFileItem(Item));
Writer.WriteList(InternalUObjectHeaders, Item => Writer.WriteFileItem(Item));
Writer.WriteList(PrivateUObjectHeaders, Item => Writer.WriteFileItem(Item));
Writer.WriteString(GeneratedCPPFilenameBase);
Writer.WriteDirectoryItem(GeneratedCodeDirectory);
Writer.WriteInt((int)GeneratedCodeVersion);
Writer.WriteBool(bIsReadOnly);
Writer.WriteList(PublicDefines, Item => Writer.WriteString(Item));
Writer.WriteString(VersePath);
Writer.WriteInt((int)VerseScope);
Writer.WriteBool(HasVerse);
Writer.WriteString(VerseMountPoint);
Writer.WriteBool(AlwaysExportStructs);
Writer.WriteBool(AlwaysExportEnums);
}
public override string ToString()
{
return ModuleName;
}
}
class UHTModuleHeaderInfo
{
public DirectoryItem SourceFolder;
public List<FileItem> HeaderFiles;
public bool bUsePrecompiled;
public UHTModuleHeaderInfo(DirectoryItem SourceFolder, List<FileItem> HeaderFiles, bool bUsePrecompiled)
{
this.SourceFolder = SourceFolder;
this.HeaderFiles = HeaderFiles;
this.bUsePrecompiled = bUsePrecompiled;
}
public UHTModuleHeaderInfo(BinaryArchiveReader Reader)
{
SourceFolder = Reader.ReadDirectoryItem()!;
HeaderFiles = Reader.ReadList(() => Reader.ReadFileItem())!;
bUsePrecompiled = Reader.ReadBool();
}
public void Write(BinaryArchiveWriter Writer)
{
Writer.WriteDirectoryItem(SourceFolder);
Writer.WriteList(HeaderFiles, Item => Writer.WriteFileItem(Item));
Writer.WriteBool(bUsePrecompiled);
}
}
/// <summary>
/// This handles all running of the UnrealHeaderTool
/// </summary>
class ExternalExecution
{
static UHTModuleType GetEngineModuleTypeFromDescriptor(ModuleDescriptor Module)
{
UHTModuleType? Type = UHTModuleTypeExtensions.EngineModuleTypeFromHostType(Module.Type);
if (Type == null)
{
throw new BuildException("Unhandled engine module type {0} for {1}", Module.Type.ToString(), Module.Name);
}
return Type.GetValueOrDefault();
}
static UHTModuleType GetGameModuleTypeFromDescriptor(ModuleDescriptor Module)
{
UHTModuleType? Type = UHTModuleTypeExtensions.GameModuleTypeFromHostType(Module.Type);
if (Type == null)
{
throw new BuildException("Unhandled game module type {0}", Module.Type.ToString());
}
return Type.GetValueOrDefault();
}
/// <summary>
/// Return the mount point for the given module
/// </summary>
/// <param name="Module"></param>
/// <returns></returns>
/// <exception cref="BuildException"></exception>
public static string GetVerseMountPointForModule(UEBuildModule Module)
{
if (Module.Rules.Plugin != null)
{
if (Module.bHasVerse && !Module.Rules.Plugin.Descriptor.bCanContainVerse)
{
throw new BuildException("Module '{0}' has an associated Verse directory but its containing plugin '{1}' does not specify \"CanContainVerse\": true.", Module.Name, Module.Rules.Plugin.Name);
}
return Module.Rules.Plugin.Name;
}
// We currently allow Verse only in plugins and programs
if (Module.bHasVerse && Module.Rules.Context.DefaultUHTModuleType.GetValueOrDefault(UHTModuleType.Program) != UHTModuleType.Program)
{
throw new BuildException("Module '{0}' has an associated Verse directory but it is neither in a plugin nor program.", Module.Name);
}
return Module.Rules.Context.Scope.Name == "Project" ? "Game" : "Engine";
}
/// <summary>
/// Gets the module type for a given rules object
/// </summary>
/// <param name="RulesObject">The rules object</param>
/// <param name="ProjectDescriptor">Descriptor for the project being built</param>
/// <returns>The module type</returns>
static UHTModuleType GetModuleType(ModuleRules RulesObject, ProjectDescriptor? ProjectDescriptor)
{
ModuleRulesContext Context = RulesObject.Context;
if (Context.bClassifyAsGameModuleForUHT)
{
if (RulesObject.Type == ModuleRules.ModuleType.External)
{
return UHTModuleType.GameThirdParty;
}
if (Context.DefaultUHTModuleType.HasValue)
{
return Context.DefaultUHTModuleType.Value;
}
if (RulesObject.Plugin != null)
{
ModuleDescriptor? Module = RulesObject.Plugin.Descriptor.Modules?.FirstOrDefault(x => x.Name == RulesObject.Name);
if (Module != null)
{
return GetGameModuleTypeFromDescriptor(Module);
}
}
if (ProjectDescriptor != null && ProjectDescriptor.Modules != null)
{
ModuleDescriptor? Module = ProjectDescriptor.Modules.FirstOrDefault(x => x.Name == RulesObject.Name);
if (Module != null)
{
return UHTModuleTypeExtensions.GameModuleTypeFromHostType(Module.Type) ?? UHTModuleType.GameRuntime;
}
}
return UHTModuleType.GameRuntime;
}
else
{
if (RulesObject.Type == ModuleRules.ModuleType.External)
{
return UHTModuleType.EngineThirdParty;
}
if (Context.DefaultUHTModuleType.HasValue)
{
return Context.DefaultUHTModuleType.Value;
}
if (RulesObject.Plugin != null)
{
string PluginName = !RulesObject.IsTestModule ? RulesObject.Name : TargetDescriptor.GetTestedName(RulesObject.Name);
ModuleDescriptor? Module = RulesObject.Plugin.Descriptor.Modules?.FirstOrDefault(x => x.Name == PluginName);
if (Module != null)
{
return GetEngineModuleTypeFromDescriptor(Module);
}
}
throw new BuildException("Unable to determine UHT module type for {0}", RulesObject.File);
}
}
/// <summary>
/// Find all the headers under the given base directory, excluding any other platform folders.
/// </summary>
/// <param name="BaseDirectory">Base directory to search</param>
/// <param name="ExcludeFolders">Array of folders to exclude</param>
/// <param name="Headers">Receives the list of headers that was found</param>
static void FindHeaders(DirectoryItem BaseDirectory, IReadOnlySet<string> ExcludeFolders, List<FileItem> Headers)
{
if (BaseDirectory.TryGetFile(".ubtignore", out FileItem? OutIgnoreFile))
{
return;
}
// Check for all the headers in this folder
Headers.AddRange(BaseDirectory.EnumerateFiles().Where((fi) => fi.HasExtension(".h")));
foreach (DirectoryItem SubDirectory in BaseDirectory.EnumerateDirectories())
{
if (!ExcludeFolders.Contains(SubDirectory.Name))
{
FindHeaders(SubDirectory, ExcludeFolders, Headers);
}
}
}
public static void SetupUObjectModules(
IEnumerable<UEBuildModuleCPP> ModulesToGenerateHeadersFor,
UnrealTargetPlatform Platform,
ProjectDescriptor? ProjectDescriptor,
List<UHTModuleInfo> UObjectModules,
List<UHTModuleHeaderInfo> UObjectModuleHeaders,
EGeneratedCodeVersion GeneratedCodeVersion,
SourceFileMetadataCache MetadataCache,
ILogger Logger)
{
// Find the type of each module
Dictionary<UEBuildModuleCPP, UHTModuleType> ModuleToType = new Dictionary<UEBuildModuleCPP, UHTModuleType>();
foreach (UEBuildModuleCPP Module in ModulesToGenerateHeadersFor)
{
ModuleToType[Module] = GetModuleType(Module.Rules, ProjectDescriptor);
}
// Sort modules by type, then by dependency
List<UEBuildModuleCPP> ModulesSortedByType = ModulesToGenerateHeadersFor.OrderBy(c => ModuleToType[c]).ToList();
ModulesSortedByType = UEBuildModule.StableTopologicalSort(ModulesSortedByType.Cast<UEBuildModule>().ToList()).Cast<UEBuildModuleCPP>().ToList();
// Create the info for each module in parallel
UHTModuleInfo[] ModuleInfoArray = new UHTModuleInfo[ModulesSortedByType.Count];
using (ThreadPoolWorkQueue Queue = new ThreadPoolWorkQueue())
{
IReadOnlySet<string> ExcludedFolders = UEBuildPlatform.GetBuildPlatform(Platform).GetExcludedFolderNames();
for (int Idx = 0; Idx < ModulesSortedByType.Count; Idx++)
{
UEBuildModuleCPP Module = ModulesSortedByType[Idx];
DirectoryItem GeneratedCodeDirectory = DirectoryItem.GetItemByDirectoryReference(Module.GeneratedCodeDirectoryUHT!);
DirectoryReference[] ModuleIncludePaths = Module.PublicIncludePaths.Union(Module.InternalIncludePaths).Union(Module.PrivateIncludePaths).ToArray();
UHTModuleInfo Info = new UHTModuleInfo(
Module.Name,
Module.RulesFile,
Module.ModuleDirectories,
ModuleIncludePaths,
ModuleToType[Module],
GeneratedCodeDirectory,
GeneratedCodeVersion,
Module.Rules.bUsePrecompiled,
Module.Rules.OverridePackageType,
Module.Rules.VersePath ?? "",
Module.Rules.VerseScope,
Module.bHasVerse,
Module.bHasVerse ? GetVerseMountPointForModule(Module) : string.Empty,
Module.Rules.bAlwaysExportStructs,
Module.Rules.bAlwaysExportEnums);
ModuleInfoArray[Idx] = Info;
Queue.Enqueue(() => SetupUObjectModule(Info, ExcludedFolders, MetadataCache, Queue));
}
}
// Filter out all the modules with reflection data
for (int Idx = 0; Idx < ModulesSortedByType.Count; Idx++)
{
UEBuildModuleCPP Module = ModulesSortedByType[Idx];
UHTModuleInfo Info = ModuleInfoArray[Idx];
Info.PublicDefines.AddRange(Module.PublicDefinitions);
if (Info.PublicUObjectClassesHeaders.Count > 0 || Info.PrivateUObjectHeaders.Count > 0 || Info.PublicUObjectHeaders.Count > 0 || Info.InternalUObjectHeaders.Count > 0)
{
Module.bHasUObjects = true;
// If we've got this far and there are no source files then it's likely we're installed and ignoring
// engine files, so we don't need a .gen.cpp either
DirectoryReference GeneratedCodeDirectoryUHT = Module.GeneratedCodeDirectoryUHT!;
Info.GeneratedCPPFilenameBase = Path.Combine(GeneratedCodeDirectoryUHT.FullName, Info.ModuleName) + ".gen";
if (!Module.Rules.bUsePrecompiled)
{
Module.GeneratedCppDirectories ??= new List<string>();
Module.GeneratedCppDirectories.Add(GeneratedCodeDirectoryUHT.FullName);
}
UObjectModules.Add(Info);
DirectoryItem ModuleDirectoryItem = DirectoryItem.GetItemByDirectoryReference(Module.ModuleDirectory);
List<FileItem> ReflectedHeaderFiles = new List<FileItem>();
ReflectedHeaderFiles.AddRange(Info.PublicUObjectClassesHeaders);
ReflectedHeaderFiles.AddRange(Info.PublicUObjectHeaders);
ReflectedHeaderFiles.AddRange(Info.InternalUObjectHeaders);
ReflectedHeaderFiles.AddRange(Info.PrivateUObjectHeaders);
UObjectModuleHeaders.Add(new UHTModuleHeaderInfo(ModuleDirectoryItem, ReflectedHeaderFiles, Module.Rules.bUsePrecompiled));
}
else
{
// Remove any stale generated code directory
if (Module.GeneratedCodeDirectoryUHT != null && !Module.Rules.bUsePrecompiled)
{
if (DirectoryReference.Exists(Module.GeneratedCodeDirectoryUHT))
{
Directory.Delete(Module.GeneratedCodeDirectoryUHT.FullName, true);
// Also delete parent directory if now empty
if (!Directory.EnumerateFileSystemEntries(Module.GeneratedCodeDirectory!.FullName).Any())
{
Directory.Delete(Module.GeneratedCodeDirectory!.FullName, true);
}
}
}
}
}
// Set Module.bHasUObjects for any IncludePathModules not already processed.
// This is necessary to keep include paths consistent between targets built with -AllModules and without
// Note that PublicIncludePathModules can be recursive so we need to traverse the entire chain
List<UEBuildModuleCPP> IncludePathModules = new();
CollectModulesOnlyIncluded(ModulesToGenerateHeadersFor, IncludePathModules);
if (IncludePathModules.Count > 0)
{
Dictionary<UEBuildModuleCPP, UHTModuleInfo> IncludePathInfo = new();
using (ThreadPoolWorkQueue Queue = new ThreadPoolWorkQueue())
{
IReadOnlySet<string> ExcludedFolders = UEBuildPlatform.GetBuildPlatform(Platform).GetExcludedFolderNames();
foreach (UEBuildModuleCPP Module in IncludePathModules)
{
Queue.Enqueue(() =>
{
foreach (DirectoryItem ModuleDirectoryItem in Module.ModuleDirectories.Select(x => DirectoryItem.GetItemByDirectoryReference(x)))
{
List<FileItem> HeaderFiles = new();
FindHeaders(ModuleDirectoryItem, ExcludedFolders, HeaderFiles);
if (HeaderFiles.Any(x => MetadataCache.ContainsReflectionMarkup(x)))
{
Module.bHasUObjects = true;
break;
}
}
});
}
}
}
}
static void CollectModulesOnlyIncluded(HashSet<UEBuildModuleCPP> HandledModules, List<UEBuildModule> IncludedModules, List<UEBuildModuleCPP> OutList)
{
foreach (UEBuildModule pi in IncludedModules)
{
if (pi is UEBuildModuleCPP IncludedModule)
{
if (IncludedModule.bHasUObjects)
{
continue;
}
if (!HandledModules.Add(IncludedModule))
{
continue;
}
OutList.Add(IncludedModule);
if (IncludedModule.PublicIncludePathModules != null)
{
CollectModulesOnlyIncluded(HandledModules, IncludedModule.PublicIncludePathModules, OutList);
}
}
}
}
static void CollectModulesOnlyIncluded(IEnumerable<UEBuildModuleCPP> ModulesToGenerateHeadersFor, List<UEBuildModuleCPP> OutList)
{
HashSet<UEBuildModuleCPP> HandledModules = new();
foreach (UEBuildModuleCPP Module in ModulesToGenerateHeadersFor)
{
HandledModules.Add(Module);
}
foreach (UEBuildModuleCPP Module in ModulesToGenerateHeadersFor)
{
if (Module.PublicIncludePathModules != null)
{
CollectModulesOnlyIncluded(HandledModules, Module.PublicIncludePathModules, OutList);
}
if (Module.PrivateIncludePathModules != null)
{
CollectModulesOnlyIncluded(HandledModules, Module.PrivateIncludePathModules, OutList);
}
}
}
static void SetupUObjectModule(UHTModuleInfo ModuleInfo, IReadOnlySet<string> ExcludedFolders, SourceFileMetadataCache MetadataCache, ThreadPoolWorkQueue Queue)
{
foreach (DirectoryReference ModuleDirectory in ModuleInfo.ModuleDirectories)
{
DirectoryItem ModuleDirectoryItem = DirectoryItem.GetItemByDirectoryReference(ModuleDirectory);
List<FileItem> HeaderFiles = new List<FileItem>();
FindHeaders(ModuleDirectoryItem, ExcludedFolders, HeaderFiles);
foreach (FileItem HeaderFile in HeaderFiles)
{
Queue.Enqueue(() => SetupUObjectModuleHeader(ModuleInfo, HeaderFile, MetadataCache));
}
}
}
static void SetupUObjectModuleHeader(UHTModuleInfo ModuleInfo, FileItem HeaderFile, SourceFileMetadataCache MetadataCache)
{
// Check to see if we know anything about this file. If we have up-to-date cached information about whether it has
// UObjects or not, we can skip doing a test here.
if (MetadataCache.ContainsReflectionMarkup(HeaderFile))
{
lock (ModuleInfo)
{
bool bFoundHeaderLocation = false;
foreach (DirectoryReference ModuleDirectory in ModuleInfo.ModuleDirectories)
{
if (HeaderFile.Location.IsUnderDirectory(DirectoryReference.Combine(ModuleDirectory, "Classes")))
{
ModuleInfo.PublicUObjectClassesHeaders.Add(HeaderFile);
bFoundHeaderLocation = true;
}
else if (HeaderFile.Location.IsUnderDirectory(DirectoryReference.Combine(ModuleDirectory, "Public")))
{
ModuleInfo.PublicUObjectHeaders.Add(HeaderFile);
bFoundHeaderLocation = true;
}
else if (HeaderFile.Location.IsUnderDirectory(DirectoryReference.Combine(ModuleDirectory, "Internal")))
{
ModuleInfo.InternalUObjectHeaders.Add(HeaderFile);
bFoundHeaderLocation = true;
}
}
if (!bFoundHeaderLocation)
{
ModuleInfo.PrivateUObjectHeaders.Add(HeaderFile);
}
}
}
}
/// <summary>
/// Gets UnrealHeaderTool.exe path. Does not care if UnrealheaderTool was build as a monolithic exe or not.
/// </summary>
static FileReference GetHeaderToolPath(FileReference ReceiptFile)
{
TargetReceipt Receipt = TargetReceipt.Read(ReceiptFile);
if (Receipt.Launch == null)
{
throw new BuildException("'Launch' property not set in UHT receipt.");
}
return Receipt.Launch;
}
/// <summary>
/// Gets the latest write time of any of the UnrealHeaderTool binaries (including DLLs and Plugins) or DateTime.MaxValue if UnrealHeaderTool does not exist
/// </summary>
/// <returns>Latest timestamp of UHT binaries or DateTime.MaxValue if UnrealHeaderTool is out of date and needs to be rebuilt.</returns>
static bool GetHeaderToolTimestampUtc(FileReference ReceiptPath, ILogger Logger, out DateTime Timestamp)
{
using (ScopedTimer TimestampTimer = new ScopedTimer("GetHeaderToolTimestamp", Logger))
{
// Try to read the receipt for UHT.
FileItem ReceiptFile = FileItem.GetItemByFileReference(ReceiptPath);
if (!ReceiptFile.Exists)
{
Timestamp = DateTime.MaxValue;
return false;
}
// Don't check timestamps for individual binaries if we're using the installed version of UHT. It will always be up to date.
if (!Unreal.IsFileInstalled(ReceiptFile.Location))
{
TargetReceipt? Receipt;
if (!TargetReceipt.TryRead(ReceiptPath, out Receipt))
{
Timestamp = DateTime.MaxValue;
return false;
}
// Make sure all the build products exist, and that the receipt is newer
foreach (BuildProduct BuildProduct in Receipt.BuildProducts)
{
FileItem BuildProductItem = FileItem.GetItemByFileReference(BuildProduct.Path);
if (!BuildProductItem.Exists || BuildProductItem.LastWriteTimeUtc > ReceiptFile.LastWriteTimeUtc)
{
Timestamp = DateTime.MaxValue;
return false;
}
}
}
// Return the timestamp for all the binaries
Timestamp = ReceiptFile.LastWriteTimeUtc;
return true;
}
}
/// <summary>
/// Gets the timestamp of CoreUObject.gen.cpp file.
/// </summary>
/// <returns>Last write time of CoreUObject.gen.cpp or DateTime.MaxValue if it doesn't exist.</returns>
private static DateTime GetCoreGeneratedTimestampUtc(string ModuleName, string ModuleGeneratedCodeDirectory)
{
// In Installed Builds, we don't check the timestamps on engine headers. Default to a very old date.
if (Unreal.IsEngineInstalled())
{
return DateTime.MinValue;
}
// Otherwise look for CoreUObject.init.gen.cpp
FileInfo CoreGeneratedFileInfo = new FileInfo(Path.Combine(ModuleGeneratedCodeDirectory, ModuleName + ".init.gen.cpp"));
if (CoreGeneratedFileInfo.Exists)
{
return CoreGeneratedFileInfo.LastWriteTimeUtc;
}
// Doesn't exist, so use a 'newer that everything' date to force rebuild headers.
return DateTime.MaxValue;
}
/// <summary>
/// Gets the timestamp of the Version.h header file. Throws an exception if not found.
/// </summary>
/// <returns>Last write time of Version.h</returns>
private static DateTime GetEngineVersionHeaderTimestampUtc()
{
// In Installed Builds, we don't check the timestamps on engine headers. Default to a very old date.
if (Unreal.IsEngineInstalled())
{
return DateTime.MinValue;
}
string EngineVersionHeaderPath = Path.Combine(Unreal.EngineSourceDirectory.FullName, "Runtime", "Launch", "Resources", "Version.h");
FileInfo EngineVersionHeaderFileInfo = new(EngineVersionHeaderPath);
if (EngineVersionHeaderFileInfo.Exists)
{
return EngineVersionHeaderFileInfo.LastWriteTimeUtc;
}
// The Version.h header doesn't exist somehow (maybe it was moved).
throw new BuildException("Could not find engine version header, expected: {0}", EngineVersionHeaderPath);
}
/// <summary>
/// Checks the class header files and determines if generated UObject code files are out of date in comparison.
/// </summary>
/// <param name="BuildConfiguration">Build configuration</param>
/// <param name="UObjectModules">Modules that we generate headers for</param>
/// <param name="HeaderToolTimestampUtc">Timestamp for UHT</param>
/// <param name="Logger">Logger for output</param>
/// <returns>True if the code files are out of date</returns>
private static bool AreGeneratedCodeFilesOutOfDate(BuildConfiguration BuildConfiguration, List<UHTModuleInfo> UObjectModules, DateTime HeaderToolTimestampUtc, ILogger Logger)
{
// Get CoreUObject.init.gen.cpp timestamp. If the source files are older than the CoreUObject generated code, we'll
// need to regenerate code for the module
DateTime? CoreGeneratedTimestampUtc = null;
{
// Find the CoreUObject module
foreach (UHTModuleInfo Module in UObjectModules)
{
if (Module.ModuleName.Equals("CoreUObject", StringComparison.InvariantCultureIgnoreCase))
{
CoreGeneratedTimestampUtc = GetCoreGeneratedTimestampUtc(Module.ModuleName, Path.GetDirectoryName(Module.GeneratedCPPFilenameBase)!);
break;
}
}
if (CoreGeneratedTimestampUtc == null)
{
throw new BuildException("Could not find CoreUObject in list of all UObjectModules");
}
}
// Also get the Version.h timestamp. If it is newer than the last UHT run, it could affect preprocessor conditionals
// that check for the engine version
DateTime EngineVersionHeaderTimestampUtc = GetEngineVersionHeaderTimestampUtc();
foreach (UHTModuleInfo Module in UObjectModules)
{
// If we're using a precompiled engine, skip checking timestamps for modules that are under the engine directory
if (Module.bIsReadOnly)
{
continue;
}
// Make sure we have an existing folder for generated code. If not, then we definitely need to generate code!
string GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase)!;
FileSystemInfo TestDirectory = (FileSystemInfo)new DirectoryInfo(GeneratedCodeDirectory);
if (!TestDirectory.Exists)
{
// Generated code directory is missing entirely!
Logger.LogDebug("UnrealHeaderTool needs to run because no generated code directory was found for module {ModuleName}", Module.ModuleName);
return true;
}
// Grab our special "Timestamp" file that we saved after the last set of headers were generated. This file
// contains the list of source files which contained UObjects, so that we can compare to see if any
// UObject source files were deleted (or no longer contain UObjects), which means we need to run UHT even
// if no other source files were outdated
string TimestampFile = Path.Combine(GeneratedCodeDirectory, @"Timestamp");
FileSystemInfo SavedTimestampFileInfo = (FileSystemInfo)new FileInfo(TimestampFile);
if (!SavedTimestampFileInfo.Exists)
{
// Timestamp file was missing (possibly deleted/cleaned), so headers are out of date
Logger.LogDebug("UnrealHeaderTool needs to run because UHT Timestamp file did not exist for module {ModuleName}", Module.ModuleName);
return true;
}
// Make sure the last UHT run completed after UnrealHeaderTool.exe was compiled last, and after the CoreUObject headers were touched last.
DateTime SavedTimestampUtc = SavedTimestampFileInfo.LastWriteTimeUtc;
if (HeaderToolTimestampUtc > SavedTimestampUtc)
{
// Generated code is older than UnrealHeaderTool.exe. Out of date!
Logger.LogDebug("UnrealHeaderTool needs to run because UnrealHeaderTool timestamp ({Time}) is later than timestamp for module {ModuleName} ({ModuleTime})", HeaderToolTimestampUtc.ToLocalTime(), Module.ModuleName, SavedTimestampUtc.ToLocalTime());
return true;
}
if (CoreGeneratedTimestampUtc > SavedTimestampUtc)
{
// Generated code is older than CoreUObject headers. Out of date!
Logger.LogDebug("UnrealHeaderTool needs to run because CoreUObject timestamp ({Time}) is newer than timestamp for module {ModuleName} ({ModuleTime})", CoreGeneratedTimestampUtc.Value.ToLocalTime(), Module.ModuleName, SavedTimestampUtc.ToLocalTime());
return true;
}
if (EngineVersionHeaderTimestampUtc > SavedTimestampUtc)
{
// Generated code is older than engine version header. Out of date!
Logger.LogDebug("UnrealHeaderTool needs to run because engine version header timestamp ({Time}) is newer than timestamp for module {ModuleName} ({ModuleTime})", EngineVersionHeaderTimestampUtc.ToLocalTime(), Module.ModuleName, SavedTimestampUtc.ToLocalTime());
return true;
}
// Has the .build.cs file changed since we last generated headers successfully?
FileInfo ModuleRulesFile = new FileInfo(Module.ModuleRulesFile.FullName);
if (!ModuleRulesFile.Exists || ModuleRulesFile.LastWriteTimeUtc > SavedTimestampUtc)
{
Logger.LogDebug("UnrealHeaderTool needs to run because SavedTimestamp is older than the rules file ({ModuleModuleRulesFile}) for module {ModuleModuleName}", Module.ModuleRulesFile, Module.ModuleName);
return true;
}
// Iterate over our UObjects headers and figure out if any of them have changed
List<FileItem> AllUObjectHeaders = new List<FileItem>();
AllUObjectHeaders.AddRange(Module.PublicUObjectClassesHeaders);
AllUObjectHeaders.AddRange(Module.PublicUObjectHeaders);
AllUObjectHeaders.AddRange(Module.InternalUObjectHeaders);
AllUObjectHeaders.AddRange(Module.PrivateUObjectHeaders);
// Load up the old timestamp file and check to see if anything has changed
{
string[] UObjectFilesFromPreviousRun = File.ReadAllLines(TimestampFile);
if (AllUObjectHeaders.Count != UObjectFilesFromPreviousRun.Length)
{
Logger.LogDebug("UnrealHeaderTool needs to run because there are a different number of UObject source files in module {ModuleModuleName}", Module.ModuleName);
return true;
}
// Iterate over our UObjects headers and figure out if any of them have changed
HashSet<string> ObjectHeadersSet = new HashSet<string>(AllUObjectHeaders.Select(x => x.AbsolutePath), FileReference.Comparer);
foreach (string FileName in UObjectFilesFromPreviousRun)
{
if (!ObjectHeadersSet.Contains(FileName))
{
Logger.LogDebug("UnrealHeaderTool needs to run because the set of UObject source files in module {ModuleModuleName} has changed ({FileName})", Module.ModuleName, FileName);
return true;
}
}
}
foreach (FileItem HeaderFile in AllUObjectHeaders)
{
DateTime HeaderFileTimestampUtc = HeaderFile.LastWriteTimeUtc;
// Has the source header changed since we last generated headers successfully?
if (HeaderFileTimestampUtc > SavedTimestampUtc)
{
Logger.LogDebug("UnrealHeaderTool needs to run because SavedTimestamp is older than HeaderFileTimestamp ({HeaderFileAbsolutePath}) for module {ModuleModuleName}", HeaderFile.AbsolutePath, Module.ModuleName);
return true;
}
}
}
return false;
}
/// <summary>
/// Determines if any external dependencies for generated code is out of date
/// </summary>
/// <param name="ExternalDependenciesFile">Path to the external dependencies file</param>
/// <returns>True if any external dependencies are out of date</returns>
private static bool AreExternalDependenciesOutOfDate(FileReference ExternalDependenciesFile)
{
if (!FileReference.Exists(ExternalDependenciesFile))
{
return true;
}
DateTime LastWriteTime = File.GetLastWriteTimeUtc(ExternalDependenciesFile.FullName);
string[] Lines = File.ReadAllLines(ExternalDependenciesFile.FullName);
foreach (string Line in Lines)
{
string ExternalDependencyFile = Line.Trim();
if (ExternalDependencyFile.Length > 0)
{
if (!File.Exists(ExternalDependencyFile) || File.GetLastWriteTimeUtc(ExternalDependencyFile) > LastWriteTime)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Updates the recorded timestamps of all the passed in UObject modules
/// </summary>
private static void UpdateTimestamps(List<UHTModuleInfo> UObjectModules)
{
Parallel.ForEach(UObjectModules, async Module =>
{
if (!Module.bIsReadOnly)
{
string GeneratedCodeDirectory = Path.GetDirectoryName(Module.GeneratedCPPFilenameBase)!;
DirectoryInfo GeneratedCodeDirectoryInfo = new DirectoryInfo(GeneratedCodeDirectory);
try
{
if (GeneratedCodeDirectoryInfo.Exists)
{
FileReference TimestampFile = FileReference.Combine(new DirectoryReference(GeneratedCodeDirectoryInfo.FullName), "Timestamp");
// Save all of the UObject files to a timestamp file. We'll load these on the next run to see if any new
// files with UObject classes were deleted, so that we'll know to run UHT even if the timestamps of all
// of the other source files were unchanged
{
List<string> AllUObjectFiles = new List<string>();
AllUObjectFiles.AddRange(Module.PublicUObjectClassesHeaders.ConvertAll(Item => Item.AbsolutePath));
AllUObjectFiles.AddRange(Module.PublicUObjectHeaders.ConvertAll(Item => Item.AbsolutePath));
AllUObjectFiles.AddRange(Module.InternalUObjectHeaders.ConvertAll(Item => Item.AbsolutePath));
AllUObjectFiles.AddRange(Module.PrivateUObjectHeaders.ConvertAll(Item => Item.AbsolutePath));
await FileReference.WriteAllLinesAsync(TimestampFile, AllUObjectFiles);
}
// Because new .cpp and .h files may have been generated by UHT, invalidate the DirectoryLookupCache
DirectoryLookupCache.InvalidateCachedDirectory(new DirectoryReference(GeneratedCodeDirectoryInfo.FullName));
}
}
catch (Exception Exception)
{
throw new BuildException(Exception, "Couldn't write Timestamp file: " + Exception.Message);
}
}
});
}
/// <summary>
/// Run an external native executable (and capture the output), given the executable path and the commandline.
/// </summary>
public static int RunExternalNativeExecutable(FileReference ExePath, string Commandline, ILogger Logger)
{
Logger.LogDebug("RunExternalExecutable {ExePathFullName} {Commandline}", ExePath.FullName, Commandline);
using (LogEventParser Parser = new LogEventParser(Logger))
{
Parser.AddMatchersFromAssembly(Assembly.GetExecutingAssembly());
using (Process GameProcess = new Process())
{
GameProcess.StartInfo.FileName = ExePath.FullName;
GameProcess.StartInfo.Arguments = Commandline;
GameProcess.StartInfo.UseShellExecute = false;
GameProcess.StartInfo.RedirectStandardOutput = true;
GameProcess.OutputDataReceived += (s, e) => PrintProcessOutputAsync(s, e, Parser);
GameProcess.Start();
GameProcess.BeginOutputReadLine();
GameProcess.WaitForExit();
return GameProcess.ExitCode;
}
}
}
/// <summary>
/// Simple function to pipe output asynchronously
/// </summary>
private static void PrintProcessOutputAsync(object Sender, DataReceivedEventArgs Event, LogEventParser Parser)
{
// DataReceivedEventHandler is fired with a null string when the output stream is closed. We don't want to
// print anything for that event.
if (!String.IsNullOrEmpty(Event.Data))
{
Parser.WriteLine(Event.Data);
}
}
/// <summary>
/// Builds and runs the header tool and touches the header directories.
/// Performs any early outs if headers need no changes, given the UObject modules, tool path, game name, and configuration
/// </summary>
public static async Task ExecuteHeaderToolIfNecessaryAsync(BuildConfiguration BuildConfiguration, FileReference? ProjectFile, TargetMakefile Makefile, string TargetName, ISourceFileWorkingSet WorkingSet, ILogger Logger)
{
// No need to run UHT on itself
if (TargetName.Equals("UnrealHeaderTool", StringComparison.InvariantCultureIgnoreCase))
{
Logger.LogInformation("DEPRECATED: C++ UHT is being built by request. C++ UHT has been deprecated and will be removed in 5.2");
return;
}
await ExecuteHeaderToolIfNecessaryInternalAsync(BuildConfiguration, ProjectFile, Makefile, TargetName, WorkingSet, Logger);
}
/// <summary>
/// Return the file reference for the UHT manifest file
/// </summary>
/// <param name="Makefile">Input makefile</param>
/// <param name="TargetName">Name of the target</param>
/// <returns>Manifest file name</returns>
public static FileReference GetUHTModuleInfoFileName(TargetMakefile Makefile, string TargetName)
{
return FileReference.Combine(Makefile.ProjectIntermediateDirectoryNoArch, TargetName + ".uhtmanifest");
}
/// <summary>
/// Return the file reference for the UHT deps file
/// </summary>
/// <param name="ModuleInfoFileName">Manifest info file name</param>
/// <returns>UHT dependency file name</returns>
public static FileReference GetUHTDepsFileName(FileReference ModuleInfoFileName)
{
return ModuleInfoFileName.ChangeExtension(".deps");
}
/// <summary>
/// Convert the makefile to a UHTManifest object
/// </summary>
/// <param name="Makefile">Input makefile</param>
/// <param name="TargetName">Name of the target</param>
/// <param name="DepsFileName">Name of the dependencies file.</param>
/// <returns>Output UHT manifest</returns>
public static UHTManifest CreateUHTManifest(TargetMakefile Makefile, string TargetName, FileReference DepsFileName)
{
List<UHTManifest.Module> Modules = new List<UHTManifest.Module>();
foreach (UHTModuleInfo UObjectModule in Makefile.UObjectModules)
{
Modules.Add(
new UHTManifest.Module
{
Name = UObjectModule.ModuleName,
ModuleType = (UHTModuleType)Enum.Parse(typeof(UHTModuleType), UObjectModule.ModuleType),
OverrideModuleType = (EPackageOverrideType)Enum.Parse(typeof(EPackageOverrideType), UObjectModule.OverrideModuleType),
BaseDirectory = UObjectModule.ModuleDirectories[0].FullName,
IncludePaths = UObjectModule.ModuleIncludePaths.Select(IncludePath => IncludePath.FullName).ToList(),
OutputDirectory = Path.GetDirectoryName(UObjectModule.GeneratedCPPFilenameBase)!,
ClassesHeaders = UObjectModule.PublicUObjectClassesHeaders.Select((Header) => Header.AbsolutePath).ToList(),
PublicHeaders = UObjectModule.PublicUObjectHeaders.Select((Header) => Header.AbsolutePath).ToList(),
InternalHeaders = UObjectModule.InternalUObjectHeaders.Select((Header) => Header.AbsolutePath).ToList(),
PrivateHeaders = UObjectModule.PrivateUObjectHeaders.Select((Header) => Header.AbsolutePath).ToList(),
PublicDefines = UObjectModule.PublicDefines,
GeneratedCPPFilenameBase = UObjectModule.GeneratedCPPFilenameBase,
SaveExportedHeaders = !UObjectModule.bIsReadOnly,
GeneratedCodeVersion = UObjectModule.GeneratedCodeVersion,
VersePath = UObjectModule.VersePath,
VerseScope = (UHTVerseScope)Enum.Parse(typeof(UHTVerseScope), UObjectModule.VerseScope.ToString()),
HasVerse = UObjectModule.HasVerse,
VerseMountPoint = UObjectModule.VerseMountPoint,
AlwaysExportStructs = UObjectModule.AlwaysExportStructs,
AlwaysExportEnums = UObjectModule.AlwaysExportEnums,
}); ;
}
List<string> UhtPlugins = new List<string>();
if (Makefile.EnabledUhtPlugins != null)
{
foreach (FileReference UhtPlugin in Makefile.EnabledUhtPlugins)
{
UhtPlugins.Add(UhtPlugin.FullName);
}
}
UHTManifest Manifest = new UHTManifest
{
TargetName = TargetName,
IsGameTarget = Makefile.TargetType != TargetType.Program,
RootLocalPath = Unreal.RootDirectory.FullName,
ExternalDependenciesFile = DepsFileName.FullName,
Modules = Modules,
UhtPlugins = UhtPlugins,
};
return Manifest;
}
/// <summary>
/// Write the manifest to disk
/// </summary>
/// <param name="Makefile">Input makefile</param>
/// <param name="TargetName">Name of the target</param>
/// <param name="ModuleInfoFileName">Destination file name. If not supplied, it will be generated.</param>
/// <param name="DepsFileName">Name of the dependencies file. If not supplied, it will be generated.</param>
public static void WriteUHTManifest(TargetMakefile Makefile, string TargetName, FileReference ModuleInfoFileName, FileReference DepsFileName)
{
// @todo ubtmake: Optimization: Ideally we could avoid having to generate this data in the case where UHT doesn't even need to run! Can't we use the existing copy? (see below use of Manifest)
UHTManifest Manifest = CreateUHTManifest(Makefile, TargetName, DepsFileName);
Directory.CreateDirectory(ModuleInfoFileName.Directory.FullName);
System.IO.File.WriteAllText(ModuleInfoFileName.FullName, JsonSerializer.Serialize(Manifest, new JsonSerializerOptions { WriteIndented = true }));
}
/// <summary>
/// Return the latest of two date/times
/// </summary>
/// <param name="Lhs">First date/time to compare</param>
/// <param name="Rhs">Second date/time to compare</param>
/// <returns>Latest of the two dates</returns>
private static DateTime LatestDateTime(DateTime Lhs, DateTime Rhs)
{
return Lhs > Rhs ? Lhs : Rhs;
}
/// <summary>
/// Builds and runs the header tool and touches the header directories.
/// Performs any early outs if headers need no changes, given the UObject modules, tool path, game name, and configuration
/// </summary>
private static async Task ExecuteHeaderToolIfNecessaryInternalAsync(BuildConfiguration BuildConfiguration, FileReference? ProjectFile,
TargetMakefile Makefile, string TargetName, ISourceFileWorkingSet WorkingSet, ILogger Logger)
{
if (ProgressWriter.bWriteMarkup)
{
Logger.LogInformation("@progress push 5%");
}
using (ProgressWriter Progress = new ProgressWriter("Generating code...", false, Logger))
{
string RootLocalPath = Unreal.RootDirectory.FullName;
// Get the path to the assemblies we care about
string UhtAssemblyPath = typeof(UhtSession).Assembly.Location;
// Get a composite date/time for cirtical assemblies being used
DateTime CompositeTimestamp = LatestDateTime(new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTimeUtc, new FileInfo(UhtAssemblyPath).LastWriteTimeUtc);
if (Makefile.EnabledUhtPlugins != null)
{
foreach (FileReference Plugin in Makefile.EnabledUhtPlugins)
{
CompositeTimestamp = LatestDateTime(CompositeTimestamp, new FileInfo(Plugin.FullName).LastWriteTimeUtc);
}
}
// ensure the headers are up to date
bool bUHTNeedsToRun = false;
if (BuildConfiguration.bForceHeaderGeneration)
{
bUHTNeedsToRun = true;
}
else if (AreGeneratedCodeFilesOutOfDate(BuildConfiguration, Makefile.UObjectModules, CompositeTimestamp, Logger))
{
bUHTNeedsToRun = true;
}
// Check we're not using a different version of UHT
FileReference ModuleInfoFileName = GetUHTModuleInfoFileName(Makefile, TargetName);
FileReference ToolInfoFile = ModuleInfoFileName.ChangeExtension(".uhtpath");
if (!bUHTNeedsToRun)
{
if (!FileReference.Exists(ToolInfoFile))
{
bUHTNeedsToRun = true;
}
else if (FileReference.ReadAllText(ToolInfoFile) != UhtAssemblyPath)
{
bUHTNeedsToRun = true;
}
}
// Get the file containing dependencies for the generated code
FileReference ExternalDependenciesFile = GetUHTDepsFileName(ModuleInfoFileName);
if (AreExternalDependenciesOutOfDate(ExternalDependenciesFile))
{
bUHTNeedsToRun = true;
}
if (bUHTNeedsToRun)
{
Progress.Write(1, 3);
WriteUHTManifest(Makefile, TargetName, ModuleInfoFileName, ExternalDependenciesFile);
string ActualTargetName = String.IsNullOrEmpty(TargetName) ? "UE5" : TargetName;
Logger.LogInformation("Parsing headers for {ActualTargetName}", ActualTargetName);
// Generate the command line
List<string> CmdArgs = new List<string>();
CmdArgs.Add((ProjectFile != null) ? ProjectFile.FullName : TargetName);
CmdArgs.Add(ModuleInfoFileName.FullName);
CmdArgs.Add("-WarningsAsErrors");
if (Unreal.IsEngineInstalled())
{
CmdArgs.Add("-installed");
}
if (BuildConfiguration.bFailIfGeneratedCodeChanges)
{
CmdArgs.Add("-FailIfGeneratedCodeChanges");
}
if (Makefile.UHTAdditionalArguments != null)
{
foreach (string Arg in Makefile.UHTAdditionalArguments)
{
if (Arg[0] == '"' && Arg[^1] == '"')
{
CmdArgs.Add(Arg.Substring(1, Arg.Length - 2));
}
else
{
CmdArgs.Add(Arg);
}
}
}
CommandLineArguments Arguments = new CommandLineArguments(CmdArgs.ToArray());
StringBuilder CmdLine = new StringBuilder();
foreach (string Arg in CmdArgs)
{
CmdLine.AppendArgument(Arg);
}
Logger.LogInformation(" Running Internal UnrealHeaderTool {CmdLine}", CmdLine);
// Run UHT
Stopwatch s = new Stopwatch();
s.Start();
IScope Timer = GlobalTracer.Instance.BuildSpan("Executing UnrealHeaderTool").StartActive();
UnrealHeaderToolMode UHTTool = new UnrealHeaderToolMode();
CompilationResult UHTResult = (CompilationResult)await UHTTool.ExecuteAsync(Arguments, Logger);
Timer.Span.Finish();
s.Stop();
if (UHTResult != CompilationResult.Succeeded)
{
// On Linux and Mac, the shell will return 128+signal number exit codes if UHT gets a signal (e.g. crashes or is interrupted)
if ((BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Linux ||
BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) &&
(int)(UHTResult) >= 128
)
{
// SIGINT is 2, so 128 + SIGINT is 130
UHTResult = ((int)(UHTResult) == 130) ? CompilationResult.Canceled : CompilationResult.CrashOrAssert;
}
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64 &&
(int)(UHTResult) < 0)
{
Logger.LogError("UnrealHeaderTool failed with exit code 0x{Result:X} - check that Unreal Engine prerequisites are installed.", (int)UHTResult);
}
throw new CompilationResultException(UHTResult);
}
Logger.LogInformation("Reflection code generated for {TargetName} in {Time} seconds", ActualTargetName, s.Elapsed.TotalSeconds);
// Update the tool info file
DirectoryReference.CreateDirectory(ToolInfoFile.Directory);
FileReference.WriteAllText(ToolInfoFile, UhtAssemblyPath);
// Now that UHT has successfully finished generating code, we need to update all cached FileItems in case their last write time has changed.
// Otherwise UBT might not detect changes UHT made.
using (GlobalTracer.Instance.BuildSpan("ExternalExecution.ResetCachedHeaderInfo()").StartActive())
{
ResetCachedHeaderInfo(Makefile.UObjectModules);
}
}
else
{
Logger.LogDebug("Generated code is up to date.");
}
Progress.Write(2, 3);
using (GlobalTracer.Instance.BuildSpan("ExternalExecution.UpdateTimestamps()").StartActive())
{
UpdateTimestamps(Makefile.UObjectModules);
}
Progress.Write(3, 3);
}
if (ProgressWriter.bWriteMarkup)
{
Logger.LogInformation("@progress pop");
}
}
static void ResetCachedHeaderInfo(List<UHTModuleInfo> UObjectModules)
{
foreach (UHTModuleInfo ModuleInfo in UObjectModules)
{
ModuleInfo.GeneratedCodeDirectory.ResetCachedInfo();
}
}
}
}