// 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 { /// /// Compiles and loads script assemblies. /// public static class ScriptManager { private static Dictionary ScriptCommands; public static readonly HashSet AllScriptAssemblies = new HashSet(); public static HashSet BuildProducts { get; private set; } = new HashSet(); /// /// Populates ScriptCommands /// static void EnumerateScriptCommands(ILogger Logger) { ScriptCommands = new Dictionary(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); } } } /// /// Enumerate the contents of the output directories /// static void EnumerateBuildProducts() { BuildProducts = new HashSet(); HashSet OutputDirs = new HashSet(); 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)); } } } } } /// /// Loads all precompiled assemblies (DLLs that end with *Scripts.dll). /// /// /// /// List of compiled assemblies public static void LoadScriptAssemblies(IEnumerable 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 Commands { get { return ScriptCommands; } } } }