// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Linq; using System.Reflection; using AutomationTool; using UnrealBuildTool; using EpicGames.Core; using System.Text; using UnrealBuildBase; using Microsoft.Extensions.Logging; using static AutomationTool.CommandUtils; [Help("Builds a plugin, and packages it for distribution")] [Help("Plugin", "Specify the path to the descriptor file for the plugin that should be packaged")] [Help("NoHostPlatform", "Prevent compiling for the editor platform on the host")] [Help("HostPlatforms", "Specify a list of host platforms to build, separated by '+' characters (eg. -HostPlatforms=Win32+Win64). Default is the current host platforms")] [Help("TargetPlatforms", "Specify a list of target platforms to build, separated by '+' characters (eg. -TargetPlatforms=Win32+Win64). Default is all the Rocket target platforms.")] [Help("Package", "The path which the build artifacts should be packaged to, ready for distribution.")] [Help("PackageAppendPluginSubdir", "Package the build artifacts in a subfolder named after the plugin.")] [Help("StrictIncludes", "Disables precompiled headers and unity build in order to check all source files have self-contained headers.")] [Help("EngineDir=", "Root Directory of the engine that will be used to build plugin(s) (optional)")] [Help("Unversioned", "Do not embed the current engine version into the descriptor")] [Help("Architecture_=", "Control architecture to compile for a platform (eg. -Architecture_Mac=arm64+x86). Default is to use UBT defaults for the platform.")] public sealed class BuildPlugin : BuildCommand { const string MacDefaultArchitectures = "arm64+x64"; const string AndroidDefaultArchitectures = "arm64+x64"; string UnrealBuildToolDllRelativePath = @"Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.dll"; FileReference UnrealBuildToolDll; static private Dictionary PlatformToArchitectureMap = new Dictionary(); public override void ExecuteBuild() { // See if an engine dir was specified, fall back on default if not. DirectoryReference EngineDirParam = ParseOptionalDirectoryReferenceParam("EngineDir"); UnrealBuildToolDll = EngineDirParam == null ? UnrealBuild.UnrealBuildToolDll : FileReference.FromString(CommandUtils.CombinePaths(EngineDirParam.ToString(), UnrealBuildToolDllRelativePath)); // Get the plugin filename string PluginParam = ParseParamValue("Plugin"); if(PluginParam == null) { throw new AutomationException("Missing -Plugin=... argument"); } // Check it exists FileReference PluginFile = new FileReference(PluginParam); if (!FileReference.Exists(PluginFile)) { throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName); } // Get the output directory string PackageParam = ParseParamValue("Package"); if (PackageParam == null) { throw new AutomationException("Missing -Package=... argument"); } // Option for verifying that all include directive s bool bStrictIncludes = ParseParam("StrictIncludes"); // Make sure the packaging directory is valid DirectoryReference PackageDir = new DirectoryReference(PackageParam); // If specified, package the build artifacts in a subfolder named after the initial plugin directory. (ie. MyPlugin/Manifest.uplugin will be packaged under {PackageDir}/MyPlufin) if (ParseParam("PackageAppendPluginSubdir")) { string PluginSubfolder = PluginFile.Directory.GetDirectoryName(); PackageDir = DirectoryReference.Combine(PackageDir, PluginSubfolder); } if (PluginFile.IsUnderDirectory(PackageDir)) { throw new AutomationException("Packaged plugin output directory must be different to source"); } if (PackageDir.IsUnderDirectory(DirectoryReference.Combine(Unreal.RootDirectory, "Engine"))) { throw new AutomationException("Output directory for packaged plugin must be outside engine directory"); } // Clear the output directory of existing stuff if (DirectoryReference.Exists(PackageDir)) { CommandUtils.DeleteDirectoryContents(PackageDir.FullName); } else { DirectoryReference.CreateDirectory(PackageDir); } // Create a placeholder FilterPlugin.ini with instructions on how to use it FileReference SourceFilterFile = FileReference.Combine(PluginFile.Directory, "Config", "FilterPlugin.ini"); if (!FileReference.Exists(SourceFilterFile)) { List Lines = new List(); Lines.Add("[FilterPlugin]"); Lines.Add("; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and"); Lines.Add("; may include \"...\", \"*\", and \"?\" wildcards to match directories, files, and individual characters respectively."); Lines.Add(";"); Lines.Add("; Examples:"); Lines.Add("; /README.txt"); Lines.Add("; /Extras/..."); Lines.Add("; /Binaries/ThirdParty/*.dll"); DirectoryReference.CreateDirectory(SourceFilterFile.Directory); CommandUtils.WriteAllLines_NoExceptions(SourceFilterFile.FullName, Lines.ToArray()); } // Create a host project for the plugin. For script generator plugins, we need to have UHT be able to load it, which can only happen if it's enabled in a project. FileReference HostProjectFile = FileReference.Combine(PackageDir, "HostProject", "HostProject.uproject"); FileReference HostProjectPluginFile = CreateHostProject(HostProjectFile, PluginFile); // Read the plugin Logger.LogInformation("Reading plugin from {HostProjectPluginFile}...", HostProjectPluginFile); PluginDescriptor Plugin = PluginDescriptor.FromFile(HostProjectPluginFile); // Get the arguments for the compile StringBuilder AdditionalArgs = new StringBuilder(); if (bStrictIncludes) { Logger.LogInformation("Building with precompiled headers and unity disabled"); AdditionalArgs.Append(" -NoPCH -NoSharedPCH -DisableUnity"); } // check if any architectures were specified foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms()) { // by default, don't specify any architecture when building (unless user requested with -architecture_Platform=), except for // any special cases set at the top of this file string DefaultValue = null; if (Platform == UnrealTargetPlatform.Mac) { DefaultValue = MacDefaultArchitectures; } else if (Platform == UnrealTargetPlatform.Android) { DefaultValue = AndroidDefaultArchitectures; } PlatformToArchitectureMap[Platform] = ParseParamValue($"architecture_{Platform}", DefaultValue); } // Compile the plugin for all the target platforms IReadOnlyList HostPlatforms = GetHostPlatforms(this); List TargetPlatforms = GetTargetPlatforms(this, BuildHostPlatform.Current.Platform); FileReference[] BuildProducts = CompilePlugin(UnrealBuildToolDll, HostProjectFile, HostProjectPluginFile, Plugin, HostPlatforms, TargetPlatforms, AdditionalArgs.ToString()); // Package up the final plugin data PackagePlugin(HostProjectPluginFile, BuildProducts, PackageDir, ParseParam("unversioned"), TargetPlatforms); // Remove the host project if(!ParseParam("NoDeleteHostProject")) { CommandUtils.DeleteDirectory(HostProjectFile.Directory.FullName); } } FileReference CreateHostProject(FileReference HostProjectFile, FileReference PluginFile) { DirectoryReference HostProjectDir = HostProjectFile.Directory; DirectoryReference.CreateDirectory(HostProjectDir); // Create the new project descriptor File.WriteAllText(HostProjectFile.FullName, "{ \"FileVersion\": 3, \"Plugins\": [ { \"Name\": \"" + PluginFile.GetFileNameWithoutExtension() + "\", \"Enabled\": true } ] }"); // Get the plugin directory in the host project, and copy all the files in DirectoryReference HostProjectPluginDir = DirectoryReference.Combine(HostProjectDir, "Plugins", PluginFile.GetFileNameWithoutExtension()); CommandUtils.ThreadedCopyFiles(PluginFile.Directory.FullName, HostProjectPluginDir.FullName); CommandUtils.DeleteDirectory(true, DirectoryReference.Combine(HostProjectPluginDir, "Intermediate").FullName); // Return the path to the plugin file in the host project return FileReference.Combine(HostProjectPluginDir, PluginFile.GetFileName()); } public abstract class TargetPlatform : CommandUtils { [Obsolete("Deprecated in UE5.1; function signature changed")] public abstract void CompilePluginWithUBT(string UBTExe, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, string TargetName, TargetType TargetType, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, List ManifestFileNames, string InAdditionalArgs); public abstract void CompilePluginWithUBT(FileReference UnrealBuildToolDll, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, string TargetName, TargetType TargetType, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, List ManifestFileNames, string InAdditionalArgs); }; private static TargetPlatform GetTargetPlatform( UnrealTargetPlatform Platform ) { // Grab all the non-abstract subclasses of TargetPlatform from the executing assembly. var AvailablePlatformTypes = from Assembly in ScriptManager.AllScriptAssemblies from Type in Assembly.GetTypes() where !Type.IsAbstract && Type.IsAssignableTo(typeof(TargetPlatform)) select Type; var PlatformTypeMap = new Dictionary(); foreach (var Type in AvailablePlatformTypes) { int Index = Type.Name.IndexOf('_'); if (Index == -1) { throw new BuildException("Invalid BuildPluginCommand target platform type found: {0}", Type); } PlatformTypeMap.Add(Type.Name, Type); } var SelectedPlatform = $"BuildPlugin_{Platform.ToString()}"; if (!PlatformTypeMap.ContainsKey(SelectedPlatform)) { return null; } var SelectedType = PlatformTypeMap[SelectedPlatform]; TargetPlatform TargetPlatform = (TargetPlatform)Activator.CreateInstance(SelectedType); if (TargetPlatform == null) { throw new BuildException("The target platform \"{0}\" could not be constructed.", SelectedPlatform); } return TargetPlatform; } [Obsolete("Deprecated in UE5.1; function signature changed")] public static FileReference[] CompilePlugin(string UBTExe, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, List HostPlatforms, List TargetPlatforms, string AdditionalArgs = "") { List ManifestFileNames = new List(); // Build the host platforms if(HostPlatforms.Count > 0) { Logger.LogInformation("Building plugin for host platforms: {Arg0}", String.Join(", ", HostPlatforms)); foreach (UnrealTargetPlatform HostPlatform in HostPlatforms) { CompilePluginWithUBT(UBTExe, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealEditor", TargetType.Editor, HostPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalArgs); } } // Add the supported game targets if(TargetPlatforms.Count > 0) { List SupportedTargetPlatforms = TargetPlatforms.FindAll(Plugin.SupportsTargetPlatform); Logger.LogInformation("Building plugin for target platforms: {Arg0}", String.Join(", ", SupportedTargetPlatforms)); foreach (UnrealTargetPlatform TargetPlatform in SupportedTargetPlatforms) { string AdditionalTargetArgs = AdditionalArgs; CompilePluginWithUBT(UBTExe, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalTargetArgs); CompilePluginWithUBT(UBTExe, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Shipping, ManifestFileNames, AdditionalTargetArgs); } } // Package the plugin to the output folder HashSet BuildProducts = new HashSet(); foreach(FileReference ManifestFileName in ManifestFileNames) { BuildManifest Manifest = CommandUtils.ReadManifest(ManifestFileName); BuildProducts.UnionWith(Manifest.BuildProducts.Select(x => new FileReference(x))); } return BuildProducts.ToArray(); } public static FileReference[] CompilePlugin(FileReference UnrealBuildToolDll, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, IReadOnlyList HostPlatforms, List TargetPlatforms, string AdditionalArgs = "") { List ManifestFileNames = new List(); // Build the host platforms if(HostPlatforms.Count > 0) { Logger.LogInformation("Building plugin for host platforms: {Arg0}", String.Join(", ", HostPlatforms)); foreach (UnrealTargetPlatform HostPlatform in HostPlatforms) { CompilePluginWithUBT(UnrealBuildToolDll, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealEditor", TargetType.Editor, HostPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalArgs); } } // Add the supported game targets if(TargetPlatforms.Count > 0) { List SupportedTargetPlatforms = TargetPlatforms.FindAll(Plugin.SupportsTargetPlatform); Logger.LogInformation("Building plugin for target platforms: {Arg0}", String.Join(", ", SupportedTargetPlatforms)); foreach (UnrealTargetPlatform TargetPlatform in SupportedTargetPlatforms) { string AdditionalTargetArgs = AdditionalArgs; CompilePluginWithUBT(UnrealBuildToolDll, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalTargetArgs); CompilePluginWithUBT(UnrealBuildToolDll, HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Shipping, ManifestFileNames, AdditionalTargetArgs); } } // Package the plugin to the output folder HashSet BuildProducts = new HashSet(); foreach(FileReference ManifestFileName in ManifestFileNames) { BuildManifest Manifest = CommandUtils.ReadManifest(ManifestFileName); BuildProducts.UnionWith(Manifest.BuildProducts.Select(x => new FileReference(x))); } return BuildProducts.ToArray(); } [Obsolete("Deprecated in UE5.1; function signature has changed")] static void CompilePluginWithUBT(string UBTExe, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, string TargetName, TargetType TargetType, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, List ManifestFileNames, string InAdditionalArgs) { // Find a list of modules that need to be built for this plugin bool bCompilePlatform = false; if (Plugin.Modules != null) { bool bBuildDeveloperTools = (TargetType == TargetType.Editor || TargetType == TargetType.Program || (Configuration != UnrealTargetConfiguration.Test && Configuration != UnrealTargetConfiguration.Shipping)); bool bBuildRequiresCookedData = (TargetType != TargetType.Editor && TargetType != TargetType.Program); foreach (ModuleDescriptor Module in Plugin.Modules) { if (Module.IsCompiledInConfiguration(Platform, Configuration, TargetName, TargetType, bBuildDeveloperTools, bBuildRequiresCookedData)) { bCompilePlatform = true; } } } // Add these modules to the build agenda if(bCompilePlatform) { TargetPlatform TargetPlatform = GetTargetPlatform(Platform); if (TargetPlatform != null) { TargetPlatform.CompilePluginWithUBT(UBTExe, HostProjectFile, HostProjectPluginFile, Plugin, TargetName, TargetType, Platform, Configuration, ManifestFileNames, InAdditionalArgs ); } else { FileReference ManifestFileName = FileReference.Combine(HostProjectFile.Directory, "Saved", String.Format("Manifest-{0}-{1}-{2}.xml", TargetName, Platform, Configuration)); ManifestFileNames.Add(ManifestFileName); string Arguments = String.Format("-plugin={0} -noubtmakefiles -manifest={1} -nohotreload", CommandUtils.MakePathSafeToUseWithCommandLine(HostProjectPluginFile.FullName), CommandUtils.MakePathSafeToUseWithCommandLine(ManifestFileName.FullName)); if (!String.IsNullOrEmpty(InAdditionalArgs)) { Arguments += InAdditionalArgs; } CommandUtils.RunUBT(CmdEnv, UBTExe, HostProjectFile, TargetName, Platform, Configuration, Arguments); } } } static void CompilePluginWithUBT(FileReference UnrealBuildToolDll, FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, string TargetName, TargetType TargetType, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, List ManifestFileNames, string InAdditionalArgs) { // Find a list of modules that need to be built for this plugin bool bCompilePlatform = false; if (Plugin.Modules != null) { bool bBuildDeveloperTools = (TargetType == TargetType.Editor || TargetType == TargetType.Program || (Configuration != UnrealTargetConfiguration.Test && Configuration != UnrealTargetConfiguration.Shipping)); bool bBuildRequiresCookedData = (TargetType != TargetType.Editor && TargetType != TargetType.Program); foreach (ModuleDescriptor Module in Plugin.Modules) { if (Module.IsCompiledInConfiguration(Platform, Configuration, TargetName, TargetType, bBuildDeveloperTools, bBuildRequiresCookedData)) { bCompilePlatform = true; } } } // Add these modules to the build agenda if(bCompilePlatform) { TargetPlatform TargetPlatform = GetTargetPlatform(Platform); if (TargetPlatform != null) { TargetPlatform.CompilePluginWithUBT(UnrealBuildToolDll, HostProjectFile, HostProjectPluginFile, Plugin, TargetName, TargetType, Platform, Configuration, ManifestFileNames, InAdditionalArgs); } else { FileReference ManifestFileName = FileReference.Combine(HostProjectFile.Directory, "Saved", String.Format("Manifest-{0}-{1}-{2}.xml", TargetName, Platform, Configuration)); ManifestFileNames.Add(ManifestFileName); string Arguments = String.Format("-plugin={0} -noubtmakefiles -manifest={1} -nohotreload", CommandUtils.MakePathSafeToUseWithCommandLine(HostProjectPluginFile.FullName), CommandUtils.MakePathSafeToUseWithCommandLine(ManifestFileName.FullName)); if (PlatformToArchitectureMap.TryGetValue(Platform, out string SpecifiedArchitecture) && !string.IsNullOrEmpty(SpecifiedArchitecture)) { Arguments += String.Format(" -architecture={0}", SpecifiedArchitecture); } if (!String.IsNullOrEmpty(InAdditionalArgs)) { Arguments += InAdditionalArgs; } CommandUtils.RunUBT(CmdEnv, UnrealBuildToolDll, HostProjectFile, TargetName, Platform, Configuration, Arguments); } } } public static void PackagePlugin(FileReference SourcePluginFile, IEnumerable BuildProducts, DirectoryReference TargetDir, bool bUnversioned, IEnumerable TargetPlatforms) { DirectoryReference SourcePluginDir = SourcePluginFile.Directory; // Copy all the files to the output directory FileReference[] SourceFiles = FilterPluginFiles(SourcePluginFile, BuildProducts, TargetPlatforms).ToArray(); foreach(FileReference SourceFile in SourceFiles) { FileReference TargetFile = FileReference.Combine(TargetDir, SourceFile.MakeRelativeTo(SourcePluginDir)); CommandUtils.CopyFile(SourceFile.FullName, TargetFile.FullName); CommandUtils.SetFileAttributes(TargetFile.FullName, ReadOnly: false); } // Get the output plugin filename FileReference TargetPluginFile = FileReference.Combine(TargetDir, SourcePluginFile.GetFileName()); PluginDescriptor NewDescriptor = PluginDescriptor.FromFile(TargetPluginFile); NewDescriptor.bEnabledByDefault = null; NewDescriptor.bInstalled = true; if(!bUnversioned) { BuildVersion Version; if(BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version)) { NewDescriptor.EngineVersion = String.Format("{0}.{1}.0", Version.MajorVersion, Version.MinorVersion); } } NewDescriptor.Save(TargetPluginFile.FullName); } static void AddRulesFromFileToFilter(FileFilter Filter, FileReference FilterFile) { if (FileReference.Exists(FilterFile)) { Logger.LogInformation("Reading filter rules from {FilterFile}", FilterFile); Filter.ReadRulesFromFile(FilterFile, "FilterPlugin"); } } static IEnumerable FilterPluginFiles(FileReference PluginFile, IEnumerable BuildProducts, IEnumerable TargetPlatforms) { // Set up the default filter FileFilter Filter = new FileFilter(); Filter.AddRuleForFile(PluginFile, PluginFile.Directory, FileFilterType.Include); Filter.AddRuleForFiles(BuildProducts, PluginFile.Directory, FileFilterType.Include); Filter.Include("/Binaries/ThirdParty/..."); Filter.Include("/Resources/..."); Filter.Include("/Content/..."); Filter.Include("/Intermediate/Build/.../Inc/..."); Filter.Include("/Shaders/..."); Filter.Include("/Source/..."); Filter.Exclude("/Tests/..."); // Add custom rules for all platforms AddRulesFromFileToFilter(Filter, FileReference.Combine(PluginFile.Directory, "Config", "FilterPlugin.ini")); // Add custom rules for targeted platforms. foreach (UnrealTargetPlatform Platform in TargetPlatforms) { Logger.LogInformation("Loading FilterPlugin{Platform}.ini", Platform); AddRulesFromFileToFilter(Filter, FileReference.Combine(PluginFile.Directory, "Config", $"FilterPlugin{Platform}.ini")); } // Apply the standard exclusion rules foreach (string RestrictedFolderName in RestrictedFolder.GetNames()) { Filter.AddRule(String.Format(".../{0}/...", RestrictedFolderName), FileFilterType.Exclude); } // Apply the filter to the plugin directory return Filter.ApplyToDirectory(PluginFile.Directory, true); } static List GetTargetPlatforms(BuildCommand Command, UnrealTargetPlatform HostPlatform) { List TargetPlatforms = new List(); if(!Command.ParseParam("NoTargetPlatforms")) { // Only interested in building for Platforms that support code projects TargetPlatforms = PlatformExports.GetRegisteredPlatforms().Where(x => InstalledPlatformInfo.IsValidPlatform(x, EProjectType.Code)).ToList(); // only build Mac on Mac if (HostPlatform != UnrealTargetPlatform.Mac && TargetPlatforms.Contains(UnrealTargetPlatform.Mac)) { TargetPlatforms.Remove(UnrealTargetPlatform.Mac); } // only build Windows on Windows if (HostPlatform != UnrealTargetPlatform.Win64 && TargetPlatforms.Contains(UnrealTargetPlatform.Win64)) { TargetPlatforms.Remove(UnrealTargetPlatform.Win64); } // build Linux on Windows and Linux if (HostPlatform != UnrealTargetPlatform.Win64 && HostPlatform != UnrealTargetPlatform.Linux) { if (TargetPlatforms.Contains(UnrealTargetPlatform.Linux)) TargetPlatforms.Remove(UnrealTargetPlatform.Linux); if (TargetPlatforms.Contains(UnrealTargetPlatform.LinuxArm64)) TargetPlatforms.Remove(UnrealTargetPlatform.LinuxArm64); } // Remove any platforms that aren't enabled on the command line string TargetPlatformFilter = Command.ParseParamValue("TargetPlatforms", null); if(TargetPlatformFilter != null) { List NewTargetPlatforms = new List(); foreach (string TargetPlatformName in TargetPlatformFilter.Split(new char[]{ '+' }, StringSplitOptions.RemoveEmptyEntries)) { UnrealTargetPlatform TargetPlatform; if (!UnrealTargetPlatform.TryParse(TargetPlatformName, out TargetPlatform)) { throw new AutomationException("Unknown target platform '{0}' specified on command line", TargetPlatformName); } if(TargetPlatforms.Contains(TargetPlatform)) { NewTargetPlatforms.Add(TargetPlatform); } } TargetPlatforms = NewTargetPlatforms; } } return TargetPlatforms; } static IReadOnlyList GetHostPlatforms(BuildCommand Command) { if (Command.ParseParam("NoHostPlatform")) { return Array.Empty(); } var CurrentPlatform = BuildHostPlatform.Current.Platform; string HostPlatformFilter = Command.ParseParamValue("HostPlatforms", null); if (HostPlatformFilter == null) { return new[] { CurrentPlatform }; } // Only interested in building for Platforms that support code projects HashSet SupportedHostPlatforms = PlatformExports.GetRegisteredPlatforms().Where(x => InstalledPlatformInfo.IsValidPlatform(x, EProjectType.Code)).ToHashSet(); // only build Mac on Mac if (CurrentPlatform != UnrealTargetPlatform.Mac && SupportedHostPlatforms.Contains(UnrealTargetPlatform.Mac)) { SupportedHostPlatforms.Remove(UnrealTargetPlatform.Mac); } // only build Windows on Windows if (CurrentPlatform != UnrealTargetPlatform.Win64 && SupportedHostPlatforms.Contains(UnrealTargetPlatform.Win64)) { SupportedHostPlatforms.Remove(UnrealTargetPlatform.Win64); } // build Linux on Windows and Linux if (CurrentPlatform != UnrealTargetPlatform.Win64 && CurrentPlatform != UnrealTargetPlatform.Linux) { SupportedHostPlatforms.Remove(UnrealTargetPlatform.Linux); SupportedHostPlatforms.Remove(UnrealTargetPlatform.LinuxArm64); } List NewHostPlatforms = new List(); foreach (string HostPlatformName in HostPlatformFilter.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries)) { UnrealTargetPlatform HostPlatform; if (!UnrealTargetPlatform.TryParse(HostPlatformName, out HostPlatform)) { throw new AutomationException("Unknown host platform '{0}' specified on command line", HostPlatformName); } if (SupportedHostPlatforms.Contains(HostPlatform)) { NewHostPlatforms.Add(HostPlatform); } else { Logger.LogWarning("HostPlatform {HostPlatformName} not supported on current platform {CurrentPlatform}", HostPlatformName, CurrentPlatform); } } return NewHostPlatforms; } }