// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using EpicGames.Core; using JsonExtensions; namespace UnrealBuildTool { /// /// The type of host that can load a module /// public enum ModuleHostType { /// /// /// Default, /// /// Any target using the UE runtime /// Runtime, /// /// Any target except for commandlet /// RuntimeNoCommandlet, /// /// Any target or program /// RuntimeAndProgram, /// /// Loaded only in cooked builds /// CookedOnly, /// /// Loaded only in uncooked builds /// UncookedOnly, /// /// Loaded only when the engine has support for developer tools enabled /// Developer, /// /// Loads on any targets where bBuildDeveloperTools is enabled /// DeveloperTool, /// /// Loaded only by the editor /// Editor, /// /// Loaded only by the editor, except when running commandlets /// EditorNoCommandlet, /// /// Loaded by the editor or program targets /// EditorAndProgram, /// /// Loaded only by programs /// Program, /// /// Loaded only by servers /// ServerOnly, /// /// Loaded only by clients, and commandlets, and editor.... /// ClientOnly, /// /// Loaded only by clients and editor (editor can run PIE which is kinda a commandlet) /// ClientOnlyNoCommandlet, /// /// External module, should never be loaded automatically only referenced /// External, } /// /// Indicates when the engine should attempt to load this module /// public enum ModuleLoadingPhase { /// /// Loaded at the default loading point during startup (during engine init, after game modules are loaded.) /// Default, /// /// Right after the default phase /// PostDefault, /// /// Right before the default phase /// PreDefault, /// /// Loaded as soon as plugins can possibly be loaded (need GConfig) /// EarliestPossible, /// /// Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks /// PostConfigInit, /// /// The first screen to be rendered after system splash screen /// PostSplashScreen, /// /// After PostConfigInit and before coreUobject initialized. used for early boot loading screens before the uobjects are initialized /// PreEarlyLoadingScreen, /// /// Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers /// PreLoadingScreen, /// /// After the engine has been initialized /// PostEngineInit, /// /// Do not automatically load this module /// None, } /// /// Class containing information about a code module /// [DebuggerDisplay("Name={Name}")] public class ModuleDescriptor { /// /// Name of this module /// public readonly string Name; /// /// Usage type of module /// public ModuleHostType Type; /// /// When should the module be loaded during the startup sequence? This is sort of an advanced setting. /// public ModuleLoadingPhase LoadingPhase = ModuleLoadingPhase.Default; /// /// List of allowed platforms /// public List? PlatformAllowList; /// /// List of disallowed platforms /// public List? PlatformDenyList; /// /// List of allowed targets /// public TargetType[]? TargetAllowList; /// /// List of disallowed targets /// public TargetType[]? TargetDenyList; /// /// List of allowed target configurations /// public UnrealTargetConfiguration[]? TargetConfigurationAllowList; /// /// List of disallowed target configurations /// public UnrealTargetConfiguration[]? TargetConfigurationDenyList; /// /// List of allowed programs /// public string[]? ProgramAllowList; /// /// List of disallowed programs /// public string[]? ProgramDenyList; /// /// List of allowed game targets /// public string[]? GameTargetAllowList; /// /// List of disallowed game targets /// public string[]? GameTargetDenyList; /// /// List of additional dependencies for building this module. /// public string[]? AdditionalDependencies; /// /// When true, an empty PlatformAllowList is interpreted as 'no platforms' with the expectation that explicit platforms will be added in plugin extensions */ /// public bool bHasExplicitPlatforms; /// /// Constructor /// /// Name of the module /// Type of target that can host this module public ModuleDescriptor(string InName, ModuleHostType InType) { Name = InName; Type = InType; } /// /// Constructs a ModuleDescriptor from a Json object /// /// /// /// The new module descriptor public static ModuleDescriptor FromJsonObject(JsonObject InObject, FileReference JsonFilePath) { ModuleDescriptor Module = new ModuleDescriptor(InObject.GetStringField("Name"), InObject.GetEnumField("Type")); ModuleLoadingPhase LoadingPhase; if (InObject.TryGetEnumField("LoadingPhase", out LoadingPhase)) { Module.LoadingPhase = LoadingPhase; } try { string[]? PlatformAllowList; // it's important we default to null, and don't have an empty allow list by default, because that will indicate that no // platforms should be compiled (see IsCompiledInConfiguration(), it only checks for null, not length) Module.PlatformAllowList = null; if (InObject.TryGetStringArrayFieldWithDeprecatedFallback("PlatformAllowList", "WhitelistPlatforms", out PlatformAllowList)) { Module.PlatformAllowList = new List(); foreach (string TargetPlatformName in PlatformAllowList) { UnrealTargetPlatform Platform; if (UnrealTargetPlatform.TryParse(TargetPlatformName, out Platform)) { Module.PlatformAllowList.Add(Platform); } else if ( !PluginDescriptor.IsAllowableMissingPlatform(TargetPlatformName, JsonFilePath.Directory) ) { Log.TraceWarningTask(JsonFilePath, $"Unknown platform {TargetPlatformName} while parsing allow list for module descriptor {Module.Name}"); } } } string[]? PlatformDenyList; if (InObject.TryGetStringArrayFieldWithDeprecatedFallback("PlatformDenyList", "BlacklistPlatforms", out PlatformDenyList)) { Module.PlatformDenyList = new List(); foreach (string TargetPlatformName in PlatformDenyList) { UnrealTargetPlatform Platform; if (UnrealTargetPlatform.TryParse(TargetPlatformName, out Platform)) { Module.PlatformDenyList.Add(Platform); } else if ( !PluginDescriptor.IsAllowableMissingPlatform(TargetPlatformName, JsonFilePath.Directory) ) { Log.TraceWarningTask(JsonFilePath, $"Unknown platform {TargetPlatformName} while parsing deny list for module descriptor {Module.Name}"); } } } } catch (BuildException Ex) { ExceptionUtils.AddContext(Ex, "while parsing module descriptor '{0}'", Module.Name); throw; } TargetType[]? TargetAllowList; if (InObject.TryGetEnumArrayFieldWithDeprecatedFallback("TargetAllowList", "WhitelistTargets", out TargetAllowList)) { Module.TargetAllowList = TargetAllowList; } TargetType[]? TargetDenyList; if (InObject.TryGetEnumArrayFieldWithDeprecatedFallback("TargetDenyList", "BlacklistTargets", out TargetDenyList)) { Module.TargetDenyList = TargetDenyList; } UnrealTargetConfiguration[]? TargetConfigurationAllowList; if (InObject.TryGetEnumArrayFieldWithDeprecatedFallback("TargetConfigurationAllowList", "WhitelistTargetConfigurations", out TargetConfigurationAllowList)) { Module.TargetConfigurationAllowList = TargetConfigurationAllowList; } UnrealTargetConfiguration[]? TargetConfigurationDenyList; if (InObject.TryGetEnumArrayFieldWithDeprecatedFallback("TargetConfigurationDenyList", "BlacklistTargetConfigurations", out TargetConfigurationDenyList)) { Module.TargetConfigurationDenyList = TargetConfigurationDenyList; } string[]? ProgramAllowList; if (InObject.TryGetStringArrayFieldWithDeprecatedFallback("ProgramAllowList", "WhitelistPrograms", out ProgramAllowList)) { Module.ProgramAllowList = ProgramAllowList; } string[]? ProgramDenyList; if (InObject.TryGetStringArrayFieldWithDeprecatedFallback("ProgramDenyList", "BlacklistPrograms", out ProgramDenyList)) { Module.ProgramDenyList = ProgramDenyList; } string[]? GameTargetAllowList; if (InObject.TryGetStringArrayField("GameTargetAllowList", out GameTargetAllowList)) { Module.GameTargetAllowList = GameTargetAllowList; } string[]? GameTargetDenyList; if (InObject.TryGetStringArrayField("GameTargetDenyList", out GameTargetDenyList)) { Module.GameTargetDenyList = GameTargetDenyList; } string[]? AdditionalDependencies; if (InObject.TryGetStringArrayField("AdditionalDependencies", out AdditionalDependencies)) { Module.AdditionalDependencies = AdditionalDependencies; } bool bHasExplicitPlatforms; if (InObject.TryGetBoolField("HasExplicitPlatforms", out bHasExplicitPlatforms)) { Module.bHasExplicitPlatforms = bHasExplicitPlatforms; } return Module; } /// /// Write this module to a JsonWriter /// /// Writer to output to void Write(JsonWriter Writer) { Writer.WriteObjectStart(); Writer.WriteValue("Name", Name); Writer.WriteValue("Type", Type.ToString()); Writer.WriteValue("LoadingPhase", LoadingPhase.ToString()); // important note: we don't check the length of the platform allow list, because if an unknown platform was read in, but was not valid, the // list will exist but be empty. We don't want to remove the allow list completely, because that would allow this module on all platforms, // which will not be the desired effect if (PlatformAllowList != null) { Writer.WriteArrayStart("PlatformAllowList"); foreach (UnrealTargetPlatform Platform in PlatformAllowList) { Writer.WriteValue(Platform.ToString()); } Writer.WriteArrayEnd(); } if (PlatformDenyList != null && PlatformDenyList.Count > 0) { Writer.WriteArrayStart("PlatformDenyList"); foreach (UnrealTargetPlatform Platform in PlatformDenyList) { Writer.WriteValue(Platform.ToString()); } Writer.WriteArrayEnd(); } if (TargetAllowList != null && TargetAllowList.Length > 0) { Writer.WriteArrayStart("TargetAllowList"); foreach (TargetType Target in TargetAllowList) { Writer.WriteValue(Target.ToString()); } Writer.WriteArrayEnd(); } if (TargetDenyList != null && TargetDenyList.Length > 0) { Writer.WriteArrayStart("TargetDenyList"); foreach (TargetType Target in TargetDenyList) { Writer.WriteValue(Target.ToString()); } Writer.WriteArrayEnd(); } if (TargetConfigurationAllowList != null && TargetConfigurationAllowList.Length > 0) { Writer.WriteArrayStart("TargetConfigurationAllowList"); foreach (UnrealTargetConfiguration Config in TargetConfigurationAllowList) { Writer.WriteValue(Config.ToString()); } Writer.WriteArrayEnd(); } if (TargetConfigurationDenyList != null && TargetConfigurationDenyList.Length > 0) { Writer.WriteArrayStart("TargetConfigurationDenyList"); foreach (UnrealTargetConfiguration Config in TargetConfigurationDenyList) { Writer.WriteValue(Config.ToString()); } Writer.WriteArrayEnd(); } if (ProgramAllowList != null && ProgramAllowList.Length > 0) { Writer.WriteStringArrayField("ProgramAllowList", ProgramAllowList); } if (ProgramDenyList != null && ProgramDenyList.Length > 0) { Writer.WriteStringArrayField("ProgramDenyList", ProgramDenyList); } if (GameTargetAllowList != null && GameTargetAllowList.Length > 0) { Writer.WriteStringArrayField("GameTargetAllowList", GameTargetAllowList); } if (GameTargetDenyList != null && GameTargetDenyList.Length > 0) { Writer.WriteStringArrayField("GameTargetDenyList", GameTargetDenyList); } if (AdditionalDependencies != null && AdditionalDependencies.Length > 0) { Writer.WriteArrayStart("AdditionalDependencies"); foreach (string AdditionalDependency in AdditionalDependencies) { Writer.WriteValue(AdditionalDependency); } Writer.WriteArrayEnd(); } if (bHasExplicitPlatforms) { Writer.WriteValue("HasExplicitPlatforms", bHasExplicitPlatforms); } Writer.WriteObjectEnd(); } JsonObject ToJsonObject() { JsonObject ModuleObject = new JsonObject(); ModuleObject.AddOrSetFieldValue("Name", Name); ModuleObject.AddOrSetFieldValue("Type", Type.ToString()); ModuleObject.AddOrSetFieldValue("LoadingPhase", LoadingPhase.ToString()); // important note: we don't check the length of the platform allow list, because if an unknown platform was read in, but was not valid, the // list will exist but be empty. We don't want to remove the allow list completely, because that would allow this module on all platforms, // which will not be the desired effect if (PlatformAllowList != null) { string[] PlatformAllowListStringArray = PlatformAllowList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("PlatformAllowList", PlatformAllowListStringArray); } if (PlatformDenyList != null && PlatformDenyList.Count > 0) { string[] PlatformDenyListStringArray = PlatformDenyList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("PlatformDenyList", PlatformDenyListStringArray); } if (TargetAllowList != null && TargetAllowList.Length > 0) { string[] TargetAllowListStringArray = TargetAllowList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("TargetAllowList", TargetAllowListStringArray); } if (TargetDenyList != null && TargetDenyList.Length > 0) { string[] TargetDenyListStringArray = TargetDenyList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("TargetDenyList", TargetDenyListStringArray); } if (TargetConfigurationAllowList != null && TargetConfigurationAllowList.Length > 0) { string[] TargetConfigurationAllowListStringArray = TargetConfigurationAllowList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("TargetConfigurationAllowList", TargetConfigurationAllowListStringArray); } if (TargetConfigurationDenyList != null && TargetConfigurationDenyList.Length > 0) { string[] TargetConfigurationDenyListStringArray = TargetConfigurationDenyList.Select(X => X.ToString()).ToArray(); ModuleObject.AddOrSetFieldValue("TargetConfigurationDenyList", TargetConfigurationDenyListStringArray); } if (ProgramAllowList != null && ProgramAllowList.Length > 0) { ModuleObject.AddOrSetFieldValue("ProgramAllowList", ProgramAllowList); } if (ProgramDenyList != null && ProgramDenyList.Length > 0) { ModuleObject.AddOrSetFieldValue("ProgramDenyList", ProgramDenyList); } if (AdditionalDependencies != null && AdditionalDependencies.Length > 0) { ModuleObject.AddOrSetFieldValue("AdditionalDependencies", AdditionalDependencies); } if (bHasExplicitPlatforms) { ModuleObject.AddOrSetFieldValue("HasExplicitPlatforms", bHasExplicitPlatforms); } return ModuleObject; } /// /// Write an array of module descriptors /// /// The Json writer to output to /// Name of the array /// Array of modules public static void WriteArray(JsonWriter Writer, string Name, ModuleDescriptor[]? Modules) { if (Modules != null && Modules.Length > 0) { Writer.WriteArrayStart(Name); foreach (ModuleDescriptor Module in Modules) { Module.Write(Writer); } Writer.WriteArrayEnd(); } } /// /// Updates a JsonObject with an array of module descriptors /// /// The JsonObject to update. /// Name of the array /// Array of modules public static void UpdateJson(JsonObject InObject, string Name, ModuleDescriptor[]? Modules) { if (Modules != null && Modules.Length > 0) { JsonObject[] JsonObjects = Modules.Select(X => X.ToJsonObject()).ToArray(); InObject.AddOrSetFieldValue(Name, JsonObjects); } } /// /// Produces any warnings and errors for the module settings /// /// File containing the module declaration public void Validate(FileReference File) { if (Type == ModuleHostType.Developer) { Log.TraceWarningOnce("The 'Developer' module type has been deprecated in 4.24. Use 'DeveloperTool' for modules that can be loaded by game/client/server targets in non-shipping configurations, or 'UncookedOnly' for modules that should only be loaded by uncooked editor and program targets (eg. modules containing blueprint nodes)"); Log.TraceWarningOnce(File, "The 'Developer' module type has been deprecated in 4.24."); } } /// /// Determines whether the given plugin module is part of the current build. /// /// The platform being compiled for /// The target configuration being compiled for /// Name of the target being built /// The type of the target being compiled /// Whether the configuration includes developer tools (typically UEBuildConfiguration.bBuildDeveloperTools for UBT callers) /// Whether the configuration requires cooked content (typically UEBuildConfiguration.bBuildRequiresCookedData for UBT callers) public bool IsCompiledInConfiguration(UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string TargetName, TargetType TargetType, bool bBuildDeveloperTools, bool bBuildRequiresCookedData) { return IsCompiledInConfiguration(Platform, Configuration, TargetName, TargetType, bBuildDeveloperTools, bBuildRequiresCookedData, out string? _); } /// /// Determines whether the given plugin module is part of the current build. /// /// The platform being compiled for /// The target configuration being compiled for /// Name of the target being built /// The type of the target being compiled /// Whether the configuration includes developer tools (typically UEBuildConfiguration.bBuildDeveloperTools for UBT callers) /// Whether the configuration requires cooked content (typically UEBuildConfiguration.bBuildRequiresCookedData for UBT callers) /// Out parameter, reason why this plugin module is invalid public bool IsCompiledInConfiguration(UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string TargetName, TargetType TargetType, bool bBuildDeveloperTools, bool bBuildRequiresCookedData, [NotNullWhen(false)] out string? invalidReason) { // Check the platform is allowed // important note: we don't check the length of the platform allow list, because if an unknown platform was read in, but was not valid, the // list will exist but be empty. In this case, we need to disallow all platforms from building, otherwise, build errors will occur when // it starts compiling for _all_ platforms. This means we don't need to check bHasExplicitPlatforms either if (PlatformAllowList != null && !PlatformAllowList.Contains(Platform)) { invalidReason = $"PlatformAllowList does not include {Platform}"; return false; } // Check the platform is not denied if (PlatformDenyList != null && PlatformDenyList.Contains(Platform)) { invalidReason = $"PlatformDenyList includes {Platform}"; return false; } // Check the target is allowed if (TargetAllowList != null && TargetAllowList.Length > 0 && !TargetAllowList.Contains(TargetType)) { invalidReason = $"TargetAllowList does not include {TargetType}"; return false; } // Check the target is not denied if (TargetDenyList != null && TargetDenyList.Contains(TargetType)) { invalidReason = $"PlatformDenyList includes {TargetType}"; return false; } // Check the target configuration is allowed if (TargetConfigurationAllowList != null && TargetConfigurationAllowList.Length > 0 && !TargetConfigurationAllowList.Contains(Configuration)) { invalidReason = $"TargetConfigurationAllowList does not include {Configuration}"; return false; } // Check the target configuration is not denied if (TargetConfigurationDenyList != null && TargetConfigurationDenyList.Contains(Configuration)) { invalidReason = $"TargetConfigurationDenyList includes {Configuration}"; return false; } if (TargetType == TargetType.Program) { // Check the program name is on the allow list. Note that this behavior is slightly different to other allow/deny checks; we will allow a module of any type if it's explicitly allowed for this program. if (ProgramAllowList != null && ProgramAllowList.Length > 0) { if (!ProgramAllowList.Contains(TargetName)) { invalidReason = $"ProgramAllowList does not include {TargetName}"; return false; } invalidReason = null; return true; } // Check the program name is not denied if (ProgramDenyList != null && ProgramDenyList.Contains(TargetName)) { invalidReason = $"ProgramDenyList includes {TargetName}"; return false; } } else { // Check that the TargetName is allowed if (GameTargetAllowList != null && GameTargetAllowList.Length > 0 && !GameTargetAllowList.Contains(TargetName)) { invalidReason = $"GameTargetAllowList does not include {TargetName}"; return false; } // Check that the TargetName is not denied if (GameTargetDenyList != null && GameTargetDenyList.Contains(TargetName)) { invalidReason = $"GameTargetDenyList includes {TargetName}"; return false; } } // Check the module is compatible with this target. invalidReason = $"Module Type {Type} not supported for Target Type {TargetType}"; switch (Type) { case ModuleHostType.Runtime: case ModuleHostType.RuntimeNoCommandlet: return TargetType != TargetType.Program; case ModuleHostType.RuntimeAndProgram: return true; case ModuleHostType.CookedOnly: return bBuildRequiresCookedData; case ModuleHostType.UncookedOnly: return !bBuildRequiresCookedData; case ModuleHostType.Developer: return TargetType == TargetType.Editor || TargetType == TargetType.Program; case ModuleHostType.DeveloperTool: return bBuildDeveloperTools; case ModuleHostType.Editor: case ModuleHostType.EditorNoCommandlet: return TargetType == TargetType.Editor; case ModuleHostType.EditorAndProgram: return TargetType == TargetType.Editor || TargetType == TargetType.Program; case ModuleHostType.Program: return TargetType == TargetType.Program; case ModuleHostType.ServerOnly: return TargetType != TargetType.Program && TargetType != TargetType.Client; case ModuleHostType.ClientOnly: case ModuleHostType.ClientOnlyNoCommandlet: return TargetType != TargetType.Program && TargetType != TargetType.Server; } return false; } } }