// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { partial struct UnrealArch { // @todo add x64 simulators to run on old macs? /// /// IOS Simulator /// public static UnrealArch IOSSimulator { get; } = FindOrAddByName("iossimulator", bIsX64: false); /// /// TVOS Simulator /// public static UnrealArch TVOSSimulator { get; } = FindOrAddByName("tvossimulator", bIsX64: false); private static IReadOnlyDictionary AppleToolchainArchitectures = new Dictionary() { { UnrealArch.Arm64, "arm64" }, { UnrealArch.X64, "x86_64" }, { UnrealArch.IOSSimulator, "arm64" }, { UnrealArch.TVOSSimulator, "arm64" }, }; /// /// Apple-specific low level name for the generic platforms /// public string AppleName { get { if (AppleToolchainArchitectures.ContainsKey(this)) { return AppleToolchainArchitectures[this]; } throw new BuildException($"Unknown architecture {ToString()} passed to UnrealArch.AppleName"); } } } /// /// IOS-specific target settings /// public partial class IOSTargetRules { /// /// Whether to strip iOS symbols or not (implied by Shipping config). /// [XmlConfigFile(Category = "BuildConfiguration")] [CommandLine("-stripsymbols", Value = "true")] public bool bStripSymbols = false; /// /// /// public bool bShipForBitcode = false; /// /// If true, then a stub IPA will be generated when compiling is done (minimal files needed for a valid IPA). /// [CommandLine("-CreateStub", Value = "true")] public bool bCreateStubIPA = false; /// /// Whether to generate a native Xcode project as a wrapper for the framework. /// public bool bGenerateFrameworkWrapperProject = false; /// /// Don't generate crashlytics data /// [CommandLine("-alwaysgeneratedsym", Value = "true")] [CommandLine("-EnableDSYM", Value = "true")] [XmlConfigFile(Category = "BuildConfiguration", Name = "bUseDSYMFiles")] public bool bGeneratedSYM = false; /// /// Don't generate crashlytics data /// [CommandLine("-skipcrashlytics")] public bool bSkipCrashlytics = false; /// /// Disables clang build verification checks on static libraries /// [CommandLine("-skipclangvalidation", Value = "true")] [XmlConfigFile(Category = "BuildConfiguration", Name = "bSkipClangValidation")] public bool bSkipClangValidation = false; /// /// Mark the build for distribution /// [CommandLine("-distribution")] public bool bForDistribution = false; /// /// Manual override for the provision to use. Should be a full path. /// [CommandLine("-ImportProvision=")] public string? ImportProvision = null; /// /// Imports the given certificate (inc private key) into a temporary keychain before signing. /// [CommandLine("-ImportCertificate=")] public string? ImportCertificate = null; /// /// Password for the imported certificate /// [CommandLine("-ImportCertificatePassword=")] public string? ImportCertificatePassword = null; /// /// Cached project settings for the target (set in ResetTarget) /// public IOSProjectSettings? ProjectSettings = null; /// /// Enables address sanitizer (ASan) /// [CommandLine("-EnableASan")] public bool bEnableAddressSanitizer = false; /// /// Enables thread sanitizer (TSan) /// [CommandLine("-EnableTSan")] public bool bEnableThreadSanitizer = false; /// /// Enables undefined behavior sanitizer (UBSan) /// [CommandLine("-EnableUBSan")] public bool bEnableUndefinedBehaviorSanitizer = false; /// /// Constructor /// public IOSTargetRules() { string? AddressSanitizer = Environment.GetEnvironmentVariable("ENABLE_ADDRESS_SANITIZER"); if (AddressSanitizer != null && AddressSanitizer == "YES") { bEnableAddressSanitizer = true; } string? ThreadSanitizer = Environment.GetEnvironmentVariable("ENABLE_THREAD_SANITIZER"); if (ThreadSanitizer != null && ThreadSanitizer == "YES") { bEnableThreadSanitizer = true; } string? UndefSanitizerMode = Environment.GetEnvironmentVariable("ENABLE_UNDEFINED_BEHAVIOR_SANITIZER"); if (UndefSanitizerMode != null && UndefSanitizerMode == "YES") { bEnableUndefinedBehaviorSanitizer = true; } } } /// /// Read-only wrapper for IOS-specific target settings /// public partial class ReadOnlyIOSTargetRules { /// /// The private mutable settings object /// private IOSTargetRules Inner; /// /// Constructor /// /// The settings object to wrap public ReadOnlyIOSTargetRules(IOSTargetRules Inner) { this.Inner = Inner; } /// /// Accessors for fields on the inner TargetRules instance /// #region Read-only accessor properties #pragma warning disable CS1591 public bool bStripSymbols => Inner.bStripSymbols; public bool bGenerateFrameworkWrapperProject => Inner.bGenerateFrameworkWrapperProject; public bool bGeneratedSYM => Inner.bGeneratedSYM; public bool bCreateStubIPA => Inner.bCreateStubIPA; public bool bSkipCrashlytics => Inner.bSkipCrashlytics; public bool bSkipClangValidation => Inner.bSkipClangValidation; public bool bForDistribution => Inner.bForDistribution; public string? ImportProvision => Inner.ImportProvision; public string? ImportCertificate => Inner.ImportCertificate; public string? ImportCertificatePassword => Inner.ImportCertificatePassword; public float RuntimeVersion => Single.Parse(Inner.ProjectSettings!.RuntimeVersion, System.Globalization.CultureInfo.InvariantCulture); public bool bEnableAddressSanitizer => Inner.bEnableAddressSanitizer; public bool bEnableThreadSanitizer => Inner.bEnableThreadSanitizer; public bool bEnableUndefinedBehaviorSanitizer => Inner.bEnableUndefinedBehaviorSanitizer; #pragma warning restore CS1591 #endregion } /// /// Stores project-specific IOS settings. Instances of this object are cached by IOSPlatform. /// public class IOSProjectSettings { /// /// The cached project file location /// public readonly FileReference? ProjectFile; /// /// Whether to build the iOS project as a framework. /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bBuildAsFramework")] [CommandLine("-build-as-framework")] public readonly bool bBuildAsFramework = false; /// /// Whether to generate a native Xcode project as a wrapper for the framework. /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bGenerateFrameworkWrapperProject")] public readonly bool bGenerateFrameworkWrapperProject = false; /// /// Whether to generate a dSYM file or not. /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bGeneratedSYMFile")] [CommandLine("-generatedsymfile")] public readonly bool bGeneratedSYMFile = false; /// /// Whether to generate a dSYM bundle (as opposed to single file dSYM) /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bGeneratedSYMBundle")] [CommandLine("-generatedsymbundle")] public readonly bool bGeneratedSYMBundle = false; /// /// Whether to generate a dSYM file or not. /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bGenerateCrashReportSymbols")] public readonly bool bGenerateCrashReportSymbols = false; /// /// The minimum supported version /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "MinimumiOSVersion")] private readonly string? MinimumIOSVersion = null; /// /// Whether to support iPhone /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsIPhone")] private readonly bool bSupportsIPhone = true; /// /// Whether to support iPad /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsIPad")] private readonly bool bSupportsIPad = true; /// /// additional linker flags for shipping /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "AdditionalShippingLinkerFlags")] public readonly string AdditionalShippingLinkerFlags = ""; /// /// additional linker flags for non-shipping /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "AdditionalLinkerFlags")] public readonly string AdditionalLinkerFlags = ""; /// /// mobile provision to use for code signing /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "MobileProvision")] public readonly string MobileProvision = ""; /// /// signing certificate to use for code signing /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "SigningCertificate")] public readonly string SigningCertificate = ""; /// /// true if notifications are enabled /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bEnableStoreKitSupport")] public readonly bool bEnableStoreKitSupport = true; /// /// true if notifications are enabled /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bEnableRemoteNotificationsSupport")] public readonly bool bNotificationsEnabled = false; /// /// true if notifications are enabled /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bEnableBackgroundFetch")] public readonly bool bBackgroundFetchEnabled = false; /// /// true if iTunes file sharing support is enabled /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsITunesFileSharing")] public readonly bool bFileSharingEnabled = false; /// /// The bundle identifier /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleIdentifier")] public readonly string BundleIdentifier = ""; /// /// true if using Xcode managed provisioning, else false /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bAutomaticSigning")] public readonly bool bAutomaticSigning = false; /// /// The IOS Team ID /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "IOSTeamID")] public readonly string TeamID = ""; /// /// true to change FORCEINLINE to a regular INLINE. /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bDisableForceInline")] public readonly bool bDisableForceInline = false; /// /// true if IDFA are enabled /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bEnableAdvertisingIdentifier")] public readonly bool bEnableAdvertisingIdentifier = false; /// /// true when building for distribution /// [ConfigFile(ConfigHierarchyType.Game, "/Script/UnrealEd.ProjectPackagingSettings", "ForDistribution")] public readonly bool bForDistribution = false; /// /// override for the app's display name if different from the project name /// [ConfigFile(ConfigHierarchyType.Game, "/Script/UnrealEd.ProjectPackagingSettings", "BundleName")] public readonly string BundleName = ""; /// /// longer display name than BundleName if needed /// [ConfigFile(ConfigHierarchyType.Game, "/Script/UnrealEd.ProjectPackagingSettings", "BundleDisplayName")] public readonly string BundleDisplayName = ""; /// /// Which version of the iOS to allow at run time /// public virtual string RuntimeVersion { get { switch (MinimumIOSVersion) { case "IOS_15": return "15.0"; case "IOS_16": return "16.0"; case "IOS_17": return "17.0"; case "IOS_18": return "18.0"; case "IOS_Minimum": default: if (String.IsNullOrEmpty(MinimumIOSVersion) || MinimumIOSVersion.StartsWith("IOS_")) { return UEBuildPlatformSDK.GetSDKForPlatform("IOS")!.GetSoftwareInfo()!.Min!; } else { // Assume the user is overriding the defaults, ie: "17.4.1" return MinimumIOSVersion; } } } } /// /// Enables iOS 16 dynamic linker workaround /// [ConfigFile(ConfigHierarchyType.Engine, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bEnableIOS16DynLinkerBugWAR")] public readonly bool bEnableIOS16DynLinkerBugWAR = false; /// /// which devices the game is allowed to run on /// public virtual string RuntimeDevices { get { if (bSupportsIPad && !bSupportsIPhone) { return "2"; } else if (!bSupportsIPad && bSupportsIPhone) { return "1"; } else { return "1,2"; } } } /// /// Constructor /// /// The project file to read settings for /// Bundle identifier needed when project file is empty public IOSProjectSettings(FileReference? ProjectFile, string? Bundle) : this(ProjectFile, UnrealTargetPlatform.IOS, Bundle) { } /// /// Protected constructor. Used by TVOSProjectSettings. /// /// The project file to read settings for /// The platform to read settings for /// Bundle identifier needed when project file is empty protected IOSProjectSettings(FileReference? ProjectFile, UnrealTargetPlatform Platform, string? Bundle) { this.ProjectFile = ProjectFile; ConfigCache.ReadSettings(DirectoryReference.FromFile(ProjectFile), Platform, this); if ((ProjectFile == null || String.IsNullOrEmpty(ProjectFile.FullName)) && !String.IsNullOrEmpty(Bundle)) { BundleIdentifier = Bundle; } BundleIdentifier = BundleIdentifier.Replace("[PROJECT_NAME]", ((ProjectFile != null) ? ProjectFile.GetFileNameWithoutAnyExtensions() : "UnrealGame")).Replace("_", ""); } } /// /// IOS provisioning data /// class IOSProvisioningData { public string? SigningCertificate; public FileReference? MobileProvisionFile; public string? MobileProvisionUUID; public string? MobileProvisionName; public string? TeamUUID; public string? BundleIdentifier; public bool bHaveCertificate = false; public string? MobileProvision => MobileProvisionFile?.GetFileName(); public IOSProvisioningData(IOSProjectSettings ProjectSettings, bool bForDistribution, ILogger Logger) : this(ProjectSettings, false, bForDistribution, Logger) { } protected IOSProvisioningData(IOSProjectSettings ProjectSettings, bool bIsTVOS, bool bForDistribution, ILogger Logger) { SigningCertificate = ProjectSettings.SigningCertificate; string? MobileProvision = ProjectSettings.MobileProvision; FileReference? ProjectFile = ProjectSettings.ProjectFile; CodeSigningConfig.Initialize(ProjectFile, bIsTVOS); if (!String.IsNullOrEmpty(SigningCertificate)) { List Certs = AppleCodeSign.FindCertificates(); List Provisions = AppleCodeSign.FindProvisions(ProjectSettings.BundleIdentifier, bForDistribution, out _); } else { SigningCertificate = bForDistribution ? "iPhone Distribution" : "iPhone Developer"; bHaveCertificate = true; } if (!String.IsNullOrEmpty(MobileProvision)) { DirectoryReference MobileProvisionDir; if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { MobileProvisionDir = DirectoryReference.Combine(new DirectoryReference(Environment.GetEnvironmentVariable("HOME")!), "Library", "MobileDevice", "Provisioning Profiles"); } else { MobileProvisionDir = DirectoryReference.Combine(DirectoryReference.GetSpecialFolder(Environment.SpecialFolder.LocalApplicationData)!, "Apple Computer", "MobileDevice", "Provisioning Profiles"); } FileReference PossibleMobileProvisionFile = FileReference.Combine(MobileProvisionDir, MobileProvision); if (FileReference.Exists(PossibleMobileProvisionFile)) { MobileProvisionFile = PossibleMobileProvisionFile; } } if (MobileProvisionFile == null || !bHaveCertificate) { SigningCertificate = ""; MobileProvision = ""; MobileProvisionFile = null; Logger.LogInformation("Provision not specified or not found for {Project}, searching for compatible match...", ((ProjectFile != null) ? ProjectFile.GetFileNameWithoutAnyExtensions() : "UnrealGame")); if (AppleCodeSign.FindCertAndProvision(ProjectSettings.BundleIdentifier, out MobileProvisionFile, out SigningCertificate)) { MobileProvision = MobileProvisionFile!.FullName; } if (MobileProvisionFile != null) { Logger.LogInformation("Provision found for {Project}, Provision: {Provision}, Certificate: {Certificate}", ((ProjectFile != null) ? ProjectFile.GetFileNameWithoutAnyExtensions() : "UnrealGame"), MobileProvisionFile, SigningCertificate); } } // add to the dictionary SigningCertificate = SigningCertificate.Replace("\"", ""); // read the provision to get the UUID if (MobileProvisionFile == null) { Logger.LogInformation("No matching provision file was discovered for {ProjectFile}. Please ensure you have a compatible provision installed.", ProjectFile); } else if (!FileReference.Exists(MobileProvisionFile)) { Logger.LogInformation("Selected mobile provision for {ProjectFile} ({MobileProvisionFile}) was not found. Please ensure you have a compatible provision installed.", ProjectFile, MobileProvisionFile); } else { byte[] AllBytes = FileReference.ReadAllBytes(MobileProvisionFile); uint StartIndex = (uint)AllBytes.Length; uint EndIndex = (uint)AllBytes.Length; for (uint i = 0; i + 4 < AllBytes.Length; i++) { if (AllBytes[i] == '<' && AllBytes[i + 1] == '?' && AllBytes[i + 2] == 'x' && AllBytes[i + 3] == 'm' && AllBytes[i + 4] == 'l') { StartIndex = i; break; } } if (StartIndex < AllBytes.Length) { for (uint i = StartIndex; i + 7 < AllBytes.Length; i++) { if (AllBytes[i] == '<' && AllBytes[i + 1] == '/' && AllBytes[i + 2] == 'p' && AllBytes[i + 3] == 'l' && AllBytes[i + 4] == 'i' && AllBytes[i + 5] == 's' && AllBytes[i + 6] == 't' && AllBytes[i + 7] == '>') { EndIndex = i + 7; break; } } } if (StartIndex < AllBytes.Length && EndIndex < AllBytes.Length) { byte[] TextBytes = new byte[EndIndex - StartIndex]; Buffer.BlockCopy(AllBytes, (int)StartIndex, TextBytes, 0, (int)(EndIndex - StartIndex)); string AllText = Encoding.UTF8.GetString(TextBytes); int idx = AllText.IndexOf("UUID"); if (idx > 0) { idx = AllText.IndexOf("", idx); if (idx > 0) { idx += "".Length; MobileProvisionUUID = AllText.Substring(idx, AllText.IndexOf("", idx) - idx); } } idx = AllText.IndexOf("com.apple.developer.team-identifier"); if (idx > 0) { idx = AllText.IndexOf("", idx); if (idx > 0) { idx += "".Length; TeamUUID = AllText.Substring(idx, AllText.IndexOf("", idx) - idx); } } idx = AllText.IndexOf("application-identifier"); if (idx > 0) { idx = AllText.IndexOf("", idx); if (idx > 0) { idx += "".Length; string FullID = AllText.Substring(idx, AllText.IndexOf("", idx) - idx); BundleIdentifier = FullID.Substring(FullID.IndexOf('.') + 1); } } idx = AllText.IndexOf("Name"); if (idx > 0) { idx = AllText.IndexOf("", idx); if (idx > 0) { idx += "".Length; MobileProvisionName = AllText.Substring(idx, AllText.IndexOf("", idx) - idx); } } } if (String.IsNullOrEmpty(MobileProvisionUUID) || String.IsNullOrEmpty(TeamUUID)) { MobileProvision = null; SigningCertificate = null; Logger.LogInformation("Failed to parse the mobile provisioning profile."); } } } void IPPDataReceivedHandler(object Sender, DataReceivedEventArgs Line, ILogger Logger) { if ((Line != null) && (Line.Data != null)) { if (Line.Data.StartsWith("IPP WARNING:")) { // Don't output IPP warnings to the console as they may not be warnings relevant to the build and could cause build failures. Logger.LogDebug("{LineData}", Line.Data); } else { Logger.LogInformation("{LineData}", Line.Data); } if (!String.IsNullOrEmpty(SigningCertificate)) { if (Line.Data.Contains("CERTIFICATE-") && Line.Data.Contains(SigningCertificate)) { bHaveCertificate = true; } } else { int cindex = Line.Data.IndexOf("CERTIFICATE-"); int pindex = Line.Data.IndexOf("PROVISION-"); if (cindex > -1 && pindex > -1) { cindex += "CERTIFICATE-".Length; SigningCertificate = Line.Data.Substring(cindex, pindex - cindex - 1); pindex += "PROVISION-".Length; if (pindex < Line.Data.Length) { MobileProvisionFile = new FileReference(Line.Data.Substring(pindex)); } } } } } } class IOSArchitectureConfig : UnrealArchitectureConfig { public IOSArchitectureConfig() : base(UnrealArchitectureMode.SingleTargetCompileSeparately, new[] { UnrealArch.Arm64, UnrealArch.IOSSimulator }) { } public override UnrealArchitectures ActiveArchitectures(FileReference? ProjectFile, string? TargetName) { // always use arm64 unless overridden on command line return new UnrealArchitectures(UnrealArch.Arm64); } } class IOSPlatform : AppleBuildPlatform { List CachedProjectSettings = new List(); List CachedProjectSettingsByBundle = new List(); Dictionary ProvisionCache = new Dictionary(); public IOSPlatform(UEBuildPlatformSDK InSDK, ILogger Logger) : this(InSDK, UnrealTargetPlatform.IOS, Logger) { } protected IOSPlatform(UEBuildPlatformSDK InSDK, UnrealTargetPlatform TargetPlatform, ILogger Logger) : base(TargetPlatform, InSDK, new IOSArchitectureConfig(), Logger) { } public override List FinalizeBinaryPaths(FileReference BinaryName, FileReference? ProjectFile, ReadOnlyTargetRules Target) { List BinaryPaths = new List(); if (Target.bShouldCompileAsDLL) { BinaryPaths.Add(FileReference.Combine(BinaryName.Directory, Target.Configuration.ToString(), Target.Name + ".framework", Target.Name)); } else { BinaryPaths.Add(BinaryName); } return BinaryPaths; } public override void ResetTarget(TargetRules Target) { Target.bDeployAfterCompile = true; Target.IOSPlatform.ProjectSettings = ((IOSPlatform)GetBuildPlatform(Target.Platform)).ReadProjectSettings(Target.ProjectFile); if (!AppleExports.UseModernXcode(Target.ProjectFile)) { // always strip in shipping configuration (commandline could have set it also) if (Target.Configuration == UnrealTargetConfiguration.Shipping) { Target.IOSPlatform.bStripSymbols = true; } // if we are stripping the executable, or if the project requested it, or if it's a buildmachine, generate the dsym if (Target.IOSPlatform.bStripSymbols || Target.IOSPlatform.ProjectSettings.bGeneratedSYMFile || Unreal.IsBuildMachine()) { Target.IOSPlatform.bGeneratedSYM = true; } } // Set bShouldCompileAsDLL when building as a framework Target.bShouldCompileAsDLL = Target.IOSPlatform.ProjectSettings.bBuildAsFramework; } public override void ValidateTarget(TargetRules Target) { base.ValidateTarget(Target); if (!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CLANG_STATIC_ANALYZER_MODE"))) { Target.StaticAnalyzer = StaticAnalyzer.Default; Target.StaticAnalyzerOutputType = (Environment.GetEnvironmentVariable("CLANG_ANALYZER_OUTPUT")?.Contains("html", StringComparison.OrdinalIgnoreCase) == true) ? StaticAnalyzerOutputType.Html : StaticAnalyzerOutputType.Text; Target.StaticAnalyzerMode = String.Equals(Environment.GetEnvironmentVariable("CLANG_STATIC_ANALYZER_MODE"), "shallow", StringComparison.OrdinalIgnoreCase) ? StaticAnalyzerMode.Shallow : StaticAnalyzerMode.Deep; } else if (Target.StaticAnalyzer == StaticAnalyzer.Clang) { Target.StaticAnalyzer = StaticAnalyzer.Default; } // Disable linking and ignore build outputs if we're using a static analyzer if (Target.StaticAnalyzer == StaticAnalyzer.Default) { Target.bDisableLinking = true; Target.bIgnoreBuildOutputs = true; // Clang static analysis requires non unity builds Target.bUseUnityBuild = false; if (Target.bStaticAnalyzerIncludeGenerated) { Target.bAlwaysUseUnityForGeneratedFiles = false; } } // we assume now we are building with IOS8 or later if (Target.bCompileAgainstEngine) { Target.GlobalDefinitions.Add("HAS_METAL=1"); Target.ExtraModuleNames.Add("MetalRHI"); } else { Target.GlobalDefinitions.Add("HAS_METAL=0"); } if (Target.bShouldCompileAsDLL) { int PreviousDefinition = Target.GlobalDefinitions.FindIndex(s => s.Contains("BUILD_EMBEDDED_APP")); if (PreviousDefinition >= 0) { Target.GlobalDefinitions.RemoveAt(PreviousDefinition); } Target.GlobalDefinitions.Add("BUILD_EMBEDDED_APP=1"); if (Target.Platform == UnrealTargetPlatform.IOS) { Target.ExportPublicHeader = "Headers/PreIOSEmbeddedView.h"; } } Target.bCheckSystemHeadersForModification = false; if (Target.StaticAllocator == StaticAllocatorType.None) { // Prefer MB2 as MB3 requires extended virtual address space entitlement Target.StaticAllocator = StaticAllocatorType.Binned2; } if (Target.IOSPlatform.bEnableAddressSanitizer) { Target.StaticAllocator = StaticAllocatorType.Ansi; } } public override void ValidateModule(UEBuildModule Module, ReadOnlyTargetRules Target, ILogger Logger) { base.ValidateModule(Module, Target, Logger); // @todo temporarily disabling due to VisionOS rquiring newer Xcode than this will allow - we may remove this entirely #if false if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac && !Target.IOSPlatform.bSkipClangValidation) { ApplePlatformSDK SDK = (ApplePlatformSDK?)GetSDK() ?? new ApplePlatformSDK(Logger); foreach (FileReference LibLoc in Module.PublicLibraries) { switch (LibLoc.GetExtension()) { case ".a": { // When static lib, grep it string Args = "-c \"strings "; Args += LibLoc.FullName; Args += " | grep -m1 -i \\(clang\""; string StdOutResult = Utils.RunLocalProcessAndReturnStdOut("bash", Args); if (String.IsNullOrEmpty(StdOutResult)) { continue; } // This Regex will extract a 2-4 segment version code from string containing a 2-5 segment code // ie: if given string: "Apple clang version 14.0.0 (clang-1400.0.17.3.1)" // it'll extract: "1400.0.17.3" (note the dropped 5th segment) Match M = Regex.Match(StdOutResult, @"(\(clang-(?\d+.\d+(.(\d+))?(.(\d+))?)(.(\d+))?\))"); if (M.Success) { string LibString = M.Groups["ver"].ToString(); Version? LibVersion = new Version(LibString); if (LibVersion != null && LibVersion > SDK.MinimumStaticLibClangVersion) { throw new BuildException("iOS Static Library:'{0}' is built with a version of clang newer than UE supports ({1} > {2}). \nPlease rebuild {3} with the minimum supported version of Xcode/clang.", LibLoc.GetFileName(), LibString, SDK.MinimumStaticLibClangVersion, LibLoc); } } } break; default: // For now, we don't validate any other types of libs (dylib, Framework, etc) break; } } } #endif } /// /// Determines if the given name is a build product for a target. /// /// The name to check /// Target or application names that may appear at the start of the build product name (eg. "UnrealEditor", "ShooterGameEditor") /// Suffixes which may appear at the end of the build product name /// True if the string matches the name of a build product, false otherwise public override bool IsBuildProduct(string FileName, string[] NamePrefixes, string[] NameSuffixes) { return IsBuildProductName(FileName, NamePrefixes, NameSuffixes, "") || IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".stub") || IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".dylib") || IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".dSYM") || IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".dSYM.zip") || IsBuildProductName(FileName, NamePrefixes, NameSuffixes, ".o"); } /// /// Get the extension to use for the given binary type /// /// The binary type being built /// string The binary extenstion (ie 'exe' or 'dll') public override string GetBinaryExtension(UEBuildBinaryType InBinaryType) { switch (InBinaryType) { case UEBuildBinaryType.DynamicLinkLibrary: return ".dylib"; case UEBuildBinaryType.Executable: return ""; case UEBuildBinaryType.StaticLibrary: return ".a"; } return base.GetBinaryExtension(InBinaryType); } public IOSProjectSettings ReadProjectSettings(FileReference? ProjectFile, string? Bundle = "") { IOSProjectSettings? ProjectSettings = null; // Use separate lists to prevent an overridden Bundle id polluting the standard project file. bool bCacheByBundle = !String.IsNullOrEmpty(Bundle); if (bCacheByBundle) { lock (CachedProjectSettingsByBundle) { ProjectSettings = CachedProjectSettingsByBundle.FirstOrDefault(x => x.ProjectFile == ProjectFile && x.BundleIdentifier == Bundle); if (ProjectSettings == null) { ProjectSettings = CreateProjectSettings(ProjectFile, Bundle); CachedProjectSettingsByBundle.Add(ProjectSettings); } } } else { lock (CachedProjectSettings) { ProjectSettings = CachedProjectSettings.FirstOrDefault(x => x.ProjectFile == ProjectFile); if (ProjectSettings == null) { ProjectSettings = CreateProjectSettings(ProjectFile, Bundle); CachedProjectSettings.Add(ProjectSettings); } } } return ProjectSettings; } protected virtual IOSProjectSettings CreateProjectSettings(FileReference? ProjectFile, string? Bundle) { return new IOSProjectSettings(ProjectFile, Bundle); } public IOSProvisioningData ReadProvisioningData(FileReference? ProjectFile, bool bForDistribution = false, string? Bundle = "") { IOSProjectSettings ProjectSettings = ReadProjectSettings(ProjectFile, Bundle); return ReadProvisioningData(ProjectSettings, bForDistribution); } public IOSProvisioningData ReadProvisioningData(IOSProjectSettings ProjectSettings, bool bForDistribution = false) { string ProvisionKey = ProjectSettings.BundleIdentifier + " " + bForDistribution.ToString(); IOSProvisioningData? ProvisioningData; lock (ProvisionCache) { if (!ProvisionCache.TryGetValue(ProvisionKey, out ProvisioningData)) { ProvisioningData = CreateProvisioningData(ProjectSettings, bForDistribution); ProvisionCache.Add(ProvisionKey, ProvisioningData); } } return ProvisioningData; } protected virtual IOSProvisioningData CreateProvisioningData(IOSProjectSettings ProjectSettings, bool bForDistribution) { return new IOSProvisioningData(ProjectSettings, bForDistribution, Logger); } public override string[] GetDebugInfoExtensions(ReadOnlyTargetRules InTarget, UEBuildBinaryType InBinaryType) { if (InTarget.IOSPlatform.bGeneratedSYM) { IOSProjectSettings ProjectSettings = ReadProjectSettings(InTarget.ProjectFile); // which format? if (ProjectSettings.bGeneratedSYMBundle) { return new string[] { ".dSYM.zip" }; } else { return new string[] { ".dSYM" }; } } return Array.Empty(); } public override bool CanUseXGE() { return BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac; } public override bool CanUseFASTBuild() { return true; } public bool HasCustomIcons(DirectoryReference ProjectDirectoryName, ILogger Logger) { string IconDir = Path.Combine(ProjectDirectoryName.FullName, "Build", "IOS", "Resources", "Graphics"); if (Directory.Exists(IconDir)) { foreach (string f in Directory.EnumerateFiles(IconDir)) { if (f.Contains("Icon") && Path.GetExtension(f).Contains(".png")) { Logger.LogInformation("Requiring custom build because project {Project} has custom icons", Path.GetFileName(ProjectDirectoryName.FullName)); return true; } } } return false; } /// /// Check for the default configuration /// return true if the project uses the default build config /// public override bool HasDefaultBuildConfig(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { string[] BoolKeys = new string[] { "bShipForBitcode", "bGeneratedSYMFile", "bGeneratedSYMBundle", "bEnableRemoteNotificationsSupport", "bEnableCloudKitSupport", "bGenerateCrashReportSymbols", "bEnableBackgroundFetch" }; string[] StringKeys = new string[] { "MinimumiOSVersion", "AdditionalLinkerFlags", "AdditionalShippingLinkerFlags" }; // check for custom icons if (HasCustomIcons(ProjectDirectoryName, Logger)) { return false; } // look up iOS specific settings if (!DoProjectSettingsMatchDefault(Platform, ProjectDirectoryName, "/Script/IOSRuntimeSettings.IOSRuntimeSettings", BoolKeys, null, StringKeys, Logger)) { return false; } // check the base settings return base.HasDefaultBuildConfig(Platform, ProjectDirectoryName); } /// /// Check for the build requirement due to platform requirements /// return true if the project requires a build /// public override bool RequiresBuild(UnrealTargetPlatform Platform, DirectoryReference ProjectDirectoryName) { // check for custom icons return HasCustomIcons(ProjectDirectoryName, Logger); } public override bool ShouldCompileMonolithicBinary(UnrealTargetPlatform InPlatform) { // This platform currently always compiles monolithic return true; } /// /// Modify the rules for a newly created module, where the target is a different host platform. /// This is not required - but allows for hiding details of a particular platform. /// /// The name of the module /// The module rules /// The target being build public override void ModifyModuleRulesForOtherPlatform(string ModuleName, ModuleRules Rules, ReadOnlyTargetRules Target) { bool bIsPlatformAvailableForTarget = UEBuildPlatform.IsPlatformAvailableForTarget(Platform, Target, bIgnoreSDKCheck: true); bool bIsPlatformAvailableForTargetWithSDK = UEBuildPlatform.IsPlatformAvailableForTarget(Platform, Target); // don't do any target platform stuff if SDK is not available if (!bIsPlatformAvailableForTarget) { return; } if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Mac)) { // For Windows we use the MetalDeveloperTools and we will check if the toolchain is present at runtime bool bBuildShaderFormats = Target.bForceBuildShaderFormats || Target.Platform == UnrealTargetPlatform.Win64; if (!Target.bBuildRequiresCookedData) { if (ModuleName == "Engine") { if (Target.bBuildDeveloperTools) { Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatformSettings"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatformSettings"); if (bIsPlatformAvailableForTargetWithSDK) { Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatform"); Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatformControls"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatform"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatformControls"); } } } else if (ModuleName == "TargetPlatform") { if (bIsPlatformAvailableForTargetWithSDK) { bBuildShaderFormats = true; Rules.DynamicallyLoadedModuleNames.Add("TextureFormatASTC"); Rules.DynamicallyLoadedModuleNames.Add("TextureFormatETC2"); if (Target.bBuildDeveloperTools && Target.bCompileAgainstEngine) { Rules.DynamicallyLoadedModuleNames.Add("AudioFormatADPCM"); } } } } // allow standalone tools to use targetplatform modules, without needing Engine if (ModuleName == "TargetPlatform") { if (Target.bForceBuildTargetPlatforms) { Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatformSettings"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatformSettings"); if (bIsPlatformAvailableForTargetWithSDK) { Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatform"); Rules.DynamicallyLoadedModuleNames.Add("IOSTargetPlatformControls"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatform"); Rules.DynamicallyLoadedModuleNames.Add("TVOSTargetPlatformControls"); } } if (bBuildShaderFormats) { Rules.DynamicallyLoadedModuleNames.Add("MetalShaderFormat"); } } if (ModuleName == "UnrealEd" && bIsPlatformAvailableForTargetWithSDK) { Rules.DynamicallyLoadedModuleNames.Add("IOSPlatformEditor"); } } } public override void ModifyModuleRulesForActivePlatform(string ModuleName, ModuleRules Rules, ReadOnlyTargetRules Target) { if (ModuleName == "Launch") { Rules.PrivateDependencyModuleNames.AddRange(new string[] { "AudioMixerAudioUnit", "IOSAudio", "LaunchDaemonMessages", }); Rules.DynamicallyLoadedModuleNames.AddRange(new string[] { "IOSLocalNotification", "IOSRuntimeSettings", }); // needed for Metal layer Rules.PublicFrameworks.Add("QuartzCore"); IOSProjectSettings ProjectSettings = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(Target.Platform)).ReadProjectSettings(Target.ProjectFile); if (ProjectSettings.bEnableIOS16DynLinkerBugWAR && Target.Platform == UnrealTargetPlatform.IOS) { Rules.PublicDependencyModuleNames.Add("Interpose"); } } } /// /// Whether this platform should create debug information or not /// /// The target being built /// bool true if debug info should be generated, false if not public override bool ShouldCreateDebugInfo(ReadOnlyTargetRules Target) { return true; } /// /// Setup the target environment for building /// /// Settings for the target being compiled /// The compile environment for this target /// The link environment for this target public override void SetUpEnvironment(ReadOnlyTargetRules Target, CppCompileEnvironment CompileEnvironment, LinkEnvironment LinkEnvironment) { base.SetUpEnvironment(Target, CompileEnvironment, LinkEnvironment); IOSProjectSettings ProjectSettings = ((IOSPlatform)UEBuildPlatform.GetBuildPlatform(Target.Platform)).ReadProjectSettings(Target.ProjectFile); if (!ProjectFileGenerator.bGenerateProjectFiles) { Logger.LogInformation("Compiling against OS Version {RuntimeVersion} [minimum allowed at runtime]", ProjectSettings.RuntimeVersion); } CompileEnvironment.Definitions.Add("PLATFORM_IOS=1"); // set a define that is _only_ on iPhones, and not AppleTV, etc if (Target.Platform == UnrealTargetPlatform.IOS) { CompileEnvironment.Definitions.Add("UE_PLATFORM_IOS_ONLY=1"); } CompileEnvironment.Definitions.Add("WITH_TTS=0"); CompileEnvironment.Definitions.Add("WITH_SPEECH_RECOGNITION=0"); CompileEnvironment.Definitions.Add("WITH_EDITOR=0"); CompileEnvironment.Definitions.Add("USE_NULL_RHI=0"); if (ProjectSettings.bNotificationsEnabled) { CompileEnvironment.Definitions.Add("NOTIFICATIONS_ENABLED=1"); } else { CompileEnvironment.Definitions.Add("NOTIFICATIONS_ENABLED=0"); } if (ProjectSettings.bBackgroundFetchEnabled) { CompileEnvironment.Definitions.Add("BACKGROUNDFETCH_ENABLED=1"); } else { CompileEnvironment.Definitions.Add("BACKGROUNDFETCH_ENABLED=0"); } if (ProjectSettings.bFileSharingEnabled) { CompileEnvironment.Definitions.Add("FILESHARING_ENABLED=1"); } else { CompileEnvironment.Definitions.Add("FILESHARING_ENABLED=0"); } CompileEnvironment.Definitions.Add("UE_DISABLE_FORCE_INLINE=" + (ProjectSettings.bDisableForceInline ? "1" : "0")); if (Target.Architecture == UnrealArch.IOSSimulator || Target.Architecture == UnrealArch.TVOSSimulator) { CompileEnvironment.Definitions.Add("WITH_IOS_SIMULATOR=1"); } else { CompileEnvironment.Definitions.Add("WITH_IOS_SIMULATOR=0"); } if (ProjectSettings.bEnableAdvertisingIdentifier) { CompileEnvironment.Definitions.Add("ENABLE_ADVERTISING_IDENTIFIER=1"); } // if the project has an Oodle compression Dll, enable the decompressor on IOS if (Target.ProjectFile != null) { DirectoryReference ProjectDir = Target.ProjectFile.Directory; string OodleDllPath = DirectoryReference.Combine(ProjectDir, "Binaries/ThirdParty/Oodle/Mac/libUnrealPakPlugin.dylib").FullName; if (File.Exists(OodleDllPath)) { Logger.LogDebug(" Registering custom oodle compressor for {Platform}", UnrealTargetPlatform.IOS.ToString()); CompileEnvironment.Definitions.Add("REGISTER_OODLE_CUSTOM_COMPRESSOR=1"); } } // convert runtime version into standardized integer float TargetFloat = Target.IOSPlatform.RuntimeVersion; int IntPart = (int)TargetFloat; int FracPart = (int)((TargetFloat - IntPart) * 10); int TargetNum = IntPart * 10000 + FracPart * 100; CompileEnvironment.Definitions.Add("MINIMUM_UE_COMPILED_IOS_VERSION=" + TargetNum); LinkEnvironment.AdditionalFrameworks.Add(new UEBuildFramework("GameKit")); LinkEnvironment.AdditionalFrameworks.Add(new UEBuildFramework("DeviceCheck")); if (ProjectSettings.bEnableStoreKitSupport) { CompileEnvironment.Definitions.Add("UE_WITH_STORE_KIT=1"); LinkEnvironment.AdditionalFrameworks.Add(new UEBuildFramework("StoreKit")); } else { CompileEnvironment.Definitions.Add("UE_WITH_STORE_KIT=0"); } } /// /// Setup the binaries for this specific platform. /// /// The target being built /// public override void AddExtraModules(ReadOnlyTargetRules Target, List ExtraModuleNames) { if (Target.Type != TargetType.Program) { ExtraModuleNames.Add("IOSPlatformFeatures"); } } /// /// Creates a toolchain instance for the given platform. /// /// The target being built /// New toolchain instance. public override UEToolChain CreateToolChain(ReadOnlyTargetRules Target) { ClangToolChainOptions Options = ClangToolChainOptions.None; if (Target.IOSPlatform.bEnableAddressSanitizer) { Options |= ClangToolChainOptions.EnableAddressSanitizer; } if (Target.IOSPlatform.bEnableThreadSanitizer) { Options |= ClangToolChainOptions.EnableThreadSanitizer; } if (Target.IOSPlatform.bEnableUndefinedBehaviorSanitizer) { Options |= ClangToolChainOptions.EnableUndefinedBehaviorSanitizer; } IOSProjectSettings ProjectSettings = ReadProjectSettings(Target.ProjectFile); return new IOSToolChain(Target, ProjectSettings, Options, Logger); } /// public override void Deploy(TargetReceipt Receipt) { if (Receipt.HasValueForAdditionalProperty("CompileAsDll", "true")) { // IOSToolchain.PostBuildSync handles the copy, nothing else to do here } else { new UEDeployIOS(Logger).PrepTargetForDeployment(Receipt); } } } class IOSPlatformFactory : UEBuildPlatformFactory { public override UnrealTargetPlatform TargetPlatform => UnrealTargetPlatform.IOS; /// /// Register the platform with the UEBuildPlatform class /// public override void RegisterBuildPlatforms(ILogger Logger) { ApplePlatformSDK SDK = new ApplePlatformSDK(Logger); // Register this build platform for IOS UEBuildPlatform.RegisterBuildPlatform(new IOSPlatform(SDK, Logger), Logger); UEBuildPlatform.RegisterPlatformWithGroup(UnrealTargetPlatform.IOS, UnrealPlatformGroup.Apple); UEBuildPlatform.RegisterPlatformWithGroup(UnrealTargetPlatform.IOS, UnrealPlatformGroup.IOS); } } }