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

991 lines
41 KiB
C#

// 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
{
/// <summary>
/// Controls how the target and SDK are validated in CreateTargetRulesInstance
/// </summary>
public enum TargetRulesValidationOptions
{
/// <summary>
/// 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.
/// </summary>
ValidateTargetAndSDK,
/// <summary>
/// This will validate the target only (see ValidateTargetAndSDK)
/// </summary>
ValidateTargetOnly,
/// <summary>
/// This will validate the SDK only (see ValidateTargetAndSDK)
/// </summary>
ValidateSDKOnly,
/// <summary>
/// This will perform neither of the validations
/// </summary>
ValidateNothing
}
/// <summary>
/// Stores information about a compiled rules assembly and the types it contains
/// </summary>
public class RulesAssembly
{
/// <summary>
/// Outers scope for items created by this assembly. Used for chaining assemblies together.
/// </summary>
internal readonly RulesScope Scope;
/// <summary>
/// The compiled assembly
/// </summary>
private readonly Assembly? CompiledAssembly;
/// <summary>
/// Returns the simple name of the assembly e.g. "UE5ProgramRules"
/// </summary>
/// <returns></returns>
public string? GetSimpleAssemblyName()
{
if (CompiledAssembly != null)
{
return CompiledAssembly.GetName().Name;
}
else
{
return null;
}
}
/// <summary>
/// Returns all the types contained in the compiled rules assembly
/// </summary>
/// <returns></returns>
public IEnumerable<Type> GetTypes()
{
if (CompiledAssembly != null)
{
return CompiledAssembly.GetTypes();
}
return Enumerable.Empty<Type>();
}
/// <summary>
/// The base directories for this assembly
/// </summary>
private readonly IReadOnlyList<DirectoryReference> BaseDirs;
/// <summary>
/// All the plugins included in this assembly
/// </summary>
private readonly IReadOnlyList<PluginInfo> Plugins;
/// <summary>
/// Maps module names to their actual xxx.Module.cs file on disk
/// </summary>
private readonly IReadOnlyDictionary<string, FileReference> ModuleNameToModuleFile;
/// <summary>
/// Maps target names to their actual xxx.Target.cs file on disk
/// </summary>
private readonly IReadOnlyDictionary<string, FileReference> TargetNameToTargetFile;
/// <summary>
/// Mapping from module file to its context.
/// </summary>
private readonly IReadOnlyDictionary<FileReference, ModuleRulesContext> ModuleFileToContext;
/// <summary>
/// Whether this assembly contains engine modules. Used to set default values for bTreatAsEngineModule.
/// </summary>
private readonly bool bContainsEngineModules;
/// <summary>
/// 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.
/// </summary>
private readonly BuildSettingsVersion? DefaultBuildSettings;
/// <summary>
/// Whether the modules and targets in this assembly are read-only
/// </summary>
private readonly bool bReadOnly;
/// <summary>
/// The parent rules assembly that this assembly inherits. Game assemblies inherit the engine assembly, and the engine assembly inherits nothing.
/// </summary>
public RulesAssembly? Parent { get; }
/// <summary>
/// The set of files that were compiled to create this assembly
/// </summary>
public IEnumerable<FileReference>? AssemblySourceFiles { get; }
/// <summary>
/// Any preprocessor defines that were set when this assembly was created
/// </summary>
public IEnumerable<string>? PreprocessorDefines { get; }
/// <summary>
/// Constructor. Compiles a rules assembly from the given source files.
/// </summary>
/// <param name="Scope">The scope of items created by this assembly</param>
/// <param name="BaseDirs">The base directories for this assembly</param>
/// <param name="Plugins">All the plugins included in this assembly</param>
/// <param name="ModuleFileToContext">List of module files to compile</param>
/// <param name="TargetFiles">List of target files to compile</param>
/// <param name="AssemblyFileName">The output path for the compiled assembly</param>
/// <param name="bContainsEngineModules">Whether this assembly contains engine modules. Used to initialize the default value for ModuleRules.bTreatAsEngineModule.</param>
/// <param name="DefaultBuildSettings">Optional override for the default build settings version for modules created from this assembly.</param>
/// <param name="bReadOnly">Whether the modules and targets in this assembly are installed, and should be created with the bUsePrecompiled flag set</param>
/// <param name="bSkipCompile">Whether to skip compiling this assembly</param>
/// <param name="bForceCompile">Whether to always compile this assembly</param>
/// <param name="Parent">The parent rules assembly</param>
/// <param name="Logger"></param>
internal RulesAssembly(RulesScope Scope, IReadOnlyList<DirectoryReference> BaseDirs, IReadOnlyList<PluginInfo> Plugins, IReadOnlyDictionary<FileReference, ModuleRulesContext> ModuleFileToContext, IReadOnlyList<FileReference> 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<FileReference> AssemblySourceFilesHashSet = new HashSet<FileReference>();
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<string>? ReferencedAssembies = null;
RulesAssembly? CurrentParent = Parent;
while (CurrentParent != null && CurrentParent.CompiledAssembly != null)
{
ReferencedAssembies ??= new List<string>();
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<string, FileReference> ModuleNameToModuleFileDict = new Dictionary<string, FileReference>(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<string, FileReference> TargetNameToTargetFileDict = new Dictionary<string, FileReference>(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<ObsoleteOverrideAttribute>(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);
}
}
}
}
}
/// <summary>
/// Determines if the given path is read-only
/// </summary>
/// <param name="Location">The location to check</param>
/// <returns>True if the path is read-only, false otherwise</returns>
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;
}
}
/// <summary>
/// Finds all the preprocessor definitions that need to be set for the current engine.
/// </summary>
/// <returns>List of preprocessor definitions that should be set</returns>
public static List<string> GetPreprocessorDefinitions()
{
List<string> PreprocessorDefines = new List<string>();
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;
}
/// <summary>
/// Fills a list with all the module names in this assembly (or its parent)
/// </summary>
/// <param name="ModuleNames">List to receive the module names</param>
public void GetAllModuleNames(List<string> 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));
}
}
/// <summary>
/// Fills a list with all the target names in this assembly
/// </summary>
/// <param name="TargetNames">List to receive the target names</param>
/// <param name="bIncludeParentAssembly">Whether to include targets in the parent assembly</param>
public void GetAllTargetNames(List<string> TargetNames, bool bIncludeParentAssembly)
{
if (Parent != null && bIncludeParentAssembly)
{
Parent.GetAllTargetNames(TargetNames, true);
}
TargetNames.AddRange(TargetNameToTargetFile.Keys);
}
/// <summary>
/// Tries to get the filename that declared the given type
/// </summary>
/// <param name="ExistingType"></param>
/// <param name="File"></param>
/// <returns>True if the type was found, false otherwise</returns>
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;
}
/// <summary>
/// Gets the source file containing rules for the given module
/// </summary>
/// <param name="ModuleName">The name of the module</param>
/// <returns>The filename containing rules for this module, or an empty string if not found</returns>
public FileReference? GetModuleFileName(string ModuleName)
{
FileReference? ModuleFile;
if (ModuleNameToModuleFile.TryGetValue(ModuleName, out ModuleFile))
{
return ModuleFile;
}
else
{
return Parent?.GetModuleFileName(ModuleName);
}
}
/// <summary>
/// Gets the type defining rules for the given module
/// </summary>
/// <param name="ModuleName">The name of the module</param>
/// <returns>The rules type for this module, or null if not found</returns>
public Type? GetModuleRulesType(string ModuleName)
{
if (ModuleNameToModuleFile.ContainsKey(ModuleName))
{
return GetModuleRulesTypeInternal(ModuleName);
}
else
{
return Parent?.GetModuleRulesType(ModuleName);
}
}
/// <summary>
/// Gets the type defining rules for the given module within this assembly
/// </summary>
/// <param name="ModuleName">The name of the module</param>
/// <returns>The rules type for this module, or null if not found</returns>
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;
}
/// <summary>
/// Gets the source file containing rules for the given target
/// </summary>
/// <param name="TargetName">The name of the target</param>
/// <returns>The filename containing rules for this target, or an empty string if not found</returns>
public FileReference? GetTargetFileName(string TargetName)
{
FileReference? TargetFile;
if (TargetNameToTargetFile.TryGetValue(TargetName, out TargetFile))
{
return TargetFile;
}
else
{
return Parent?.GetTargetFileName(TargetName);
}
}
/// <summary>
/// Creates an instance of a module rules descriptor object for the specified module name
/// </summary>
/// <param name="ModuleName">Name of the module</param>
/// <param name="Target">Information about the target associated with this module</param>
/// <param name="ReferenceChain">Chain of references leading to this module</param>
/// <param name="Logger">Logger for output</param>
/// <returns>Compiled module rule info</returns>
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<string> 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<string>();
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);
}
}
/// <summary>
/// Construct an instance of the given target rules
/// Will return null if the requested type name does not exist in the assembly
/// </summary>
/// <param name="TypeName">Type name of the target rules</param>
/// <param name="TargetInfo">Target configuration information to pass to the constructor</param>
/// <param name="Logger">Logger for output</param>
/// <param name="IsTestTarget">If building a low level tests target</param>
/// <param name="ValidationOptions">Controls validation of target and SDK</param>
/// <returns>Instance of the corresponding TargetRules or null if requested type name does not exist</returns>
protected TargetRules? CreateTargetRulesInstance(string TypeName, TargetInfo TargetInfo, ILogger Logger, bool IsTestTarget = false, TargetRulesValidationOptions ValidationOptions = TargetRulesValidationOptions.ValidateTargetAndSDK)
{
// The build module must define a type named '<TargetName>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<string> DisallowedPlatformsAndGroups = Utils.MakeListOfUnsupportedPlatforms(Rules.OptedInModulePlatforms.ToList(), false, Logger);
// look in all plugins' paths to see if any disallowed
IEnumerable<PluginInfo> 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());
}
}
}
}
/// <summary>
/// Creates a target rules object for the specified target name.
/// </summary>
/// <param name="TargetName">Name of the target</param>
/// <param name="Platform">Platform being compiled</param>
/// <param name="Configuration">Configuration being compiled</param>
/// <param name="Architectures">Architectures being built</param>
/// <param name="ProjectFile">Path to the project file for this target</param>
/// <param name="Arguments">Command line arguments for this target</param>
/// <param name="Logger"></param>
/// <param name="IsTestTarget">If building a low level test target</param>
/// <param name="ValidationOptions">Controls validation of target and SDK</param>
/// <param name="IntermediateEnvironment">Intermediate environment to use</param>
/// <returns>The build target rules for the specified target</returns>
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<string, FileReference> 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 '<TargetName>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;
}
/// <summary>
/// Determines a target name based on the type of target we're trying to build
/// </summary>
/// <param name="Type">The type of target to look for</param>
/// <param name="Platform">The platform being built</param>
/// <param name="Configuration">The configuration being built</param>
/// <param name="ProjectFile">Project file for the target being built</param>
/// <param name="Logger">Logger for output</param>
/// <returns>Name of the target for the given type</returns>
public string GetTargetNameByType(TargetType Type, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, FileReference? ProjectFile, ILogger Logger)
{
// Create all the targets in this assembly
List<string> Matches = new List<string>();
foreach (KeyValuePair<string, FileReference> 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);
}
}
/// <summary>
/// Enumerates all the plugins that are available
/// </summary>
/// <returns></returns>
public IEnumerable<PluginSet> EnumeratePlugins()
{
return global::UnrealBuildTool.Plugins.FilterPlugins(EnumeratePluginsInternal());
}
/// <summary>
/// Enumerates all the plugins that are available
/// </summary>
/// <returns></returns>
protected IEnumerable<PluginInfo> EnumeratePluginsInternal()
{
if (Parent == null)
{
return Plugins;
}
else
{
return Plugins.Concat(Parent.EnumeratePluginsInternal());
}
}
/// <summary>
/// Tries to find the ModuleRulesContext associated with a given module file
/// </summary>
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;
}
}
}