// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace UnrealBuildBase { public class Rules { /// /// Enum for types of rules files. Should match extensions in RulesFileExtensions. /// public enum RulesFileType { /// /// .build.cs files /// Module, /// /// .target.cs files /// Target, /// /// .automation.csproj files /// AutomationModule, /// /// .ubtplugin.csproj files /// UbtPlugin, } /// /// Cached list of rules files in each directory of each type /// class RulesFileCache { public List ModuleRules = new List(); public List TargetRules = new List(); public List AutomationModules = new List(); public List UbtPlugins = new List(); } /// Map of root folders to a cached list of all UBT-related source files in that folder or any of its sub-folders. /// We cache these file names so we can avoid searching for them later on. static Dictionary RootFolderToRulesFileCache = new Dictionary(); /// /// /// /// /// /// /// /// /// Whether to include targets generated by UAT to accomodate content-only projects that need to be compiled to include plugins /// public static List FindAllRulesSourceFiles(RulesFileType RulesFileType, List? GameFolders, List? ForeignPlugins, List? AdditionalSearchPaths, bool bIncludeEngine = true, bool bIncludeTempTargets = true) { List Folders = new List(); // Add all engine source (including third party source) if (bIncludeEngine) { Folders.AddRange(Unreal.GetExtensionDirs(Unreal.EngineDirectory, "Source")); } // @todo plugin: Disallow modules from including plugin modules as dependency modules? (except when the module is part of that plugin) // Get all the root folders for plugins List RootFolders = new List(); if (bIncludeEngine) { RootFolders.AddRange(Unreal.GetExtensionDirs(Unreal.EngineDirectory)); } if (GameFolders != null) { RootFolders.AddRange(GameFolders.SelectMany(x => Unreal.GetExtensionDirs(x))); } // Find all the plugin source directories foreach (DirectoryReference RootFolder in RootFolders) { DirectoryReference PluginsFolder = DirectoryReference.Combine(RootFolder, "Plugins"); foreach (FileReference PluginFile in PluginsBase.EnumeratePlugins(PluginsFolder)) { Folders.Add(DirectoryReference.Combine(PluginFile.Directory, "Source")); } } // Add all the extra plugin folders if (ForeignPlugins != null) { foreach (FileReference ForeignPlugin in ForeignPlugins) { Folders.Add(DirectoryReference.Combine(ForeignPlugin.Directory, "Source")); } } // Add in the game folders to search if (GameFolders != null) { foreach (DirectoryReference GameFolder in GameFolders) { Folders.AddRange(Unreal.GetExtensionDirs(GameFolder, "Source")); if (bIncludeTempTargets) { DirectoryReference GameIntermediateSourceFolder = DirectoryReference.Combine(GameFolder, "Intermediate", "Source"); Folders.Add(GameIntermediateSourceFolder); } } } // Process the additional search path, if sent in if (AdditionalSearchPaths != null) { foreach (DirectoryReference AdditionalSearchPath in AdditionalSearchPaths) { if (AdditionalSearchPath != null) { if (DirectoryReference.Exists(AdditionalSearchPath)) { Folders.Add(AdditionalSearchPath); } else { throw new Exception($"Couldn't find AdditionalSearchPath for rules source files '{AdditionalSearchPath}'"); } } } } return FindAllRulesFiles(Folders, RulesFileType); } /// /// Invalidate the cache for the givcen directory /// /// Directory to invalidate public static void InvalidateRulesFileCache(string DirectoryPath) { DirectoryReference Directory = new DirectoryReference(DirectoryPath); RootFolderToRulesFileCache.Remove(Directory); DirectoryLookupCache.InvalidateCachedDirectory(Directory); } /// /// Prefetch multiple directories in parallel /// /// The directories to cache public static void PrefetchRulesFiles(IEnumerable Directories) { ThreadPoolWorkQueue? Queue = null; try { foreach(DirectoryReference Directory in Directories) { if(!RootFolderToRulesFileCache.ContainsKey(Directory)) { RulesFileCache Cache = new RulesFileCache(); RootFolderToRulesFileCache[Directory] = Cache; if(Queue == null) { Queue = new ThreadPoolWorkQueue(); } DirectoryItem DirectoryItem = DirectoryItem.GetItemByDirectoryReference(Directory); Queue.Enqueue(() => FindAllRulesFilesRecursively(DirectoryItem, Cache, Queue)); } } } finally { if(Queue != null) { Queue.Dispose(); Queue = null; } } } /// /// Finds all the rules of the given type under a given directory /// /// Directory to search /// Type of rules to return /// List of rules files of the given type public static IReadOnlyList FindAllRulesFiles(DirectoryReference Directory, RulesFileType Type) { return FindAllRulesFiles(new List { Directory }, Type); } /// /// Finds all the rules of the given type under a given directories /// /// Directories to search /// Type of rules to return /// List of rules files of the given type public static List FindAllRulesFiles(IEnumerable Directories, RulesFileType Type) { List<(DirectoryReference, RulesFileCache)> Caches = new List<(DirectoryReference, RulesFileCache)>(Directories.Count()); using (ThreadPoolWorkQueue Queue = new ThreadPoolWorkQueue()) { foreach (DirectoryReference Directory in Directories) { RulesFileCache? Cache; if (!RootFolderToRulesFileCache.TryGetValue(Directory, out Cache)) { Cache = new RulesFileCache(); Queue.Enqueue(() => FindAllRulesFilesRecursively(DirectoryItem.GetItemByDirectoryReference(Directory), Cache, Queue)); } Caches.Add((Directory, Cache)); } } List Files = new List(); foreach ((DirectoryReference Directory, RulesFileCache Cache) in Caches) { if (!RootFolderToRulesFileCache.ContainsKey(Directory)) { Cache.ModuleRules.Sort((A, B) => A.FullName.CompareTo(B.FullName)); Cache.TargetRules.Sort((A, B) => A.FullName.CompareTo(B.FullName)); Cache.AutomationModules.Sort((A, B) => A.FullName.CompareTo(B.FullName)); Cache.UbtPlugins.Sort((A, B) => A.FullName.CompareTo(B.FullName)); RootFolderToRulesFileCache[Directory] = Cache; } // Get the list of files of the type we're looking for if (Type == RulesFileType.Module) { Files.AddRange(Cache.ModuleRules); } else if (Type == RulesFileType.Target) { Files.AddRange(Cache.TargetRules); } else if (Type == RulesFileType.AutomationModule) { Files.AddRange(Cache.AutomationModules); } else if (Type == RulesFileType.UbtPlugin) { Files.AddRange(Cache.UbtPlugins); } else { throw new Exception($"Unhandled rules type: {Type}"); } } return Files; } /// /// Search through a directory tree for any rules files /// /// The root directory to search from /// Receives all the discovered rules files /// Queue for adding additional tasks to private static void FindAllRulesFilesRecursively(DirectoryItem Directory, RulesFileCache Cache, ThreadPoolWorkQueue Queue) { // Scan all the files in this directory bool bSearchSubFolders = true; foreach (FileItem File in Directory.EnumerateFiles()) { if (File.HasExtension(".build.cs")) { lock(Cache.ModuleRules) { Cache.ModuleRules.Add(File.Location); } bSearchSubFolders = false; } else if (File.HasExtension(".target.cs")) { lock(Cache.TargetRules) { Cache.TargetRules.Add(File.Location); } } else if (File.HasExtension(".automation.csproj")) { lock(Cache.AutomationModules) { Cache.AutomationModules.Add(File.Location); } bSearchSubFolders = false; } else if (File.HasExtension(".ubtplugin.csproj")) { lock(Cache.UbtPlugins) { Cache.UbtPlugins.Add(File.Location); } bSearchSubFolders = false; } } // If we didn't find anything to stop the search, search all the subdirectories too if (bSearchSubFolders) { foreach (DirectoryItem SubDirectory in Directory.EnumerateDirectories()) { Queue.Enqueue(() => FindAllRulesFilesRecursively(SubDirectory, Cache, Queue)); } } } } }