// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using AutomationTool; using UnrealBuildTool; using System.Threading; using System.Text.RegularExpressions; using System.Linq; using EpicGames.Core; using System.Security.Cryptography.X509Certificates; namespace Gauntlet { internal static class MacAppChecker { public static bool IsAppBundleUsable(string AppPath, string ProjectName) { string MacOSDir = Path.Combine(AppPath, "Contents", "MacOS"); // check both binary AND Engine content. Regular Unreal builds won't have the latter if (Directory.Exists(MacOSDir) && Directory.Exists(Path.Combine(AppPath, "Contents", "UE"))) { // if we don't have a project passed in, we have checked all we can, so we are valid now if (ProjectName == null) { return true; } // check there's an executable with the right name string ShortName = Regex.Replace(ProjectName, "Game", "", RegexOptions.IgnoreCase); FileInfo Executable = new DirectoryInfo(MacOSDir).GetFiles().Where(Fi => Fi.Name.StartsWith(ShortName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); // make sure it was found if (Executable != null) { return true; } } // if we got here, we are not usable return false; } } public class MacStagedBuild : StagedBuild { public MacStagedBuild(UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfig, UnrealTargetRole InRole, string InBuildPath, string InExecutablePath, string InFlavor = "") : base(InPlatform, InConfig, InRole, InBuildPath, InExecutablePath, InFlavor) { } public override int PreferenceOrder { get { return 0; } } } /// /// Represents a loose collection of files. For Mac the executable will point to the binary.app bundle and /// not the inner executable file /// public class MacStagedBuildSource : StagedBuildSource { public override string BuildName { get { return "MacStagedBuildSource"; } } public override UnrealTargetPlatform Platform { get { return UnrealTargetPlatform.Mac; } } public override string PlatformFolderPrefix { get { return "Mac"; } } public override bool ShouldMakeBuildAvailable(string AppPath) { return MacAppChecker.IsAppBundleUsable(AppPath, null); } } /// /// Represents a packaged mac build. E.g. a Game.app that contains the executable and content /// public class MacPackagedBuild : PackagedBuild { public MacPackagedBuild(UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfig, UnrealTargetRole InRole, string InBuildPath, BuildFlags InFlags) : base(InPlatform, InConfig, InRole, InBuildPath, InFlags) { } public override int PreferenceOrder { get { return 1; } } } /// /// Build source that discovers packaged builds for Mac. A packaged build is a bundle.app that contains both an executable and UE content /// public class MacPackagedBuildSource : IFolderBuildSource { public string BuildName { get { return "MacPackagedBuildSource"; } } public string PlatformFolderPrefix { get { return "Mac"; } } public bool CanSupportPlatform(UnrealTargetPlatform InPlatform) { return InPlatform == UnrealTargetPlatform.Mac; } public List GetBuildsAtPath(string InProjectName, string InPath, int MaxRecursion = 3) { List Builds = new List(); // c:\path\to\build DirectoryInfo RootDir = new DirectoryInfo(InPath); if (RootDir.Exists == false) { Log.Warning("No such path {0} to search for builds", InPath); return Builds; } List SearchDirs = new List(); // If this folder starts with the platform prefix the path is likely /path/builds/MacClient etc if (RootDir.Name.StartsWith(PlatformFolderPrefix)) { SearchDirs.Add(RootDir); } else { // assume this is something like /Path/Builds and add any /Path/Builds/MacClient, /Path/Builds/MacNoEditor etc SearchDirs = RootDir.GetDirectories().Where(D => D.Name.StartsWith(PlatformFolderPrefix)).ToList(); } // recurse and add subfolders. IEnumerable DirsToRecurse = new List(SearchDirs); List AllPackages = new List(); // get subdirs while (MaxRecursion-- > 0) { IEnumerable DiscoveredDirs = DirsToRecurse.SelectMany(D => D.GetDirectories("*", SearchOption.TopDirectoryOnly)); // mac packages are folders so we only want things that end with .app IEnumerable Packages = DiscoveredDirs.Where(D => Path.GetExtension(D.Name).Equals(".app", StringComparison.OrdinalIgnoreCase)); AllPackages.AddRange(Packages); // don't recurse into the dirs that end with .app since those are builds we'll look at DirsToRecurse = DiscoveredDirs.Except(Packages); } // Now foreach (DirectoryInfo Di in AllPackages) { // Mac packages are directories such as GameName.app or GameClient-Mac-Test.app so we can use the Gauntlet helpers // to discover them. If config is unknown we don't recognize the format UnrealTargetConfiguration Config = UnrealHelpers.GetConfigurationFromExecutableName(InProjectName, Di.Name); UnrealTargetRole Role = UnrealHelpers.GetRoleFromExecutableName(InProjectName, Di.Name); if (Config != UnrealTargetConfiguration.Unknown) { if (MacAppChecker.IsAppBundleUsable(Di.FullName, InProjectName)) { // We can use the base packaged build class. it has everything we need. MacPackagedBuild Build = new MacPackagedBuild( UnrealTargetPlatform.Mac, Config, Role, Di.FullName, BuildFlags.Packaged | BuildFlags.CanReplaceCommandLine | BuildFlags.CanReplaceExecutable); Builds.Add(Build); } } } return Builds; } } }