using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace UnrealBuildTool
{
///
/// Stores information about a compiled rules assembly and the types it contains
///
public class RulesAssembly
{
///
/// The compiled assembly
///
private Assembly CompiledAssembly;
///
/// 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 plugin info.
///
private Dictionary ModuleFileToPluginInfo;
///
/// Cache for whether a module has source code
///
private Dictionary ModuleHasSource = new Dictionary();
///
/// The parent rules assembly that this assembly inherits. Game assemblies inherit the engine assembly, and the engine assembly inherits nothing.
///
private RulesAssembly Parent;
///
/// Constructor. Compiles a rules assembly from the given source files.
///
/// All the plugins included in this assembly
/// List of module files to compile
/// List of target files to compile
/// Mapping of module file to the plugin that contains it
/// The output path for the compiled assembly
/// The parent rules assembly
public RulesAssembly(IReadOnlyList Plugins, List ModuleFiles, List TargetFiles, Dictionary ModuleFileToPluginInfo, FileReference AssemblyFileName, RulesAssembly Parent)
{
this.Plugins = Plugins;
this.ModuleFileToPluginInfo = ModuleFileToPluginInfo;
this.Parent = Parent;
// Find all the source files
List AssemblySourceFiles = new List();
AssemblySourceFiles.AddRange(ModuleFiles);
AssemblySourceFiles.AddRange(TargetFiles);
// Compile the assembly
if (AssemblySourceFiles.Count > 0)
{
CompiledAssembly = DynamicCompilation.CompileAndLoadAssembly(AssemblyFileName, AssemblySourceFiles);
}
// Setup the module map
foreach (FileReference ModuleFile in ModuleFiles)
{
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);
}
Log.TraceWarning("{0}: warning: {1}", 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);
}
Log.TraceWarning("{0}: 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);
}
}
}
}
}
///
/// 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);
}
ModuleNames.AddRange(ModuleNameToModuleFile.Keys);
}
///
/// Tries to get the filename that declared the given type
///
///
///
/// True if the type was found, false otherwise
public bool TryGetFileNameFromType(Type ExistingType, 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 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
/// Compiled module rule info
public ModuleRules CreateModuleRules(string ModuleName, ReadOnlyTargetRules Target)
{
FileReference ModuleFileName;
return CreateModuleRules(ModuleName, Target, out ModuleFileName);
}
///
/// 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
/// The original source file name for the Module.cs file for this module
/// Compiled module rule info
public ModuleRules CreateModuleRules(string ModuleName, ReadOnlyTargetRules Target, out FileReference ModuleFileName)
{
// Currently, we expect the user's rules object type name to be the same as the module name
string ModuleTypeName = ModuleName;
// Make sure the module file is known to us
if (!ModuleNameToModuleFile.TryGetValue(ModuleName, out ModuleFileName))
{
if (Parent == null)
{
throw new MissingModuleException(ModuleName);
}
else
{
return Parent.CreateModuleRules(ModuleName, Target, out ModuleFileName);
}
}
// 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);
}
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
ModuleRules RulesObject;
try
{
// Create an uninitialized ModuleRules object and initialize some fields on it while we're still supporting the deprecated parameterless constructor.
RulesObject = (ModuleRules)FormatterServices.GetUninitializedObject(RulesObjectType);
typeof(ModuleRules).GetField("Target").SetValue(RulesObject, Target);
// Call the constructor
ConstructorInfo Constructor = RulesObjectType.GetConstructor(new Type[] { typeof(ReadOnlyTargetRules) });
if(Constructor != null)
{
Constructor.Invoke(RulesObject, new object[] { Target });
}
else
{
ConstructorInfo DeprecatedConstructor = RulesObjectType.GetConstructor(new Type[] { typeof(TargetInfo) });
if(DeprecatedConstructor == null)
{
throw new Exception("No valid constructor found.");
}
DeprecatedConstructor.Invoke(RulesObject, new object[] { new TargetInfo(Target) });
}
}
catch (Exception Ex)
{
throw new BuildException(Ex, "Unable to instantiate instance of '{0}' object type from compiled assembly '{1}'. Unreal Build Tool creates an instance of your module's 'Rules' object in order to find out about your module's requirements. The CLR exception details may provide more information: {2}", ModuleTypeName, CompiledAssembly.FullName, Ex.ToString());
}
// Update the run-time dependencies path to remove $(PluginDir) and replace with a full path. When the receipt is saved it'll be converted to a $(ProjectDir) or $(EngineDir) equivalent.
foreach (RuntimeDependency Dependency in RulesObject.RuntimeDependencies)
{
const string PluginDirVariable = "$(PluginDir)";
if (Dependency.Path.StartsWith(PluginDirVariable, StringComparison.InvariantCultureIgnoreCase))
{
PluginInfo Plugin;
if (ModuleFileToPluginInfo.TryGetValue(ModuleFileName, out Plugin))
{
Dependency.Path = Plugin.Directory + Dependency.Path.Substring(PluginDirVariable.Length);
}
}
}
return RulesObject;
}
///
/// Determines whether the given module name is a game module (as opposed to an engine module)
///
public bool IsGameModule(string InModuleName)
{
FileReference ModuleFileName = GetModuleFileName(InModuleName);
return (ModuleFileName != null && !ModuleFileName.IsUnderDirectory(UnrealBuildTool.EngineDirectory));
}
///
/// Construct an instance of the given target rules
///
/// Type name of the target rules
/// Target configuration information to pass to the constructor
/// Instance of the corresponding TargetRules
protected TargetRules CreateTargetRulesInstance(string TypeName, TargetInfo TargetInfo)
{
// The build module must define a type named 'Target' that derives from our 'TargetRules' type.
Type RulesObjectType = CompiledAssembly.GetType(TypeName);
if (RulesObjectType == 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);
}
// Create an instance of the module's rules object. To avoid breaking backwards compatibility and requiring this information be passed through to the base class
// constructor, we construct these objects and call the constructor manually for now.
TargetRules RulesObject = (TargetRules)FormatterServices.GetUninitializedObject(RulesObjectType);
typeof(TargetRules).GetField("Name").SetValue(RulesObject, TargetInfo.Name);
typeof(TargetRules).GetField("Platform").SetValue(RulesObject, TargetInfo.Platform);
typeof(TargetRules).GetField("Configuration").SetValue(RulesObject, TargetInfo.Configuration);
typeof(TargetRules).GetField("Architecture").SetValue(RulesObject, TargetInfo.Architecture);
typeof(TargetRules).GetField("ProjectFile").SetValue(RulesObject, TargetInfo.ProjectFile);
// Find the constructor
ConstructorInfo Constructor = RulesObjectType.GetConstructor(new Type[] { typeof(TargetInfo) });
if(Constructor == null)
{
throw new BuildException("No constructor found on {0} which takes an argument of type TargetInfo.", RulesObjectType.Name);
}
// Invoke the regular constructor
try
{
Constructor.Invoke(RulesObject, new object[] { TargetInfo });
}
catch (Exception Ex)
{
throw new BuildException(Ex, "Unable to instantiate instance of '{0}' object type from compiled assembly '{1}'. Unreal Build Tool creates an instance of your module's 'Rules' object in order to find out about your module's requirements. The CLR exception details may provide more information: {2}", TypeName, Path.GetFileNameWithoutExtension(CompiledAssembly.Location), Ex.ToString());
}
RulesObject.SetOverridesForTargetType();
return RulesObject;
}
///
/// Creates a target rules object for the specified target name.
///
/// Name of the target
/// The platform that the target is being built for
/// The configuration the target is being built for
/// The architecture the target is being built for
/// The project containing the target being built
/// Whether this is an editor recompile, where we need to guess the name of the editor target
/// The build target rules for the specified target
public TargetRules CreateTargetRules(string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string Architecture, FileReference ProjectFile, bool bInEditorRecompile)
{
FileReference TargetFileName;
return CreateTargetRules(TargetName, Platform, Configuration, Architecture, ProjectFile, bInEditorRecompile, out TargetFileName);
}
///
/// 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
/// Whether this is an editor recompile, where we need to guess the name of the editor target
/// The original source file name of the Target.cs file for this target
/// The build target rules for the specified target
public TargetRules CreateTargetRules(string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string Architecture, FileReference ProjectFile, bool bInEditorRecompile, out FileReference TargetFileName)
{
// Make sure the target file is known to us
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, bInEditorRecompile, out TargetFileName);
}
}
// Return the target file name to the caller
TargetFileName = TargetNameToTargetFile[TargetName];
// 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 RulesObject = CreateTargetRulesInstance(TargetTypeName, new TargetInfo(TargetName, Platform, Configuration, Architecture, ProjectFile));
if (bInEditorRecompile)
{
// Make sure this is an editor module.
if (RulesObject != null)
{
if (RulesObject.Type != TargetType.Editor)
{
// Not the editor... determine the editor project
string TargetSourceFolderString = TargetFileName.FullName;
Int32 SourceFolderIndex = -1;
if (Utils.IsRunningOnMono)
{
TargetSourceFolderString = TargetSourceFolderString.Replace("\\", "/");
SourceFolderIndex = TargetSourceFolderString.LastIndexOf("/Source/", StringComparison.InvariantCultureIgnoreCase);
}
else
{
TargetSourceFolderString = TargetSourceFolderString.Replace("/", "\\");
SourceFolderIndex = TargetSourceFolderString.LastIndexOf("\\Source\\", StringComparison.InvariantCultureIgnoreCase);
}
if (SourceFolderIndex != -1)
{
DirectoryReference TargetSourceFolder = new DirectoryReference(TargetSourceFolderString.Substring(0, SourceFolderIndex + 7));
foreach (KeyValuePair CheckEntry in TargetNameToTargetFile)
{
if (CheckEntry.Value.IsUnderDirectory(TargetSourceFolder))
{
if (CheckEntry.Key.Equals(TargetName, StringComparison.InvariantCultureIgnoreCase) == false)
{
// We have found a target in the same source folder that is not the original target found.
// See if it is the editor project
string CheckTargetTypeName = CheckEntry.Key + "Target";
TargetRules CheckRulesObject = CreateTargetRulesInstance(CheckTargetTypeName, new TargetInfo(CheckEntry.Key, Platform, Configuration, Architecture, ProjectFile));
if (CheckRulesObject != null)
{
if (CheckRulesObject.Type == TargetType.Editor)
{
// Found it
// NOTE: This prevents multiple Editor targets from co-existing...
RulesObject = CheckRulesObject;
break;
}
}
}
}
}
}
}
}
}
return RulesObject;
}
///
/// 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 PluginInfo associated with a given module file
///
/// The module to search for
/// The matching plugin info, or null.
/// True if the module belongs to a plugin
public bool TryGetPluginForModule(FileReference ModuleFile, out PluginInfo Plugin)
{
if (ModuleFileToPluginInfo.TryGetValue(ModuleFile, out Plugin))
{
return true;
}
else
{
return (Parent == null) ? false : Parent.TryGetPluginForModule(ModuleFile, out Plugin);
}
}
///
/// Determines if a module in this rules assembly has source code.
///
/// Name of the module to check
/// True if the module has source files, false if the module was not found, or does not have source files.
public bool DoesModuleHaveSource(string ModuleName)
{
FileReference ModuleFile;
if (ModuleNameToModuleFile.TryGetValue(ModuleName, out ModuleFile))
{
bool HasSource;
if (!ModuleHasSource.TryGetValue(ModuleFile, out HasSource))
{
foreach (string FileName in Directory.EnumerateFiles(ModuleFile.Directory.FullName, "*.cpp", SearchOption.AllDirectories))
{
HasSource = true;
break;
}
ModuleHasSource.Add(ModuleFile, HasSource);
}
return HasSource;
}
return (Parent == null) ? false : Parent.DoesModuleHaveSource(ModuleName);
}
}
}