// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Reflection; using UnrealBuildTool; using EpicGames.Core; using UnrealBuildBase; using Microsoft.Extensions.Logging; using static AutomationTool.CommandUtils; using IdentityModel.Client; namespace AutomationTool { public interface ITurnkeyContext { string RetrieveFileSource(string Name, string InType = "Misc", string InPlatform = null, string SubType = null); string RetrieveFileSource(object HintObject); string GetVariable(string VariableName); int RunExternalCommand(string Command, string Params, bool bRequiresPrivilegeElevation, bool bUnattended, bool bCreateWindow); void Log(string Message); void ReportError(string Message); void PauseForUser(string Message); int ReadInputInt(string Prompt, List Options, bool bIsCancellable, int DefaultValue = -1); } //public interface InputOutput //{ // string RetrieveByTags(string[] RequiredTags, string[] PreferredTags, Dictionary ExtraVariables = null); //} public class DeviceInfo { public enum AutoSoftwareUpdateMode { Unknown, Disabled, Enabled } public DeviceInfo(UnrealTargetPlatform Platform) { this.Platform = Platform; } public DeviceInfo(UnrealTargetPlatform Platform, string Name, string Id, string SoftwareVersion, string Type, bool bIsDefault, bool bCanConnect, Dictionary PlatformValues = null, AutoSoftwareUpdateMode AutoSoftwareUpdates = AutoSoftwareUpdateMode.Unknown) { this.Platform = Platform; this.Name = Name; this.Id = Id; this.SoftwareVersion = SoftwareVersion; this.Type = Type; this.bIsDefault = bIsDefault; this.bCanConnect = bCanConnect; this.AutoSoftwareUpdates = AutoSoftwareUpdates; if (PlatformValues != null) { this.PlatformValues = new Dictionary(PlatformValues); } } public UnrealTargetPlatform Platform; public string Name; public string Id; public string SoftwareVersion; public string Type; public bool bIsDefault = false; // is the device able to be connected to (this is more about able to flash SDK or run, not about matching SDK version) // if false, any of the above fields are suspect, especially SoftwareVersion public bool bCanConnect = true; public AutoSoftwareUpdateMode AutoSoftwareUpdates = AutoSoftwareUpdateMode.Unknown; // case insensitive platform value dictionary. turnkey doesn't use this, but the platform can look up the device during deployment, etc to get this out public Dictionary PlatformValues = new Dictionary(StringComparer.InvariantCultureIgnoreCase); } /// /// Holds information for targeting specific platform (platform type + cook flavor) /// public struct TargetPlatformDescriptor { public UnrealTargetPlatform Type; public string CookFlavor; public TargetPlatformDescriptor(UnrealTargetPlatform InType) { Type = InType; CookFlavor = ""; } public TargetPlatformDescriptor(UnrealTargetPlatform InType, string InCookFlavor) { Type = InType; CookFlavor = InCookFlavor ?? ""; } public override string ToString() { return Type.ToString(); } } /// /// Platform abstraction layer. /// public class Platform : CommandUtils { private static Dictionary AllPlatforms = new Dictionary(); internal static void InitializePlatforms(HashSet AssembliesWithPlatforms) { Logger.LogDebug("Creating platforms."); // Create all available platforms. foreach (var ScriptAssembly in AssembliesWithPlatforms) { CreatePlatformsFromAssembly(ScriptAssembly); } // Create dummy platforms for platforms we don't support foreach (UnrealTargetPlatform PlatformType in UnrealTargetPlatform.GetValidPlatforms()) { var TargetDesc = new TargetPlatformDescriptor(PlatformType); Platform ExistingInstance; if (AllPlatforms.TryGetValue(TargetDesc, out ExistingInstance) == false) { Logger.LogDebug("Creating placeholder platform for target: {TargetType}", TargetDesc.Type); AllPlatforms.Add(TargetDesc, new Platform(TargetDesc.Type)); } } } private static void CreatePlatformsFromAssembly(Assembly ScriptAssembly) { Logger.LogDebug("Looking for platforms in {Location}", ScriptAssembly.Location); Type[] AllTypes = null; try { AllTypes = ScriptAssembly.GetTypes(); } catch (Exception Ex) { Logger.LogError("Failed to get assembly types for {Location}", ScriptAssembly.Location); if (Ex is ReflectionTypeLoadException) { var TypeLoadException = (ReflectionTypeLoadException)Ex; if (!IsNullOrEmpty(TypeLoadException.LoaderExceptions)) { Logger.LogError("Loader Exceptions:"); foreach (var LoaderException in TypeLoadException.LoaderExceptions) { Logger.LogError(LoaderException, "{Text}", LogUtils.FormatException(LoaderException)); } } else { Logger.LogError("No Loader Exceptions available."); } } // Re-throw, this is still a critical error! throw; } foreach (var PotentialPlatformType in AllTypes) { if (PotentialPlatformType != typeof(Platform) && typeof(Platform).IsAssignableFrom(PotentialPlatformType) && !PotentialPlatformType.IsAbstract) { Logger.LogDebug("Creating platform {Platform} from {Location}.", PotentialPlatformType.Name, ScriptAssembly.Location); var PlatformInstance = Activator.CreateInstance(PotentialPlatformType) as Platform; var PlatformDesc = PlatformInstance.GetTargetPlatformDescriptor(); Platform ExistingInstance; if (!AllPlatforms.TryGetValue(PlatformDesc, out ExistingInstance)) { AllPlatforms.Add(PlatformDesc, PlatformInstance); } else { if (ExistingInstance.GetType() != PlatformInstance.GetType()) { Logger.LogWarning("Platform {Platform} already exists", PotentialPlatformType.Name); } } } } } protected UnrealTargetPlatform TargetPlatformType; protected UnrealTargetPlatform TargetIniPlatformType; public Platform(UnrealTargetPlatform PlatformType) { TargetPlatformType = PlatformType; TargetIniPlatformType = PlatformType; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); } /// /// Allow the platform to alter the ProjectParams /// /// public virtual void PlatformSetupParams(ref ProjectParams ProjParams) { } public virtual TargetPlatformDescriptor GetTargetPlatformDescriptor() { return new TargetPlatformDescriptor(TargetPlatformType, ""); } /// /// Allows a platform to add runtime dependencies to UAT that may not be referenced in other ways, but are needed for staging UAT /// /// /// public virtual void GetPlatformUATDependencies(DirectoryReference ProjectDirectory, List Dependencies) { } #region Turnkey public virtual DeviceInfo[] GetDevices() { return null; } public virtual DeviceInfo GetDeviceByName( string DeviceName ) { DeviceInfo[] Devices = GetDevices(); if (Devices == null) { return null; } // look by Id first DeviceInfo Device = Array.Find(Devices, x => string.Compare(x.Id, DeviceName, true) == 0); // if that fails, use Name if (Device == null) { Device = Array.Find(Devices, x => string.Compare(x.Name, DeviceName, true) == 0); } return Device; } public virtual bool InstallSDK(BuildCommand BuildCommand, ITurnkeyContext TurnkeyContext, DeviceInfo Device, bool bUnattended, bool bSdkAlreadyInstalled) { string Command, Params; bool bRequiresPrivilegeElevation = false; bool bCreateWindow = false; if (Device != null && GetDeviceUpdateSoftwareCommand(out Command, out Params, ref bRequiresPrivilegeElevation, ref bCreateWindow, TurnkeyContext, Device)) { int ExitCode = TurnkeyContext.RunExternalCommand(Command, Params, bRequiresPrivilegeElevation, bUnattended, bCreateWindow); return OnSDKInstallComplete(ExitCode, TurnkeyContext, Device); } else if (Device == null && GetSDKInstallCommand(out Command, out Params, ref bRequiresPrivilegeElevation, ref bCreateWindow, TurnkeyContext, bSdkAlreadyInstalled)) { int ExitCode = TurnkeyContext.RunExternalCommand(Command, Params, bRequiresPrivilegeElevation, bUnattended, bCreateWindow); return OnSDKInstallComplete(ExitCode, TurnkeyContext, null); } return false; } public virtual bool PostSDKSetup(ITurnkeyContext TurnkeyContext, bool bUnattended) { return true; } /// /// Return a list of versions that will be used to create "fake" FileSource objects which are used /// for install Sdks where no file downloads are needed /// /// public virtual string[] GetCodeSpecifiedSdkVersions() { return new string[] { }; } /// /// Return a list of versions that will be used to create "fake" FileSource objects which are used /// for flash/system updates where no file downloads are needed /// public virtual string[] GetCodeSpecifiedDeviceSoftwareUpdateVersions() { return new string[] { }; } public virtual bool GetSDKInstallCommand(out string Command, out string Params, ref bool bRequiresPrivilegeElevation, ref bool bCreateWindow, ITurnkeyContext TurnkeyContext) { Command = null; Params = null; return false; } public virtual bool GetSDKInstallCommand(out string Command, out string Params, ref bool bRequiresPrivilegeElevation, ref bool bCreateWindow, ITurnkeyContext TurnkeyContext, bool bSdkAlreadyInstalled) { return GetSDKInstallCommand(out Command, out Params, ref bRequiresPrivilegeElevation, ref bCreateWindow, TurnkeyContext); } public virtual bool GetDeviceUpdateSoftwareCommand(out string Command, out string Params, ref bool bRequiresPrivilegeElevation, ref bool bCreateWindow, ITurnkeyContext TurnkeyContext, DeviceInfo Device = null) { Command = null; Params = null; return false; } /// /// Let's the platform handle the result of /// /// /// /// /// True if the installation was a success (defaults to ExitCode == 0) public virtual bool OnSDKInstallComplete(int ExitCode, ITurnkeyContext TurnkeyContext, DeviceInfo Device) { return ExitCode == 0; } public virtual void PersistSdkRootVar() { } public virtual string GetSDKCreationHelp() { return null; } public virtual bool UpdateHostPrerequisites(BuildCommand Command, ITurnkeyContext TurnkeyContext, bool bVerifyOnly) { return true; } public virtual bool UpdateDevicePrerequisites(DeviceInfo Device, BuildCommand Command, ITurnkeyContext TurnkeyContext, bool bVerifyOnly) { return true; } public virtual bool SetDeviceAutoSoftwareUpdateMode(DeviceInfo Device, bool bEnableAutoSoftwareUpdates) { Logger.LogWarning("{PlatformType} does not implement SetDeviceAutoSoftwareUpdateMode", PlatformType); return false; } #endregion /// /// Package files for the current platform. /// /// /// /// public virtual void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { throw new AutomationException("{0} does not yet implement Packaging.", PlatformType); } /// /// Does the reverse of the output from the package process /// /// /// /// public virtual void ExtractPackage(ProjectParams Params, string SourcePath, string DestinationPath) { throw new AutomationException("{0} does not yet implement ExtractPackage.", PlatformType); } /// /// Allow platform to do platform specific work on archived project before it's deployed. /// /// /// public virtual void ProcessArchivedProject(ProjectParams Params, DeploymentContext SC) { } /// /// Get all connected device names for this platform /// /// /// public virtual void GetConnectedDevices(ProjectParams Params, out List Devices) { Devices = null; Logger.LogWarning("{PlatformType} does not implement GetConnectedDevices", PlatformType); } /// /// Allow platform specific work prior to touching the staging directory /// /// /// public virtual void PreStage(ProjectParams Params, DeploymentContext SC) { // do nothing on most platforms } /// /// Deploy the application on the current platform /// /// /// public virtual void Deploy(ProjectParams Params, DeploymentContext SC) { Logger.LogWarning("{PlatformType} does not implement Deploy...", PlatformType); } /// /// Run the client application on the platform /// /// /// /// /// /// public virtual IProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params, DeploymentContext SC) { return RunClient(ClientRunFlags, ClientApp, ClientCmdLine, Params ); } /// /// Run the client application on the platform /// /// /// /// /// public virtual IProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params) { PushDir(Path.GetDirectoryName(ClientApp)); // Always start client process and don't wait for exit. IProcessResult ClientProcess = Run(ClientApp, ClientCmdLine, null, ClientRunFlags | ERunOptions.NoWaitForExit); PopDir(); return ClientProcess; } /// /// Downloads file from target device to local pc /// /// /// /// public virtual void GetTargetFile(string RemoteFilePath, string LocalFile, ProjectParams Params) { throw new NotImplementedException(); } /// /// Allow platform specific clean-up or detection after client has run /// /// /// public virtual void PostRunClient(IProcessResult Result, ProjectParams Params) { // do nothing in the default case } /// /// Get the platform-specific name for the executable (with out the file extension) /// /// /// public virtual string GetPlatformExecutableName(string InExecutableName) { return InExecutableName; } public virtual List GetExecutableNames(DeploymentContext SC) { List ExecutableNames = new List(); foreach (StageTarget Target in SC.StageTargets) { foreach (BuildProduct Product in Target.Receipt.BuildProducts) { if (Product.Type == BuildProductType.Executable) { FileReference BuildProductFile = Product.Path; if (BuildProductFile.IsUnderDirectory(SC.ProjectRoot)) { ExecutableNames.Add(FileReference.Combine(SC.RuntimeProjectRootDir, BuildProductFile.MakeRelativeTo(SC.ProjectRoot))); } else { ExecutableNames.Add(FileReference.Combine(SC.RuntimeRootDir, BuildProductFile.MakeRelativeTo(Unreal.RootDirectory))); } } } } return ExecutableNames; } /// /// Get the files to deploy, specific to this platform, typically binaries /// /// /// Deployment Context public virtual void GetFilesToDeployOrStage(ProjectParams Params, DeploymentContext SC) { throw new AutomationException("{0} does not yet implement GetFilesToDeployOrStage.", PlatformType); } /// /// Get additional platform specific files to stage when staging DLC /// /// /// Deployment Context public virtual void GetFilesToStageForDLC(ProjectParams Params, DeploymentContext SC) { } /// /// Called after CopyUsingStagingManifest. Does anything platform specific that requires a final list of staged files. /// /// /// public virtual void PostStagingFileCopy(ProjectParams Params, DeploymentContext SC) { } /// /// Get the files to deploy, specific to this platform, typically binaries /// /// /// Deployment Context public virtual void GetFilesToArchive(ProjectParams Params, DeploymentContext SC) { SC.ArchiveFiles(SC.StageDirectory.FullName); } /// /// Gets cook platform name for this platform. /// /// True if cooking for dedicated server /// True if cooking for client only /// Cook platform string. public virtual string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly) { // this should get all cases, but a platform can override if needed string Suffix = bIsClientOnly ? "Client" : bDedicatedServer ? "Server" : ""; string PlatformName = GetGenericPlatformName(TargetPlatformType); return $"{PlatformName}{Suffix}"; } /// /// Gets extra cook commandline arguments for this platform. /// /// ProjectParams /// Cook platform string. public virtual string GetCookExtraCommandLine(ProjectParams Params) { return ""; } /// /// Gets extra maps needed on this platform. /// /// extra maps public virtual List GetCookExtraMaps() { return new List(); } /// /// Get a release pak file path, if we are currently building a patch then get the previous release pak file path, if we are creating a new release this will be the output path /// /// /// /// /// public virtual string GetReleasePakFilePath(DeploymentContext SC, ProjectParams Params, string PakName) { if (Params.IsGeneratingPatch) { return CombinePaths(Params.GetBasedOnReleaseVersionPath(SC, Params.Client), PakName); } else { return CombinePaths(Params.GetCreateReleaseVersionPath(SC, Params.Client), PakName); } } /// /// Gets editor cook platform name for this platform. Cooking the editor is not useful, but this is used to fill the derived data cache /// /// Cook platform string. public virtual string GetEditorCookPlatform() { return GetCookPlatform(false, false); } /// /// return true if we need to change the case of filenames outside of pak files /// /// The staged file type to check (UFS vs SsytemNonUFS, etc) /// true if files should be lower-cased during staging, for the given filetype public virtual bool DeployLowerCaseFilenames(StagedFileType FileType) { return false; } /// /// return true if we need to change the case of a particular file /// /// /// The staged file type to check (UFS vs SsytemNonUFS, etc) /// true if files should be lower-cased during staging, for the given filetype public virtual bool DeployLowerCaseFile(FileReference File, StagedFileType FileType) { return DeployLowerCaseFilenames(FileType); } /// /// Converts local path to target platform path. /// /// Local path. /// Local root. /// Local path converted to device format path. public virtual string LocalPathToTargetPath(string LocalPath, string LocalRoot) { return LocalPath; } /// /// Update the build agenda for this platform /// /// Agenda to update /// Any additional files that will be created public virtual void MakeAgenda(UnrealBuild.BuildAgenda Agenda, List ExtraBuildProducts) { } /// /// Returns a list of the compiler produced debug file extensions /// /// a list of the compiler produced debug file extensions public virtual List GetDebugFileExtensions() { return new List(); } /// /// UnrealTargetPlatform type for this platform. /// public UnrealTargetPlatform PlatformType { get { return TargetPlatformType; } } /// /// UnrealTargetPlatform type for this platform. /// public UnrealTargetPlatform IniPlatformType { get { return TargetIniPlatformType; } } /// /// True if this platform is supported. /// public virtual bool IsSupported { get { return false; } } /// /// True if this platform requires UFE for deploying /// public virtual bool DeployViaUFE { get { return false; } } /// /// True if this platform requires UFE for launching /// public virtual bool LaunchViaUFE { get { return false; } } /// /// Gets extra launch commandline arguments for this platform. /// /// ProjectParams /// Launch platform string. public virtual string GetLaunchExtraCommandLine(ProjectParams Params) { return ""; } /// /// Modify or override the list of file host addresses for this platform. /// public virtual void ModifyFileHostAddresses(List HostAddresses) { } /// /// True if this platform can write to the abslog path that's on the host desktop. /// public virtual bool UseAbsLog { get { return BuildHostPlatform.Current.Platform == PlatformType; } } /// /// return true if we need to call Remap of a specific file type /// /// The staged file type to check (UFS vs SsytemNonUFS, etc) /// true if files should be remaped, for the given filetype public virtual bool RemapFileType(StagedFileType FileType) { return (FileType == StagedFileType.UFS || FileType == StagedFileType.NonUFS); } /// /// Remaps the given content directory to its final location /// public virtual StagedFileReference Remap(StagedFileReference Dest) { return Dest; } /// /// Tri-state - The intent is to override command line parameters for pak if needed per platform. /// public enum PakType { Always, Never, DontCare }; public virtual PakType RequiresPak(ProjectParams Params) { return PakType.DontCare; } /// /// Returns platform specific command line options for UnrealPak /// public virtual string GetPlatformPakCommandLine(ProjectParams Params, DeploymentContext SC) { return ""; } /// /// Returns platform specific command line options for the IoStore cmdlet /// public virtual string GetPlatformIoStoreCommandLine(ProjectParams Params, DeploymentContext SC) { return ""; } /// /// Get the custom deployment handler for this platform, /// /// /// Deployment Context public virtual CustomDeploymentHandler GetCustomDeploymentHandler(ProjectParams Params, DeploymentContext SC) { return null; } /// /// True if this platform is supported. /// public virtual bool SupportsMultiDeviceDeploy { get { return false; } } /// /// Returns the ICU data version we use for this platform /// public virtual string ICUDataVersion { get { return "icudt64l"; } } /// /// Returns true if the platform wants patches to generate a small .pak file containing the difference /// of current data against a shipped pak file. /// /// public virtual bool GetPlatformPatchesWithDiffPak(ProjectParams Params, DeploymentContext SC) { return true; } /// /// Returns whether the platform requires a package to deploy to a device /// public virtual bool RequiresPackageToDeploy(ProjectParams Params) { return false; } /// /// Returns whether the platform requires the Manifest_*_.txt files to be copied to the staged directory. /// public virtual bool RequiresManifestFiles { get { return true; } } public virtual HashSet GetFilesForCRCCheck() { string CmdLine = "UECommandLine.txt"; // using SystemNonUFS because that is how it's staged in CreateStagingManifest if (DeployLowerCaseFilenames(StagedFileType.SystemNonUFS)) { CmdLine = CmdLine.ToLowerInvariant(); } return new HashSet() { new StagedFileReference(CmdLine) }; } public virtual void StripSymbols(FileReference SourceFile, FileReference TargetFile) { if (SourceFile == TargetFile) { Logger.LogWarning("StripSymbols() has not been implemented for {Arg0}", PlatformType.ToString()); } else { Logger.LogWarning("StripSymbols() has not been implemented for {Arg0}; copying files", PlatformType.ToString()); File.Copy(SourceFile.FullName, TargetFile.FullName, true); } } public virtual bool PublishSymbols(DirectoryReference SymbolStoreDirectory, List Files, bool bIndexSources, List SourceFiles, string Product, string Branch, int Change, string BuildVersion = null) { Logger.LogWarning("PublishSymbols() has not been implemented for {Arg0}", PlatformType.ToString()); return false; } public virtual int GetExecutableSize(DirectoryReference BinariesDirectory, string ClientName, HashSet BuildProducts) { Logger.LogWarning("GetExecutableSize() has not been implemented for {Arg0}", PlatformType.ToString()); return -1; } public virtual bool UpdatePatchPackagingParameters( string ProjectRoot, string BuildToUse, string AdditionalOptions ) { Logger.LogWarning("UpdatePackagingParameters() not needed on platform {Arg0}", PlatformType.ToString()); return false; } public virtual bool BuildAdditionalApps(string ProjectRoot, string Configuration, string OutputDir, string AdditionalOptions) { Logger.LogWarning("BuildAdditionalApps() not needed on platform {Arg0}", PlatformType.ToString()); return false; } /// /// When overridden, returns the directory structure of the platform's symbol server. /// Each element is a semi-colon separated string of possible directory names. /// The * wildcard is allowed in any entry. {0} will be substituted for a custom filter string. /// public virtual string[] SymbolServerDirectoryStructure { get { return null; } } /// /// When overridden to return true, allows the AgeStoreTask to delete individual files in a single symbol folder, /// rather than requiring all files in a symbol folder to be out of date before deleting the entire directory. /// public virtual bool SymbolServerDeleteIndividualFiles { get { return false; } } /// /// When true, callers of PublishSymbols() must provide an explicit list of source files to create the index from. /// Some platforms discover the source files via other means, so it is possible to turn this step of the process /// off, since it can be slow. /// public virtual bool SymbolServerSourceIndexingRequiresListOfSourceFiles { get { return true; } } /// /// If true, indicates the platform's symbol server directory must be locked for /// exclusive access before any operation is performed on it. Platforms may override /// this to disable if their tools support concurrent access to the symbol server directory. /// public virtual bool SymbolServerRequiresLock { get { return true; } } public virtual void PreBuildAgenda(UnrealBuild Build, UnrealBuild.BuildAgenda Agenda, ProjectParams Params) { } /// /// Allows a platform to use the crash reporter from a different (built-in) platform /// public virtual UnrealTargetPlatform? CrashReportPlatform { get { return null; } } /// /// General purpose command to run generic string commands inside the platform interfeace /// /// public virtual int RunCommand(string Command) { return 0; } /// /// Determines whether we should stage a UECommandLine.txt for this platform /// public virtual bool ShouldStageCommandLine(ProjectParams Params, DeploymentContext SC) { return true; } /// /// Can host compile and cook for the platform /// public virtual bool CanHostPlatform(UnrealTargetPlatform Platform) { return false; } /// /// Allows some platforms to not be compiled, for instance when BuildCookRun -build is performed /// /// true if this instance can be compiled; otherwise, false. public virtual bool CanBeCompiled() { return true; } public virtual bool RetrieveDeployedManifests(ProjectParams Params, DeploymentContext SC, string DeviceName, out List UFSManifests, out List NonUFSManifests) { UFSManifests = null; NonUFSManifests = null; return false; } public virtual bool SignExecutables(DeploymentContext SC, ProjectParams Params) { return true; } public virtual UnrealTargetPlatform[] GetStagePlatforms() { return new UnrealTargetPlatform[] { PlatformType }; } public virtual DirectoryReference GetProjectRootForStage(DirectoryReference RuntimeRoot, StagedDirectoryReference RelativeProjectRootForStage) { return DirectoryReference.Combine(RuntimeRoot, RelativeProjectRootForStage.Name); } public virtual void PrepareForDebugging(string SourcePackage, string ProjectFilePath, string ClientPlatform) { Logger.LogError("Not implemented for the {Platform} platform.", ClientPlatform); } public virtual void SetSecondaryRemoteMac(string ProjectFilePath, string ClientPlatform) { Logger.LogError("Not implemented for this platform."); } // let the platform set the exe extension if it chooses (otherwise, use // the switch statement in GetExeExtension below) protected virtual string GetPlatformExeExtension() { return null; } public static string GetExeExtension(UnrealTargetPlatform Target) { Platform Plat = GetPlatform(Target); string PlatformExeExtension = Plat.GetPlatformExeExtension(); if (!string.IsNullOrEmpty(PlatformExeExtension)) { return PlatformExeExtension; } if (Target == UnrealTargetPlatform.Win64) { return ".exe"; } if (Target == UnrealTargetPlatform.IOS) { return ".stub"; } if (Target == UnrealTargetPlatform.Linux || Target == UnrealTargetPlatform.LinuxArm64) { return ""; } if (Target == UnrealTargetPlatform.Mac) { return ".app"; } return String.Empty; } public static Dictionary Platforms { get { return AllPlatforms; } } public static Platform GetPlatform(UnrealTargetPlatform PlatformType) { TargetPlatformDescriptor Desc = new TargetPlatformDescriptor(PlatformType); return AllPlatforms[Desc]; } public static Platform GetPlatform(UnrealTargetPlatform PlatformType, string CookFlavor) { TargetPlatformDescriptor Desc = new TargetPlatformDescriptor(PlatformType, CookFlavor); return AllPlatforms[Desc]; } public static bool IsValidTargetPlatform(TargetPlatformDescriptor PlatformDesc) { return AllPlatforms.ContainsKey(PlatformDesc); } public static List GetValidTargetPlatforms(UnrealTargetPlatform PlatformType, List CookFlavors) { List ValidPlatforms = new List(); if (!CommandUtils.IsNullOrEmpty(CookFlavors)) { foreach (string CookFlavor in CookFlavors) { TargetPlatformDescriptor TargetDesc = new TargetPlatformDescriptor(PlatformType, CookFlavor); if (IsValidTargetPlatform(TargetDesc)) { ValidPlatforms.Add(TargetDesc); } } } // In case there are no flavors specified or this platform type does not care/support flavors add it as generic platform if (ValidPlatforms.Count == 0) { TargetPlatformDescriptor TargetDesc = new TargetPlatformDescriptor(PlatformType); if (IsValidTargetPlatform(TargetDesc)) { ValidPlatforms.Add(TargetDesc); } } return ValidPlatforms; } } }