// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// 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 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; } /// /// The base directories for this assembly /// private List BaseDirs; /// /// All the plugins included in this assembly /// private IReadOnlyList Plugins; /// /// Maps module names to their actual xxx.Module.cs file on disk /// private Dictionary ModuleNameToModuleFile = new Dictionary(StringComparer.InvariantCultureIgnoreCase); /// /// Maps target names to their actual xxx.Target.cs file on disk /// private Dictionary TargetNameToTargetFile = new Dictionary(StringComparer.InvariantCultureIgnoreCase); /// /// Mapping from module file to its context. /// private Dictionary ModuleFileToContext; /// /// Whether this assembly contains engine modules. Used to set default values for bTreatAsEngineModule. /// private 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 BuildSettingsVersion? DefaultBuildSettings; /// /// Whether the modules and targets in this assembly are read-only /// private 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 HashSet? AssemblySourceFiles { get; } /// /// Any preprocessor defines that were set when this assembly was created /// public List? 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, List BaseDirs, IReadOnlyList Plugins, Dictionary ModuleFileToContext, List 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 AssemblySourceFiles = new HashSet(); AssemblySourceFiles.UnionWith(ModuleFileToContext.Keys); AssemblySourceFiles.UnionWith(TargetFiles); // Compile the assembly if (AssemblySourceFiles.Count > 0) { PreprocessorDefines = GetPreprocessorDefinitions(); CompiledAssembly = DynamicCompilation.CompileAndLoadAssembly(AssemblyFileName, AssemblySourceFiles, Logger, PreprocessorDefines: PreprocessorDefines, DoNotCompile: bSkipCompile, ForceCompile: bForceCompile); } // Setup the module map foreach (FileReference ModuleFile in ModuleFileToContext.Keys) { string ModuleName = ModuleFile.GetFileNameWithoutAnyExtensions(); if (!ModuleNameToModuleFile.ContainsKey(ModuleName)) { ModuleNameToModuleFile.Add(ModuleName, ModuleFile); } } // Setup the target map foreach (FileReference TargetFile in TargetFiles) { string TargetName = TargetFile.GetFileNameWithoutAnyExtensions(); if (!TargetNameToTargetFile.ContainsKey(TargetName)) { TargetNameToTargetFile.Add(TargetName, TargetFile); } } // Write any deprecation warnings for methods overriden 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) { if (Parent != null) { 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 == null) ? null : 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 == null) ? null : 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 == null) ? null : 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.GetTestedTargetName(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 BuildException("Could not find definition for module '{0}', (referenced via {1})", 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 BuildException("Found multiple platform group overrides ({0} and {1}) for module {2} 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 BuildException("Platform module file {0} is not allowed (only platforms '{1}', 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 != null ? PlatformRulesObjectType : BaseRulesObjectType; if (RulesObjectType == null) { throw new BuildException("Expecting to find a type to be declared in a module rules named '{0}' in {1}. This type must derive from the 'ModuleRules' type defined by Unreal Build Tool.", ModuleTypeName, CompiledAssembly?.FullName); } // Create an instance of the module's rules object try { // Create an uninitialized ModuleRules object and set some defaults. ModuleRules RulesObject = (ModuleRules)FormatterServices.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; // 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.DirectoriesForModuleSubClasses = new Dictionary(); RulesObject.SubclassRules = new List(); while (SubType != null && 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 BuildException("{0} is not derived from {1}", RulesObjectType.Name, BaseRulesObjectType.Name); } SubType = SubType.BaseType; } } // Call the constructor ConstructorInfo? Constructor = RulesObjectType.GetConstructor(new Type[] { typeof(ReadOnlyTargetRules) }); if(Constructor == null) { throw new BuildException("No valid constructor found for {0}.", ModuleName); } Constructor.Invoke(RulesObject, new object[] { Target }); if (Target.IsTestTarget && !RulesObject.IsTestModule) { if (!Target.ExplicitTestsTarget) { if (Target.LaunchModuleName != null && ModuleName == TargetDescriptor.GetTestedTargetName(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 BuildException(Ex, "Unable to instantiate module '{0}': {1}\n(referenced via {2})", ModuleName, MessageEx.ToString(), ReferenceChain); } } /// /// Construct an instance of the given target rules /// /// Type name of the target rules /// Target configuration information to pass to the constructor /// Logger for output /// If building a low level tests target /// Instance of the corresponding TargetRules protected TargetRules CreateTargetRulesInstance(string TypeName, TargetInfo TargetInfo, ILogger Logger, bool IsTestTarget = false) { // The build module must define a type named 'Target' that derives from our 'TargetRules' type. Type? BaseRulesType = CompiledAssembly?.GetType(TypeName); if (BaseRulesType == null) { throw new BuildException("Expecting to find a type to be declared in a target rules named '{0}'. This type must derive from the 'TargetRules' type defined by Unreal Build Tool.", TypeName); } // 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 BuildException("Found multiple platform group overrides ({0} and {1}) for rules {2} 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 BuildException("Expecting {0} to be a specialization of {1}", 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, DefaultBuildSettings, Logger); // 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 BuildException("TargetRules.LinkType should be inferred from TargetType"); } // Set the default value for whether to use the shared build environment if(Rules.BuildEnvironment == TargetBuildEnvironment.Unique && Unreal.IsEngineInstalled()) { throw new BuildException("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.bDisableDebugInfo = false; Rules.bOmitPCDebugInfoInDevelopment = false; } // Setup the malloc profiler if (Rules.bUseMallocProfiler) { Rules.bOmitFramePointers = false; Rules.GlobalDefinitions.Add("USE_MALLOC_PROFILER=1"); } // 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 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(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 UEBuildPlatform Platform = UEBuildPlatform.GetBuildPlatform(Rules.Platform); Platform.ValidateTarget(Rules); // Some platforms may *require* monolithic compilation... if (Rules.LinkType != TargetLinkType.Monolithic && UEBuildPlatform.PlatformRequiresMonolithicBuilds(Rules.Platform, Rules.Configuration)) { throw new BuildException(String.Format("{0}: {1} does not support modular builds", Rules.Name, Rules.Platform)); } if (IsTestTarget) { Rules = new TestTargetRules(Rules, TargetInfo); } return Rules; } /// /// Creates a target rules object for the specified target name. /// /// Name of the target /// Platform being compiled /// Configuration being compiled /// Architecture being built /// Path to the project file for this target /// Command line arguments for this target /// /// If building a low level test target /// The build target rules for the specified target public TargetRules CreateTargetRules(string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string Architecture, FileReference? ProjectFile, CommandLineArguments? Arguments, ILogger Logger, bool IsTestTarget = false) { if (IsTestTarget) { TargetName = TargetDescriptor.GetTestedTargetName(TargetName); } bool bFoundTargetName = TargetNameToTargetFile.ContainsKey(TargetName); if (bFoundTargetName == false) { if (Parent == null) { // throw new BuildException("Couldn't find target rules file for target '{0}' in rules assembly '{1}'.", TargetName, RulesAssembly.FullName); string ExceptionMessage = "Couldn't find target rules file for target '"; ExceptionMessage += TargetName; ExceptionMessage += "' in rules assembly '"; ExceptionMessage += CompiledAssembly?.FullName; ExceptionMessage += "'." + Environment.NewLine; ExceptionMessage += "Location: " + CompiledAssembly?.Location + Environment.NewLine; ExceptionMessage += "Target rules found:" + Environment.NewLine; foreach (KeyValuePair entry in TargetNameToTargetFile) { ExceptionMessage += "\t" + entry.Key + " - " + entry.Value + Environment.NewLine; } throw new BuildException(ExceptionMessage); } else { return Parent.CreateTargetRules(TargetName, Platform, Configuration, Architecture, ProjectFile, Arguments, Logger, IsTestTarget); } } // 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. return CreateTargetRulesInstance(TargetTypeName, new TargetInfo(TargetName, Platform, Configuration, Architecture, ProjectFile, Arguments), Logger, IsTestTarget); } /// /// 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 /// The architecture 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, string Architecture, 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, Architecture, ProjectFile, null), Logger); if(Rules.Type == Type) { 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 BuildException("Unable to find target of type '{0}' for project '{1}'", Type, ProjectFile); } else { return Parent.GetTargetNameByType(Type, Platform, Configuration, Architecture, 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 BuildException("Found multiple targets with TargetType={0}: {1}.\nSpecify a default with a {2} 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 var Context)) { return Context; } } return null; } } }