// 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;
}
}
}