// 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;
}
}
}