// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Linq; using Tools.DotNETCommon; namespace UnrealBuildTool { /// /// Where a plugin was loaded from /// public enum PluginLoadedFrom { /// /// Plugin is built-in to the engine /// Engine, /// /// Project-specific plugin, stored within a game project directory /// Project } /// /// Where a plugin was loaded from. The order of this enum is important; in the case of name collisions, larger-valued types will take precedence. Plugins of the same type may not be duplicated. /// public enum PluginType { /// /// Plugin is built-in to the engine /// Engine, /// /// Enterprise plugin /// Enterprise, /// /// Project-specific plugin, stored within a game project directory /// Project, /// /// Plugin found in an external directory (found in an AdditionalPluginDirectory listed in the project file, or referenced on the command line) /// External, /// /// Project-specific mod plugin /// Mod, } /// /// Information about a single plugin /// [DebuggerDisplay("\\{{File}\\}")] public class PluginInfo { /// /// Plugin name /// public readonly string Name; /// /// Path to the plugin /// public readonly FileReference File; /// /// Path to the plugin's root directory /// public readonly DirectoryReference Directory; /// /// The plugin descriptor /// public PluginDescriptor Descriptor; /// /// The type of this plugin /// public PluginType Type; /// /// Constructs a PluginInfo object /// /// Path to the plugin descriptor /// The type of this plugin public PluginInfo(FileReference InFile, PluginType InType) { Name = Path.GetFileNameWithoutExtension(InFile.FullName); File = InFile; Directory = File.Directory; Descriptor = PluginDescriptor.FromFile(File); Type = InType; } /// /// Determines whether the plugin should be enabled by default /// public bool EnabledByDefault { get { if(Descriptor.bEnabledByDefault.HasValue) { return Descriptor.bEnabledByDefault.Value; } else { return (LoadedFrom == PluginLoadedFrom.Project); } } } /// /// Determines where the plugin was loaded from /// public PluginLoadedFrom LoadedFrom { get { if(Type == PluginType.Engine || Type == PluginType.Enterprise) { return PluginLoadedFrom.Engine; } else { return PluginLoadedFrom.Project; } } } } /// /// Class for enumerating plugin metadata /// public static class Plugins { /// /// Cache of plugins under each directory /// static Dictionary> PluginInfoCache = new Dictionary>(); /// /// Cache of plugin filenames under each directory /// static Dictionary> PluginFileCache = new Dictionary>(); /// /// Filters the list of plugins to ensure that any game plugins override engine plugins with the same name, and otherwise that no two /// plugins with the same name exist. /// /// List of plugins to filter /// Filtered list of plugins in the original order public static IEnumerable FilterPlugins(IEnumerable Plugins) { Dictionary NameToPluginInfo = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach(PluginInfo Plugin in Plugins) { PluginInfo ExistingPluginInfo; if(!NameToPluginInfo.TryGetValue(Plugin.Name, out ExistingPluginInfo)) { NameToPluginInfo.Add(Plugin.Name, Plugin); } else if(Plugin.Type > ExistingPluginInfo.Type) { NameToPluginInfo[Plugin.Name] = Plugin; } else if(Plugin.Type == ExistingPluginInfo.Type) { throw new BuildException(String.Format("Found '{0}' plugin in two locations ({1} and {2}). Plugin names must be unique.", Plugin.Name, ExistingPluginInfo.File, Plugin.File)); } } return Plugins.Where(x => NameToPluginInfo[x.Name] == x); } /// /// Read all the plugins available to a given project /// /// Path to the engine directory /// Path to the project file (or null) /// List of additional directories to scan for available plugins /// Sequence of PluginInfo objects, one for each discovered plugin public static List ReadAvailablePlugins(DirectoryReference EngineDirectoryName, FileReference ProjectFileName, string[] AdditionalDirectories) { List Plugins = new List(); // Read all the engine plugins Plugins.AddRange(ReadEnginePlugins(EngineDirectoryName)); // Read all the project plugins if (ProjectFileName != null) { Plugins.AddRange(ReadProjectPlugins(ProjectFileName.Directory)); } // Scan for shared plugins in project specified additional directories if(AdditionalDirectories != null) { foreach (string AdditionalDirectory in AdditionalDirectories) { DirectoryReference DirRef = DirectoryReference.Combine(ProjectFileName.Directory, AdditionalDirectory); Plugins.AddRange(ReadPluginsFromDirectory(DirRef, PluginType.External)); } } return Plugins; } /// /// Enumerates all the plugin files available to the given project /// /// Path to the project file /// List of project files public static IEnumerable EnumeratePlugins(FileReference ProjectFile) { DirectoryReference EnginePluginsDir = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Plugins"); foreach(FileReference PluginFile in EnumeratePlugins(EnginePluginsDir)) { yield return PluginFile; } DirectoryReference EnterprisePluginsDir = DirectoryReference.Combine(UnrealBuildTool.EnterpriseDirectory, "Plugins"); foreach(FileReference PluginFile in EnumeratePlugins(EnterprisePluginsDir)) { yield return PluginFile; } if(ProjectFile != null) { DirectoryReference ProjectPluginsDir = DirectoryReference.Combine(ProjectFile.Directory, "Plugins"); foreach(FileReference PluginFile in EnumeratePlugins(ProjectPluginsDir)) { yield return PluginFile; } DirectoryReference ProjectModsDir = DirectoryReference.Combine(ProjectFile.Directory, "Mods"); foreach(FileReference PluginFile in EnumeratePlugins(ProjectModsDir)) { yield return PluginFile; } } } /// /// Read all the plugin descriptors under the given engine directory /// /// The parent directory to look in. /// Sequence of the found PluginInfo object. public static IReadOnlyList ReadEnginePlugins(DirectoryReference EngineDirectory) { DirectoryReference PluginsDir = DirectoryReference.Combine(EngineDirectory, "Plugins"); return ReadPluginsFromDirectory(PluginsDir, PluginType.Engine); } /// /// Read all the plugin descriptors under the given enterprise directory /// /// The parent directory to look in. /// Sequence of the found PluginInfo object. public static IReadOnlyList ReadEnterprisePlugins(DirectoryReference EnterpriseDirectory) { DirectoryReference PluginsDir = DirectoryReference.Combine(EnterpriseDirectory, "Plugins"); return ReadPluginsFromDirectory(PluginsDir, PluginType.Enterprise); } /// /// Read all the plugin descriptors under the given project directory /// /// The parent directory to look in. /// Sequence of the found PluginInfo object. public static IReadOnlyList ReadProjectPlugins(DirectoryReference ProjectDirectory) { List Plugins = new List(); Plugins.AddRange(ReadPluginsFromDirectory(DirectoryReference.Combine(ProjectDirectory, "Plugins"), PluginType.Project)); Plugins.AddRange(ReadPluginsFromDirectory(DirectoryReference.Combine(ProjectDirectory, "Mods"), PluginType.Mod)); return Plugins.AsReadOnly(); } /// /// Read all of the plugins found in the project specified additional plugin directories /// /// The list of additional directories to scan /// List of the found PluginInfo objects public static IReadOnlyList ReadAdditionalPlugins(DirectoryReference AdditionalDirectory) { return ReadPluginsFromDirectory(AdditionalDirectory, PluginType.External); } /// /// Read all the plugin descriptors under the given directory /// /// The parent directory to look in. /// The plugin type /// Sequence of the found PluginInfo object. public static IReadOnlyList ReadPluginsFromDirectory(DirectoryReference ParentDirectory, PluginType Type) { List Plugins; if (!PluginInfoCache.TryGetValue(ParentDirectory, out Plugins)) { Plugins = new List(); foreach (FileReference PluginFileName in EnumeratePlugins(ParentDirectory)) { PluginInfo Plugin = new PluginInfo(PluginFileName, Type); Plugins.Add(Plugin); } PluginInfoCache.Add(ParentDirectory, Plugins); } return Plugins; } /// /// Find paths to all the plugins under a given parent directory (recursively) /// /// Parent directory to look in. Plugins will be found in any *subfolders* of this directory. public static IEnumerable EnumeratePlugins(DirectoryReference ParentDirectory) { List FileNames; if (!PluginFileCache.TryGetValue(ParentDirectory, out FileNames)) { FileNames = new List(); DirectoryItem ParentDirectoryItem = DirectoryItem.GetItemByDirectoryReference(ParentDirectory); if (ParentDirectoryItem.Exists) { using(ThreadPoolWorkQueue Queue = new ThreadPoolWorkQueue()) { EnumeratePluginsInternal(ParentDirectoryItem, FileNames, Queue); } } // Sort the filenames to ensure that the plugin order is deterministic; otherwise response files will change with each build. FileNames = FileNames.OrderBy(x => x.FullName, StringComparer.OrdinalIgnoreCase).ToList(); PluginFileCache.Add(ParentDirectory, FileNames); } return FileNames; } /// /// Find paths to all the plugins under a given parent directory (recursively) /// /// Parent directory to look in. Plugins will be found in any *subfolders* of this directory. /// List of filenames. Will have all the discovered .uplugin files appended to it. /// Queue for tasks to be executed static void EnumeratePluginsInternal(DirectoryItem ParentDirectory, List FileNames, ThreadPoolWorkQueue Queue) { foreach (DirectoryItem ChildDirectory in ParentDirectory.EnumerateDirectories()) { bool bSearchSubDirectories = true; foreach (FileItem PluginFile in ChildDirectory.EnumerateFiles()) { if(PluginFile.HasExtension(".uplugin")) { lock(FileNames) { FileNames.Add(PluginFile.Location); } bSearchSubDirectories = false; } } if (bSearchSubDirectories) { Queue.Enqueue(() => EnumeratePluginsInternal(ChildDirectory, FileNames, Queue)); } } } /// /// Determine if a plugin is enabled for a given project /// /// The project to check. May be null. /// Information about the plugin /// The target platform /// The target configuration /// /// True if the plugin should be enabled for this project public static bool IsPluginEnabledForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform, UnrealTargetConfiguration TargetConfiguration, TargetType Target) { if (!Plugin.Descriptor.SupportsTargetPlatform(Platform)) { return false; } bool bEnabled = Plugin.EnabledByDefault; if (Project != null && Project.Plugins != null) { foreach (PluginReferenceDescriptor PluginReference in Project.Plugins) { if (String.Compare(PluginReference.Name, Plugin.Name, true) == 0 && !PluginReference.bOptional) { bEnabled = PluginReference.IsEnabledForPlatform(Platform) && PluginReference.IsEnabledForTargetConfiguration(TargetConfiguration) && PluginReference.IsEnabledForTarget(Target); } } } return bEnabled; } } }