Files
UnrealEngine/Engine/Source/Programs/AutomationTool/AutomationUtils/ScriptManager.cs
2025-05-18 13:04:45 +08:00

144 lines
4.6 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using UnrealBuildTool;
using EpicGames.Core;
using System.Threading.Tasks;
using UnrealBuildBase;
using Microsoft.Extensions.Logging;
using static AutomationTool.CommandUtils;
namespace AutomationTool
{
/// <summary>
/// Compiles and loads script assemblies.
/// </summary>
public static class ScriptManager
{
private static Dictionary<string, Type> ScriptCommands;
public static readonly HashSet<Assembly> AllScriptAssemblies = new HashSet<Assembly>();
public static HashSet<FileReference> BuildProducts { get; private set; } = new HashSet<FileReference>();
/// <summary>
/// Populates ScriptCommands
/// </summary>
static void EnumerateScriptCommands(ILogger Logger)
{
ScriptCommands = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
foreach (Assembly CompiledScripts in AllScriptAssemblies)
{
try
{
foreach (Type ClassType in CompiledScripts.GetTypes())
{
if (ClassType.IsSubclassOf(typeof(BuildCommand)) && ClassType.IsAbstract == false)
{
if (ScriptCommands.ContainsKey(ClassType.Name) == false)
{
ScriptCommands.Add(ClassType.Name, ClassType);
}
else
{
bool IsSame = string.Equals(ClassType.AssemblyQualifiedName, ScriptCommands[ClassType.Name].AssemblyQualifiedName);
if (IsSame == false)
{
Logger.LogWarning("Unable to add command {ClassName} twice. Previous: {OldQualifiedName}, Current: {NewQualifiedName}", ClassType.Name,
ClassType.AssemblyQualifiedName, ScriptCommands[ClassType.Name].AssemblyQualifiedName);
}
}
}
}
}
catch (ReflectionTypeLoadException LoadEx)
{
foreach (Exception SubEx in LoadEx.LoaderExceptions)
{
Logger.LogWarning(SubEx, "Got type loader exception: {SubEx}", SubEx.ToString());
}
throw new AutomationException("Failed to add commands from {0}. {1}", CompiledScripts, LoadEx);
}
catch (Exception Ex)
{
throw new AutomationException("Failed to add commands from {0}. {1}", CompiledScripts, Ex);
}
}
}
/// <summary>
/// Enumerate the contents of the output directories
/// </summary>
static void EnumerateBuildProducts()
{
BuildProducts = new HashSet<FileReference>();
HashSet<DirectoryReference> OutputDirs = new HashSet<DirectoryReference>();
foreach (Assembly CompiledAssembly in AllScriptAssemblies)
{
DirectoryReference AssemblyDirectory = FileReference.FromString(CompiledAssembly.Location).Directory;
AssemblyDirectory = DirectoryReference.FindCorrectCase(AssemblyDirectory);
if (OutputDirs.Add(AssemblyDirectory))
{
BuildProducts.UnionWith(DirectoryReference.EnumerateFiles(AssemblyDirectory));
// If there's "runtimes" sub-directory, include all the files contained therein
foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(AssemblyDirectory))
{
if (String.Equals(SubDir.GetDirectoryName(), "runtimes"))
{
BuildProducts.UnionWith(DirectoryReference.EnumerateFiles(SubDir, "*", SearchOption.AllDirectories));
}
}
}
}
}
/// <summary>
/// Loads all precompiled assemblies (DLLs that end with *Scripts.dll).
/// </summary>
/// <param name="AssemblyPaths"></param>
/// <param name="Logger"></param>
/// <returns>List of compiled assemblies</returns>
public static void LoadScriptAssemblies(IEnumerable<FileReference> AssemblyPaths, ILogger Logger)
{
foreach (FileReference AssemblyLocation in AssemblyPaths)
{
// Load the assembly into our app domain
Logger.LogDebug("Loading script DLL: {AssemblyLocation}", AssemblyLocation);
try
{
AssemblyUtils.AddFileToAssemblyCache(AssemblyLocation.FullName);
// Add a resolver for the Assembly directory, so that its dependencies may be found alongside it
AssemblyUtils.InstallRecursiveAssemblyResolver(AssemblyLocation.Directory.FullName);
Assembly Assembly = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(AssemblyLocation.FullName));
AllScriptAssemblies.Add(Assembly);
}
catch (Exception Ex)
{
throw new AutomationException("Failed to load script DLL: {0}: {1}", AssemblyLocation, Ex.Message);
}
}
Platform.InitializePlatforms(AllScriptAssemblies);
EnumerateScriptCommands(Logger);
EnumerateBuildProducts();
}
public static Dictionary<string, Type> Commands
{
get { return ScriptCommands; }
}
}
}