// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnrealBuildBase { public class PluginsBase { /// /// Cache of plugin filenames under each directory /// static ConcurrentDictionary>> PluginFileCache = new(); /// /// Cache of all UBT plugins that have been built. When generating projects, there is a good chance that a UBT project /// might need to be rebuilt. /// static Dictionary UbtPluginFileCache = []; /// /// Invalidate cached plugin data so that we can pickup new things /// Warning: Will make subsequent plugin lookups and directory scans slow until the caches are repopulated /// public static void InvalidateCache_SLOW() { PluginFileCache = new(); DirectoryItem.ResetAllCachedInfo_SLOW(); } /// /// Enumerates all the plugin files available to the given project /// /// Path to the project file /// List of project files public static List EnumeratePlugins(FileReference? ProjectFile) { List BaseDirs = [.. Unreal.GetExtensionDirs(Unreal.EngineDirectory, "Plugins")]; if(ProjectFile != null) { BaseDirs.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Plugins")); BaseDirs.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Mods")); } return BaseDirs.SelectMany(x => EnumeratePlugins(x)).ToList(); } /// /// 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 List EnumeratePlugins(DirectoryReference ParentDirectory) { return PluginFileCache.GetOrAdd(ParentDirectory, new Lazy>(() => { List FileNames = []; DirectoryItem ParentDirectoryItem = DirectoryItem.GetItemByDirectoryReference(ParentDirectory); if (ParentDirectoryItem.Exists) { ParallelQueue Queue = new(); EnumeratePluginsInternal(ParentDirectoryItem, FileNames, Queue); Queue.Drain(); } // 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)]; return FileNames; })).Value; } /// /// 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, ParallelQueue 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; } else if (PluginFile.Name == ".ubtignore") { bSearchSubDirectories = false; } } if (bSearchSubDirectories) { Queue.Enqueue(() => EnumeratePluginsInternal(ChildDirectory, FileNames, Queue)); } } } /// /// Enumerates all the UBT plugin files available to the given project /// /// Path to the project file /// The collection of UBT project files public static List EnumerateUbtPlugins(FileReference? ProjectFile) { List PluginDirectories = [ .. Unreal.GetExtensionDirs(Unreal.EngineDirectory, "Build"), .. Unreal.GetExtensionDirs(Unreal.EngineDirectory, "Source"), .. Unreal.GetExtensionDirs(Unreal.EngineDirectory, "Plugins"), ]; if (ProjectFile != null) { PluginDirectories.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Build")); PluginDirectories.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Source")); PluginDirectories.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Plugins")); PluginDirectories.AddRange(Unreal.GetExtensionDirs(ProjectFile.Directory, "Mods")); } List Plugins = UnrealBuildBase.Rules.FindAllRulesFiles(PluginDirectories, UnrealBuildBase.Rules.RulesFileType.UbtPlugin); Plugins.SortBy(P => P.FullName); return Plugins; } /// /// Build the given collection of plugins /// /// Path to the project file /// Collection of plugins to build /// Collection of constants to add to the projects /// Logger for output /// Collection of built plugins /// True if the plugins compiled public static bool BuildUbtPlugins(FileReference? ProjectFile, IEnumerable FoundPlugins, IEnumerable? DefineConstants, ILogger Logger, out (FileReference ProjectFile, FileReference TargetAssembly)[]? Plugins) { lock (UbtPluginFileCache) { bool bBuildSuccess = true; Plugins = null; // Collect the list of plugins already build List Built = FoundPlugins.Where(P => UbtPluginFileCache.ContainsKey(P)).ToList(); // Collect the list of plugins not build List NotBuilt = FoundPlugins.Where(P => !UbtPluginFileCache.ContainsKey(P)).ToList(); // If we have anything left to build, then build it if (NotBuilt.Count > 0) { if (Unreal.IsEngineInstalled()) { bBuildSuccess = BuildUbtPluginsInternal(PluginSet.EngineOnly, ProjectFile, Built, NotBuilt, DefineConstants, Logger); bBuildSuccess |= BuildUbtPluginsInternal(PluginSet.ProjectOnly, ProjectFile, Built, NotBuilt, DefineConstants, Logger); } else { bBuildSuccess = BuildUbtPluginsInternal(PluginSet.Both, ProjectFile, Built, NotBuilt, DefineConstants, Logger); } } Plugins = [.. Built.Where(P => UbtPluginFileCache.ContainsKey(P)).Select(P => (P, UbtPluginFileCache[P])).OrderBy(X => X.P.FullName)]; return bBuildSuccess; } } private enum PluginSet { EngineOnly, ProjectOnly, Both, } private static bool BuildUbtPluginsInternal(PluginSet PluginSet, FileReference? ProjectFile, List Built, IEnumerable NotBuilt, IEnumerable? DefineConstants, ILogger Logger) { // Collect the work to be done IEnumerable? ToBuild = null; List BaseDirectories = []; CompileScriptModule.BuildFlags BuildFlags = CompileScriptModule.BuildFlags.UseBuildRecords; switch (PluginSet) { case PluginSet.EngineOnly: BaseDirectories.Add(Unreal.EngineDirectory); BuildFlags |= CompileScriptModule.BuildFlags.NoCompile | CompileScriptModule.BuildFlags.ErrorOnMissingTarget; ToBuild = NotBuilt.Where(P => P.IsUnderDirectory(Unreal.EngineDirectory)); break; case PluginSet.ProjectOnly: if (ProjectFile != null) { BaseDirectories.Add(ProjectFile.Directory); ToBuild = NotBuilt.Where(P => !P.IsUnderDirectory(Unreal.EngineDirectory)); } break; case PluginSet.Both: BaseDirectories.Add(Unreal.EngineDirectory); if (ProjectFile != null) { BaseDirectories.Add(ProjectFile.Directory); } ToBuild = NotBuilt; break; } // Just return if there is nothing if (ToBuild == null || !ToBuild.Any() || BaseDirectories.Count == 0) { return true; } // Build the things needing to be built bool bBuildSuccess = true; Dictionary? BuiltPlugins = CompileScriptModule.Build(UnrealBuildBase.Rules.RulesFileType.UbtPlugin, [.. NotBuilt], BaseDirectories, DefineConstants, CompileScriptModule.BuildFlags.UseBuildRecords, out bBuildSuccess, Count => { if (Log.OutputFile != null) { Logger.LogInformation("Building {Count} plugins (see Log '{LogFile}' for more details)", Count, Log.OutputFile); } else { Logger.LogInformation("Building {Count} plugins", Count); } }, Logger ); // Add any built plugins back into the cache foreach (KeyValuePair BuiltPlugin in BuiltPlugins) { Built.Add(BuiltPlugin.Key); UbtPluginFileCache.Add(BuiltPlugin.Key, CompileScriptModule.GetTargetPath(BuiltPlugin)); } return bBuildSuccess; } } }