// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Controls how the target and SDK are validated in CreateTargetRulesInstance /// public enum TargetRulesValidationOptions { /// /// This will perform full target validation (allows the platform to update the target to make sure it's valid), and /// SDK validation (makes sure SDK overrides are not conflicting). /// This is the standard, default mode. /// ValidateTargetAndSDK, /// /// This will validate the target only (see ValidateTargetAndSDK) /// ValidateTargetOnly, /// /// This will validate the SDK only (see ValidateTargetAndSDK) /// ValidateSDKOnly, /// /// This will perform neither of the validations /// ValidateNothing } /// /// Stores information about a compiled rules assembly and the types it contains /// public class RulesAssembly { /// /// Outers scope for items created by this assembly. Used for chaining assemblies together. /// internal readonly RulesScope Scope; /// /// The compiled assembly /// private readonly Assembly? CompiledAssembly; /// /// Returns the simple name of the assembly e.g. "UE5ProgramRules" /// /// public string? GetSimpleAssemblyName() { if (CompiledAssembly != null) { return CompiledAssembly.GetName().Name; } else { return null; } } /// /// Returns all the types contained in the compiled rules assembly /// /// public IEnumerable GetTypes() { if (CompiledAssembly != null) { return CompiledAssembly.GetTypes(); } return Enumerable.Empty(); } /// /// The base directories for this assembly /// private readonly IReadOnlyList BaseDirs; /// /// All the plugins included in this assembly /// private readonly IReadOnlyList Plugins; /// /// Maps module names to their actual xxx.Module.cs file on disk /// private readonly IReadOnlyDictionary ModuleNameToModuleFile; /// /// Maps target names to their actual xxx.Target.cs file on disk /// private readonly IReadOnlyDictionary TargetNameToTargetFile; /// /// Mapping from module file to its context. /// private readonly IReadOnlyDictionary ModuleFileToContext; /// /// Whether this assembly contains engine modules. Used to set default values for bTreatAsEngineModule. /// private readonly bool bContainsEngineModules; /// /// Whether to use backwards compatible default settings for module and target rules. This is enabled by default for game projects to support a simpler migration path, but /// is disabled for engine modules. /// private readonly BuildSettingsVersion? DefaultBuildSettings; /// /// Whether the modules and targets in this assembly are read-only /// private readonly bool bReadOnly; /// /// The parent rules assembly that this assembly inherits. Game assemblies inherit the engine assembly, and the engine assembly inherits nothing. /// public RulesAssembly? Parent { get; } /// /// The set of files that were compiled to create this assembly /// public IEnumerable? AssemblySourceFiles { get; } /// /// Any preprocessor defines that were set when this assembly was created /// public IEnumerable? PreprocessorDefines { get; } /// /// Constructor. Compiles a rules assembly from the given source files. /// /// The scope of items created by this assembly /// The base directories for this assembly /// All the plugins included in this assembly /// List of module files to compile /// List of target files to compile /// The output path for the compiled assembly /// Whether this assembly contains engine modules. Used to initialize the default value for ModuleRules.bTreatAsEngineModule. /// Optional override for the default build settings version for modules created from this assembly. /// Whether the modules and targets in this assembly are installed, and should be created with the bUsePrecompiled flag set /// Whether to skip compiling this assembly /// Whether to always compile this assembly /// The parent rules assembly /// internal RulesAssembly(RulesScope Scope, IReadOnlyList BaseDirs, IReadOnlyList Plugins, IReadOnlyDictionary ModuleFileToContext, IReadOnlyList TargetFiles, FileReference AssemblyFileName, bool bContainsEngineModules, BuildSettingsVersion? DefaultBuildSettings, bool bReadOnly, bool bSkipCompile, bool bForceCompile, RulesAssembly? Parent, ILogger Logger) { this.Scope = Scope; this.BaseDirs = BaseDirs; this.Plugins = Plugins; this.ModuleFileToContext = ModuleFileToContext; this.bContainsEngineModules = bContainsEngineModules; this.DefaultBuildSettings = DefaultBuildSettings; this.bReadOnly = bReadOnly; this.Parent = Parent; // Find all the source files HashSet AssemblySourceFilesHashSet = new HashSet(); AssemblySourceFilesHashSet.UnionWith(ModuleFileToContext.Keys); AssemblySourceFilesHashSet.UnionWith(TargetFiles); AssemblySourceFiles = AssemblySourceFilesHashSet; // Compile the assembly if (AssemblySourceFiles.Any()) { // Add the parent assemblies as references so they can be used List? ReferencedAssembies = null; RulesAssembly? CurrentParent = Parent; while (CurrentParent != null && CurrentParent.CompiledAssembly != null) { ReferencedAssembies ??= new List(); ReferencedAssembies.Add(CurrentParent.CompiledAssembly.Location); CurrentParent = CurrentParent.Parent; } PreprocessorDefines = GetPreprocessorDefinitions(); CompiledAssembly = DynamicCompilation.CompileAndLoadAssembly(AssemblyFileName, AssemblySourceFiles, Logger, ReferencedAssembies: ReferencedAssembies, PreprocessorDefines: PreprocessorDefines, DoNotCompile: bSkipCompile, ForceCompile: bForceCompile); } // Setup the module map Dictionary ModuleNameToModuleFileDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); ModuleNameToModuleFile = ModuleNameToModuleFileDict; foreach (FileReference ModuleFile in ModuleFileToContext.Keys) { string ModuleName = ModuleFile.GetFileNameWithoutAnyExtensions(); if (!ModuleNameToModuleFile.ContainsKey(ModuleName)) { ModuleNameToModuleFileDict.Add(ModuleName, ModuleFile); } } // Setup the target map Dictionary TargetNameToTargetFileDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); TargetNameToTargetFile = TargetNameToTargetFileDict; foreach (FileReference TargetFile in TargetFiles) { string TargetName = TargetFile.GetFileNameWithoutAnyExtensions(); if (!TargetNameToTargetFile.ContainsKey(TargetName)) { TargetNameToTargetFileDict.Add(TargetName, TargetFile); } } // Write any deprecation warnings for methods overridden from a base with the [ObsoleteOverride] attribute. Unlike the [Obsolete] attribute, this ensures the message // is given because the method is implemented, not because it's called. if (CompiledAssembly != null) { foreach (Type CompiledType in CompiledAssembly.GetTypes()) { foreach (MethodInfo Method in CompiledType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) { ObsoleteOverrideAttribute? Attribute = Method.GetCustomAttribute(true); if (Attribute != null) { FileReference? Location; if (!TryGetFileNameFromType(CompiledType, out Location)) { Location = new FileReference(CompiledAssembly.Location); } Logger.LogWarning("{Location}: warning: {AttributeMessage}", Location, Attribute.Message); } } if (CompiledType.BaseType == typeof(ModuleRules)) { ConstructorInfo? Constructor = CompiledType.GetConstructor(new Type[] { typeof(TargetInfo) }); if (Constructor != null) { FileReference? Location; if (!TryGetFileNameFromType(CompiledType, out Location)) { Location = new FileReference(CompiledAssembly.Location); } Logger.LogWarning("{Location}: warning: Module constructors should take a ReadOnlyTargetRules argument (rather than a TargetInfo argument) and pass it to the base class constructor from 4.15 onwards. Please update the method signature.", Location); } } } } } /// /// Determines if the given path is read-only /// /// The location to check /// True if the path is read-only, false otherwise public bool IsReadOnly(FileSystemReference Location) { if (BaseDirs.Any(x => Location.IsUnderDirectory(x))) { return bReadOnly; } else if (Parent != null) { return Parent.IsReadOnly(Location); } else { return false; } } /// /// Finds all the preprocessor definitions that need to be set for the current engine. /// /// List of preprocessor definitions that should be set public static List GetPreprocessorDefinitions() { List PreprocessorDefines = new List(); PreprocessorDefines.Add("WITH_FORWARDED_MODULE_RULES_CTOR"); PreprocessorDefines.Add("WITH_FORWARDED_TARGET_RULES_CTOR"); // Define macros for the Unreal engine version, starting with 4.17 // Assumes the current MajorVersion is 5 BuildVersion? Version; if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version)) { for (int MinorVersion = 17; MinorVersion <= 30; MinorVersion++) { PreprocessorDefines.Add(String.Format("UE_4_{0}_OR_LATER", MinorVersion)); } for (int MinorVersion = 0; MinorVersion <= Version.MinorVersion; MinorVersion++) { PreprocessorDefines.Add(String.Format("UE_5_{0}_OR_LATER", MinorVersion)); } } return PreprocessorDefines; } /// /// Fills a list with all the module names in this assembly (or its parent) /// /// List to receive the module names public void GetAllModuleNames(List ModuleNames) { Parent?.GetAllModuleNames(ModuleNames); if (CompiledAssembly != null) { ModuleNames.AddRange(CompiledAssembly.GetTypes().Where(x => x.IsClass && x.IsSubclassOf(typeof(ModuleRules)) && ModuleNameToModuleFile.ContainsKey(x.Name)).Select(x => x.Name)); } } /// /// Fills a list with all the target names in this assembly /// /// List to receive the target names /// Whether to include targets in the parent assembly public void GetAllTargetNames(List TargetNames, bool bIncludeParentAssembly) { if (Parent != null && bIncludeParentAssembly) { Parent.GetAllTargetNames(TargetNames, true); } TargetNames.AddRange(TargetNameToTargetFile.Keys); } /// /// Tries to get the filename that declared the given type /// /// /// /// True if the type was found, false otherwise public bool TryGetFileNameFromType(Type ExistingType, [NotNullWhen(true)] out FileReference? File) { if (ExistingType.Assembly == CompiledAssembly) { string Name = ExistingType.Name; if (ModuleNameToModuleFile.TryGetValue(Name, out File)) { return true; } string NameWithoutTarget = Name; if (NameWithoutTarget.EndsWith("Target")) { NameWithoutTarget = NameWithoutTarget.Substring(0, NameWithoutTarget.Length - 6); } if (TargetNameToTargetFile.TryGetValue(NameWithoutTarget, out File)) { return true; } } else { if (Parent != null && Parent.TryGetFileNameFromType(ExistingType, out File)) { return true; } } File = null; return false; } /// /// Gets the source file containing rules for the given module /// /// The name of the module /// The filename containing rules for this module, or an empty string if not found public FileReference? GetModuleFileName(string ModuleName) { FileReference? ModuleFile; if (ModuleNameToModuleFile.TryGetValue(ModuleName, out ModuleFile)) { return ModuleFile; } else { return Parent?.GetModuleFileName(ModuleName); } } /// /// Gets the type defining rules for the given module /// /// The name of the module /// The rules type for this module, or null if not found public Type? GetModuleRulesType(string ModuleName) { if (ModuleNameToModuleFile.ContainsKey(ModuleName)) { return GetModuleRulesTypeInternal(ModuleName); } else { return Parent?.GetModuleRulesType(ModuleName); } } /// /// Gets the type defining rules for the given module within this assembly /// /// The name of the module /// The rules type for this module, or null if not found private Type? GetModuleRulesTypeInternal(string ModuleName) { // The build module must define a type named 'Rules' that derives from our 'ModuleRules' type. Type? RulesObjectType = CompiledAssembly?.GetType(ModuleName); if (RulesObjectType == null) { // Temporary hack to avoid System namespace collisions // @todo projectfiles: Make rules assemblies require namespaces. RulesObjectType = CompiledAssembly?.GetType("UnrealBuildTool.Rules." + ModuleName); } return RulesObjectType; } /// /// Gets the source file containing rules for the given target /// /// The name of the target /// The filename containing rules for this target, or an empty string if not found public FileReference? GetTargetFileName(string TargetName) { FileReference? TargetFile; if (TargetNameToTargetFile.TryGetValue(TargetName, out TargetFile)) { return TargetFile; } else { return Parent?.GetTargetFileName(TargetName); } } /// /// Creates an instance of a module rules descriptor object for the specified module name /// /// Name of the module /// Information about the target associated with this module /// Chain of references leading to this module /// Logger for output /// Compiled module rule info public ModuleRules CreateModuleRules(string ModuleName, ReadOnlyTargetRules Target, string ReferenceChain, ILogger Logger) { if (Target.IsTestTarget && !Target.ExplicitTestsTarget) { ModuleName = TargetDescriptor.GetTestedName(ModuleName); } // Currently, we expect the user's rules object type name to be the same as the module name string ModuleTypeName = ModuleName; // Make sure the base module file is known to us FileReference? ModuleFileName; if (!ModuleNameToModuleFile.TryGetValue(ModuleTypeName, out ModuleFileName)) { if (Parent == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Could not find definition for module '{ModuleTypeName}', (referenced via {ReferenceChain})", ModuleTypeName, ReferenceChain); } else { return Parent.CreateModuleRules(ModuleName, Target, ReferenceChain, Logger); } } // get the standard Rules object class from the assembly Type BaseRulesObjectType = GetModuleRulesTypeInternal(ModuleTypeName)!; // look around for platform/group modules that we will use instead of the basic module Type? PlatformRulesObjectType = GetModuleRulesTypeInternal(ModuleTypeName + "_" + Target.Platform.ToString()); if (PlatformRulesObjectType == null) { foreach (UnrealPlatformGroup Group in UEBuildPlatform.GetPlatformGroups(Target.Platform)) { // look to see if the group has an override Type? GroupRulesObjectType = GetModuleRulesTypeInternal(ModuleName + "_" + Group.ToString()); // we expect only one platform group to be found in the extensions if (GroupRulesObjectType != null && PlatformRulesObjectType != null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Found multiple platform group overrides ({GroupRulesName} and {PlatformRulesName}) for module {ModuleName} without a platform specific override. Create a platform override with the class hierarchy as needed.", GroupRulesObjectType.Name, PlatformRulesObjectType.Name, ModuleName); } // remember the platform group if we found it, but keep searching to verify there isn't more than one if (GroupRulesObjectType != null) { PlatformRulesObjectType = GroupRulesObjectType; } } } // verify that we aren't creating a platform module when we definitely don't want it if (Target.OptedInModulePlatforms != null) { // figure out what platforms/groups aren't allowed with this opted in list List DisallowedPlatformsAndGroups = Utils.MakeListOfUnsupportedPlatforms(Target.OptedInModulePlatforms.ToList(), false, Logger); // check if the module file is disallowed if (ModuleFileName.ContainsAnyNames(DisallowedPlatformsAndGroups, Unreal.EngineDirectory) || (Target.ProjectFile != null && ModuleFileName.ContainsAnyNames(DisallowedPlatformsAndGroups, Target.ProjectFile.Directory))) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Platform module file {ModuleFileName} is not allowed (only platforms '{Platforms}', and their groups, are allowed. This indicates a module reference not being checked with something like IsPlatformAvailableForTarget()).", ModuleFileName, String.Join(",", Target.OptedInModulePlatforms)); } } // Figure out the best rules object to use Type? RulesObjectType = PlatformRulesObjectType ?? BaseRulesObjectType; if (RulesObjectType == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Expecting to find a type to be declared in a module rules named '{ModuleTypeName}' in '{AssemblyName}'. This type must derive from the 'ModuleRules' type defined by UnrealBuildTool.", ModuleTypeName, CompiledAssembly?.FullName ?? "Unknown Assembly"); } // Create an instance of the module's rules object try { // Create an uninitialized ModuleRules object and set some defaults. ModuleRules RulesObject = (ModuleRules)RuntimeHelpers.GetUninitializedObject(RulesObjectType); // even if we created a platform-extension version of the module rules, we are pretending to be // the base type, so that no one else needs to manage this RulesObject.Name = ModuleName; RulesObject.File = ModuleFileName; RulesObject.Directory = ModuleFileName.Directory; RulesObject.Context = ModuleFileToContext[RulesObject.File]; RulesObject.Plugin = RulesObject.Context.Plugin; RulesObject.bTreatAsEngineModule = bContainsEngineModules; if (DefaultBuildSettings.HasValue) { RulesObject.DefaultBuildSettings = DefaultBuildSettings.Value; } RulesObject.bPrecompile = (RulesObject.bTreatAsEngineModule || ModuleName.Equals("UnrealGame", StringComparison.OrdinalIgnoreCase)) && Target.bPrecompile; RulesObject.bUsePrecompiled = bReadOnly; RulesObject.RulesAssembly = this; RulesObject.DirectoriesForModuleSubClasses = new(); RulesObject.DirectoriesForModuleSubClasses.Add(BaseRulesObjectType, RulesObject.Directory); // go up the type hierarchy (if there is a hierarchy), looking for any extra directories for the module if (RulesObjectType != BaseRulesObjectType && RulesObjectType != typeof(ModuleRules)) { Type SubType = RulesObjectType; RulesObject.SubclassRules = new List(); while (SubType != BaseRulesObjectType) { FileReference? SubTypeFileName; if (TryGetFileNameFromType(SubType, out SubTypeFileName)) { RulesObject.DirectoriesForModuleSubClasses.Add(SubType, SubTypeFileName.Directory); RulesObject.SubclassRules.Add(SubTypeFileName.FullName); } if (SubType.BaseType == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "{TypeName} is not derived from {BaseTypeName}", RulesObjectType.Name, BaseRulesObjectType.Name); } SubType = SubType.BaseType; } } // Call the constructor ConstructorInfo? Constructor = RulesObjectType.GetConstructor(new Type[] { typeof(ReadOnlyTargetRules) }); if (Constructor == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "No valid constructor found for {ModuleName}.", ModuleName); } // Add the parent assemblies to the assembly cache so the types in them can be used when the constructor is called RulesAssembly? CurrentParent = Parent; while (CurrentParent != null && CurrentParent.CompiledAssembly != null) { EpicGames.Core.AssemblyUtils.AddFileToAssemblyCache(CurrentParent.CompiledAssembly.Location); CurrentParent = CurrentParent.Parent; } Constructor.Invoke(RulesObject, new object[] { Target }); if (Target.IsTestTarget && !RulesObject.IsTestModule) { if (!Target.ExplicitTestsTarget) { if (Target.LaunchModuleName != null && ModuleName == TargetDescriptor.GetTestedName(Target.LaunchModuleName)) { RulesObject = new TestModuleRules(RulesObject); } } RulesObject.PrepareModuleForTests(); } return RulesObject; } catch (Exception Ex) { Exception MessageEx = (Ex is TargetInvocationException && Ex.InnerException != null) ? Ex.InnerException : Ex; throw new CompilationResultException(CompilationResult.RulesError, Ex, KnownLogEvents.RulesAssembly, "Unable to instantiate module '{ModuleName}': {ExceptionMessage}\n(referenced via {ReferenceChain})", ModuleName, MessageEx.ToString(), ReferenceChain); } } /// /// Construct an instance of the given target rules /// Will return null if the requested type name does not exist in the assembly /// /// Type name of the target rules /// Target configuration information to pass to the constructor /// Logger for output /// If building a low level tests target /// Controls validation of target and SDK /// Instance of the corresponding TargetRules or null if requested type name does not exist protected TargetRules? CreateTargetRulesInstance(string TypeName, TargetInfo TargetInfo, ILogger Logger, bool IsTestTarget = false, TargetRulesValidationOptions ValidationOptions = TargetRulesValidationOptions.ValidateTargetAndSDK) { // The build module must define a type named 'Target' that derives from our 'TargetRules' type. Type? BaseRulesType = CompiledAssembly?.GetType(TypeName); if (BaseRulesType == null) { return null; } // Look for platform/group rules that we will use instead of the base rules string PlatformRulesName = TargetInfo.Name + "_" + TargetInfo.Platform.ToString(); Type? PlatformRulesType = CompiledAssembly?.GetType(TypeName + "_" + TargetInfo.Platform.ToString()); if (PlatformRulesType == null) { foreach (UnrealPlatformGroup Group in UEBuildPlatform.GetPlatformGroups(TargetInfo.Platform)) { // look to see if the group has an override string GroupRulesName = TargetInfo.Name + "_" + Group.ToString(); Type? GroupRulesObjectType = CompiledAssembly?.GetType(TypeName + "_" + Group.ToString()); // we expect only one platform group to be found in the extensions if (GroupRulesObjectType != null && PlatformRulesType != null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Found multiple platform group overrides ({GroupRulesName} and {PlatformRulesName}) for rules {TypeName} without a platform specific override. Create a platform override with the class hierarchy as needed.", GroupRulesObjectType.Name, PlatformRulesType.Name, TypeName); } // remember the platform group if we found it, but keep searching to verify there isn't more than one if (GroupRulesObjectType != null) { PlatformRulesName = GroupRulesName; PlatformRulesType = GroupRulesObjectType; } } } if (PlatformRulesType != null && !PlatformRulesType.IsSubclassOf(BaseRulesType)) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Expecting {PlatformRulesType} to be a specialization of {BaseRulesType}", PlatformRulesType, BaseRulesType); } // Create an instance of the module's rules object, and set some defaults before calling the constructor. Type RulesType = PlatformRulesType ?? BaseRulesType; FileReference BaseFile = TargetNameToTargetFile[TargetInfo.Name]; FileReference PlatformFile = TargetNameToTargetFile.TryGetValue(PlatformRulesName, out FileReference? PlatformTargetFile) ? PlatformTargetFile : BaseFile; TargetRules Rules = TargetRules.Create(RulesType, TargetInfo, BaseFile, PlatformFile, TargetNameToTargetFile.Values, DefaultBuildSettings, Logger); bool bValidateTarget = ValidationOptions == TargetRulesValidationOptions.ValidateTargetAndSDK || ValidationOptions == TargetRulesValidationOptions.ValidateTargetOnly; bool bValidateSDK = ValidationOptions == TargetRulesValidationOptions.ValidateTargetAndSDK || ValidationOptions == TargetRulesValidationOptions.ValidateSDKOnly; // Set the default overriddes for the configured target type Rules.SetOverridesForTargetType(); // Set the final value for the link type in the target rules if (Rules.LinkType == TargetLinkType.Default) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "TargetRules.LinkType should be inferred from TargetType"); } if (bValidateTarget) { // Delayed-fixup of TargetBuildEnvironment.UniqueIfNeeded Rules.UpdateBuildEnvironmentIfNeeded(this, arguments: null, Logger); // Set the default value for whether to use the shared build environment if (Rules.BuildEnvironment == TargetBuildEnvironment.Unique && Unreal.IsEngineInstalled()) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Targets with a unique build environment cannot be built with an installed engine."); } } // Automatically include CoreUObject if (Rules.bCompileAgainstEngine) { Rules.bCompileAgainstCoreUObject = true; } if (Rules.Type == TargetType.Editor) { Rules.bBuildWithEditorOnlyData = true; // Must have editor only data if building the editor. Rules.bCompileAgainstEditor = true; } // Apply the override to force debug info to be enabled if (Rules.bForceDebugInfo) { Rules.DebugInfo = DebugInfoMode.Full; Rules.bOmitPCDebugInfoInDevelopment = false; } // If merging modules, force modular and strip exports if (Rules.bMergeModules) { Rules.BuildEnvironment = TargetBuildEnvironment.Unique; Rules.LinkType = TargetLinkType.Modular; Rules.bStripExports = true; } // Setup utrace for Shader Compiler Worker if (Rules.bShaderCompilerWorkerTrace) { Rules.GlobalDefinitions.Add("USE_SHADER_COMPILER_WORKER_TRACE=1"); } else { Rules.GlobalDefinitions.Add("USE_SHADER_COMPILER_WORKER_TRACE=0"); } // Set a macro if we allow using generated inis if (!Rules.bAllowGeneratedIniWhenCooked) { Rules.GlobalDefinitions.Add("DISABLE_GENERATED_INI_WHEN_COOKED=1"); } if (!Rules.bAllowNonUFSIniWhenCooked) { Rules.GlobalDefinitions.Add("DISABLE_NONUFS_INI_WHEN_COOKED=1"); } if (Rules.bDisableUnverifiedCertificates) { Rules.GlobalDefinitions.Add("DISABLE_UNVERIFIED_CERTIFICATE_LOADING=1"); } if (Rules.bRequireObjectPtrForAddReferencedObjects) { Rules.GlobalDefinitions.Add("UE_REFERENCE_COLLECTOR_REQUIRE_OBJECTPTR=1"); } // Until VNI fully supports the new VM, we need the ability to have both the old and new // available in some rare cases. If we are using the old VM and the target hasn't overridden // the new VM define, then set the define based on the old VM flag. if (!Rules.GlobalDefinitions.Any(x => x.StartsWith("WITH_VERSE_VM="))) { Rules.GlobalDefinitions.Add($"WITH_VERSE_VM={(Rules.bUseVerseBPVM ? 0 : 1)}"); } // if the Target has opted in only some platforms, disable any plugins of other platforms (there may be editor, etc, modules that // will just add themselves, with no other reference to be able to remove them, other than disabling them here) if (Rules.OptedInModulePlatforms != null) { // figure out what platforms/groups aren't allowed with this opted in list List DisallowedPlatformsAndGroups = Utils.MakeListOfUnsupportedPlatforms(Rules.OptedInModulePlatforms.ToList(), false, Logger); // look in all plugins' paths to see if any disallowed IEnumerable DisallowedPlugins = EnumeratePlugins().Where(x => x.ChoiceVersion != null).Select(x => x.ChoiceVersion!).Where(Plugin => Plugin.File.ContainsAnyNames(DisallowedPlatformsAndGroups, Unreal.EngineDirectory) || (Rules.ProjectFile != null && Plugin.File.ContainsAnyNames(DisallowedPlatformsAndGroups, Rules.ProjectFile.Directory))); // log out the plugins we are disabling DisallowedPlugins.ToList().ForEach(x => Logger.LogDebug("Disallowing non-opted-in platform plugin {PluginFile}", x.File)); // and, disable these plugins Rules.DisablePlugins.AddRange(DisallowedPlugins.Select(x => x.Name)); } // Allow the platform to finalize the settings if (bValidateTarget) { UEBuildPlatform Platform = UEBuildPlatform.GetBuildPlatform(Rules.Platform); Platform.ValidateTarget(Rules); } // make sure any SDK overrides are valid if (bValidateSDK) { ValidateSDKs(Rules); } // Some platforms may *require* monolithic compilation... if (Rules.LinkType != TargetLinkType.Monolithic && UEBuildPlatform.PlatformRequiresMonolithicBuilds(Rules.Platform, Rules.Configuration)) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "{RulesName}: {RulesPlatform} does not support modular builds", Rules.Name, Rules.Platform); } if (IsTestTarget) { Rules = TestTargetRules.Create(Rules, TargetInfo); } return Rules; } private void ValidateSDKs(TargetRules Rules) { // ask if each plaform SDK matters to this target foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms()) { if (!Rules.IsSDKVersionRelevant(Platform)) { continue; } UEBuildPlatformSDK? SDK = UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()); if (SDK == null) { continue; } if (SDK.bHasSDKOverride) { // if the target doesn't allow for an override at all, error if (!Rules.AllowsPerProjectSDKVersion()) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Target {RulesName} is being built with a overridden {Platform} SDK version to '{SdkVersion}', but this target is not allowed - likely due to a modular build using a Shared BuildEnvironment. If this target doesn't care about SDK versions, set 'bAreTargetSDKVersionsRelevantOverride = false' in your Target.cs file. Be aware this could cause confusion with multiple targets fighting over shared modules.", Rules.Name, Platform, SDK.GetMainVersion()); } // check to see if the project _doesn't_ override the SDK version (if it did, this was already handled during early processing) if (Rules.ProjectFile == null || !SDK.ProjectsThatOverrodeSDK.Contains(Rules.ProjectFile)) { string OverrideProject = SDK.ProjectsThatOverrodeSDK[0].GetFileNameWithoutAnyExtensions(); throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Target {RulesName} is using default {Platform} SDK version, but another target (probably {OverrideProject}) has overridden the SDK version to '{SdkVersion}'. If this target doesn't care about SDK versions, set 'bAreTargetSDKVersionsRelevantOverride = false' in your Target.cs file", Rules.Name, Platform, OverrideProject, SDK.GetMainVersion()); } } } } /// /// Creates a target rules object for the specified target name. /// /// Name of the target /// Platform being compiled /// Configuration being compiled /// Architectures being built /// Path to the project file for this target /// Command line arguments for this target /// /// If building a low level test target /// Controls validation of target and SDK /// Intermediate environment to use /// The build target rules for the specified target public TargetRules CreateTargetRules(string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, UnrealArchitectures? Architectures, FileReference? ProjectFile, CommandLineArguments? Arguments, ILogger Logger, bool IsTestTarget = false, TargetRulesValidationOptions ValidationOptions = TargetRulesValidationOptions.ValidateTargetAndSDK, UnrealIntermediateEnvironment IntermediateEnvironment = UnrealIntermediateEnvironment.Default) { if (IsTestTarget) { TargetName = TargetDescriptor.GetTestedName(TargetName); } Architectures ??= UnrealArchitectureConfig.ForPlatform(Platform).ActiveArchitectures(ProjectFile, TargetName); bool bFoundTargetName = TargetNameToTargetFile.ContainsKey(TargetName); if (bFoundTargetName == false) { if (Parent == null) { string ExceptionMessage = "Couldn't find target rules file for target '{TargetName}' in rules assembly '{RulesAssembly}'" + Environment.NewLine; ExceptionMessage += "Location: {AssemblyLocation}" + Environment.NewLine; if (TargetNameToTargetFile.Any()) { ExceptionMessage += "Target rules found:" + Environment.NewLine; foreach (KeyValuePair entry in TargetNameToTargetFile) { ExceptionMessage += "\t" + entry.Key + " - " + entry.Value + Environment.NewLine; } } throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, ExceptionMessage, TargetName, CompiledAssembly?.FullName ?? "Unknown Assembly", CompiledAssembly?.Location ?? "Unknown Location"); } else { return Parent.CreateTargetRules(TargetName, Platform, Configuration, Architectures, ProjectFile, Arguments, Logger, IsTestTarget, ValidationOptions, IntermediateEnvironment); } } // Currently, we expect the user's rules object type name to be the same as the module name + 'Target' string TargetTypeName = TargetName + "Target"; // The build module must define a type named 'Target' that derives from our 'TargetRules' type. TargetRules? TargetRules = CreateTargetRulesInstance(TargetTypeName, new TargetInfo(TargetName, Platform, Configuration, Architectures, ProjectFile, Arguments, IntermediateEnvironment), Logger, IsTestTarget, ValidationOptions); if (TargetRules == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Expecting to find a type to be declared in a target rules named '{TargetTypeName}'. This type must derive from the 'TargetRules' type defined by UnrealBuildTool.", TargetTypeName); } return TargetRules; } /// /// Determines a target name based on the type of target we're trying to build /// /// The type of target to look for /// The platform being built /// The configuration being built /// Project file for the target being built /// Logger for output /// Name of the target for the given type public string GetTargetNameByType(TargetType Type, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, FileReference? ProjectFile, ILogger Logger) { // Create all the targets in this assembly List Matches = new List(); foreach (KeyValuePair TargetPair in TargetNameToTargetFile) { TargetRules? Rules = CreateTargetRulesInstance(TargetPair.Key + "Target", new TargetInfo(TargetPair.Key, Platform, Configuration, null, ProjectFile, null), Logger); if (Rules != null && Rules.Type == Type && !Rules.bExplicitTargetForType) { Matches.Add(TargetPair.Key); } } // If we got a result, return it. If there were multiple results, fail. if (Matches.Count == 0) { if (Parent == null) { throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Unable to find target of type '{Type}' for project '{1}'", Type, ProjectFile?.FullName ?? "NoProject"); } else { return Parent.GetTargetNameByType(Type, Platform, Configuration, ProjectFile, Logger); } } else { if (Matches.Count == 1) { return Matches[0]; } // attempt to get a default target (like DefaultEditorTarget) from the Engine.ini string KeyName = $"Default{Type}Target"; string? DefaultTargetName; // read in the engine config hierarchy and get the value ConfigHierarchy EngineConfig = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, ProjectFile?.Directory, Platform); if (EngineConfig.GetString("/Script/BuildSettings.BuildSettings", KeyName, out DefaultTargetName)) { // if a value was found, make sure that this is one of the found targets if (Matches.Contains(DefaultTargetName)) { return DefaultTargetName; } } throw new CompilationResultException(CompilationResult.RulesError, KnownLogEvents.RulesAssembly, "Found multiple targets with TargetType={Type}: {Matches}.\nSpecify a default with a {KeyName} entry in [/Script/BuildSettings.BuildSettings] section of your DefaultEngine.ini", Type, String.Join(", ", Matches), KeyName); } } /// /// Enumerates all the plugins that are available /// /// public IEnumerable EnumeratePlugins() { return global::UnrealBuildTool.Plugins.FilterPlugins(EnumeratePluginsInternal()); } /// /// Enumerates all the plugins that are available /// /// protected IEnumerable EnumeratePluginsInternal() { if (Parent == null) { return Plugins; } else { return Plugins.Concat(Parent.EnumeratePluginsInternal()); } } /// /// Tries to find the ModuleRulesContext associated with a given module file /// internal ModuleRulesContext? TryGetContextForModule(FileReference ModuleFile) { for (RulesAssembly? Assembly = this; Assembly != null; Assembly = Assembly.Parent) { if (Assembly.ModuleFileToContext.TryGetValue(ModuleFile, out ModuleRulesContext? Context)) { return Context; } } return null; } } }