// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.Threading.Tasks; using EpicGames.Core; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Represents a Mac/IOS framework /// class UEBuildFramework { /// /// The name of this framework /// public readonly string Name; /// /// Path to a zip file containing the framework. May be null. /// public readonly FileReference? ZipFile; /// /// Path where the zip should be extracted. May be null. /// public readonly DirectoryReference? ZipOutputDirectory; /// /// Base path to the framework on disk. /// readonly DirectoryReference? FrameworkDirectory; /// /// /// public readonly string? CopyBundledAssets; /// /// Copy the framework to the target's Framework directory /// public readonly bool bCopyFramework = false; /// /// File created after the framework has been extracted. Used to add dependencies into the action graph. /// public FileItem? ExtractedTokenFile; /// /// List of variants contained in this XCFramework. Only non null for XCFrameworks /// readonly List? XCFrameworkVariants; /// /// Constructor /// /// The framework name /// public UEBuildFramework(string Name, string? CopyBundledAssets = null) { this.Name = Name; this.CopyBundledAssets = CopyBundledAssets; } /// /// Constructor /// /// The framework name /// Path to the zip file for this framework /// Path for the extracted zip file /// /// Copy the framework to the target's Framework directory public UEBuildFramework(string Name, FileReference? ZipFile, DirectoryReference? OutputDirectory, string? CopyBundledAssets, bool bCopyFramework) { this.Name = Name; this.ZipFile = ZipFile; this.ZipOutputDirectory = OutputDirectory; this.FrameworkDirectory = OutputDirectory; this.CopyBundledAssets = CopyBundledAssets; this.bCopyFramework = bCopyFramework; } /// /// Constructor /// /// The framework name /// Path for the framework on disk /// /// Copy the framework to the target's Framework directory public UEBuildFramework(String Name, DirectoryReference? FrameworkDirectory, string? CopyBundledAssets, bool bCopyFramework) { this.Name = Name; this.FrameworkDirectory = FrameworkDirectory; this.CopyBundledAssets = CopyBundledAssets; this.bCopyFramework = bCopyFramework; if( this.FrameworkDirectory != null && this.FrameworkDirectory.FullName.EndsWith(".xcframework")) { this.XCFrameworkVariants = LoadXCFrameworkVariants(); } } /// /// Gets the framework directory for given build settings /// /// /// public DirectoryReference? GetFrameworkDirectory(UnrealTargetPlatform Platform, string Architecture) { if( XCFrameworkVariants != null) { XCFrameworkVariantEntry? FrameworkVariant = XCFrameworkVariants.Find(x => x.Matches(Platform, Architecture)); if( FrameworkVariant != null ) { return DirectoryReference.Combine(FrameworkDirectory!, FrameworkVariant.LibraryIdentifier); } Log.TraceWarning($"Variant for platform \"{Platform.ToString()}\" with architecture {Architecture} not found in XCFramework {Name}."); } return FrameworkDirectory; } /// /// Loads XCFramework variants description from Info.plist file inside XCFramework structure /// List? LoadXCFrameworkVariants() { XmlDocument PlistDoc = new XmlDocument(); PlistDoc.Load(FileReference.Combine(FrameworkDirectory!, "Info.plist" ).FullName); // Check the plist type XmlNode? CFBundlePackageType = PlistDoc.SelectSingleNode("/plist/dict[key='CFBundlePackageType']/string[1]"); if( CFBundlePackageType == null || CFBundlePackageType.NodeType != XmlNodeType.Element || CFBundlePackageType.InnerText != "XFWK" ) { Log.TraceWarning($"CFBundlePackageType is not set to XFWK in Info.plist for XCFramework {Name}"); return null; } // Load Framework variants data from dictionary nodes XmlNodeList? FrameworkVariantDicts = PlistDoc.SelectNodes("/plist/dict[key='AvailableLibraries']/array/dict"); if( FrameworkVariantDicts == null ) { Log.TraceWarning($"Invalid Info.plist file for XCFramework {Name}. It will be used as a regular Framework"); return null; } List Variants = new List(); foreach( XmlNode VariantDict in FrameworkVariantDicts ) { XCFrameworkVariantEntry? Variant = XCFrameworkVariantEntry.Parse(VariantDict); if( Variant == null ) { Log.TraceWarning($"Failed to load variant from Info.plist file for XCFramework {Name}"); } else { Log.TraceVerbose($"Found {Variant.LibraryIdentifier} variant in XCFramework {Name}"); Variants.Add(Variant); } } return Variants; } /// /// Represents a XCFramework variant description /// private class XCFrameworkVariantEntry { /// /// Identifier of this framework variant. Is in the form platform-architectures-platformvariant. /// Some examples would be ios-arm64, ios-arm64_x86_64-simulator /// It also represents the relative path inside the XCFramefork where the Framework for this variant resides /// public readonly string LibraryIdentifier; /// /// Relative path where framework lives after applying LibraryIdentifier path /// public readonly string LibraryPath; /// /// List of supported architectures for this Framework variants /// public readonly List SupportedArchitectures; /// /// Platform this Framework variant is intended for. Possible values are ios, macos, watchos, tvos /// public readonly string SupportedPlatform; /// /// Platform variant for this Framework variant. It can be empty or any other value representing a platform variane, like maccatalyst or simulator /// public readonly string? SupportedPlatformVariant; /// /// Constructor /// /// /// /// /// /// XCFrameworkVariantEntry(string LibraryIdentifier, string LibraryPath, List SupportedArchitectures, string SupportedPlatform, string? SupportedPlatformVariant) { this.LibraryIdentifier = LibraryIdentifier; this.LibraryPath = LibraryPath; this.SupportedArchitectures = SupportedArchitectures; this.SupportedPlatform = SupportedPlatform; this.SupportedPlatformVariant = SupportedPlatformVariant; } /// /// Returns wether this variant is a match for given platform and architecture /// /// /// public bool Matches(UnrealTargetPlatform Platform, string Architecture) { if(( Platform == UnrealTargetPlatform.IOS && SupportedPlatform == "ios" ) || ( Platform == UnrealTargetPlatform.Mac && SupportedPlatform == "macos" ) || ( Platform == UnrealTargetPlatform.TVOS && SupportedPlatform == "tvos" )) { if( Architecture == "-simulator" ) { // When using -simulator we don't have the actual architecture. Assume arm64 as other parts of UBT already are doing return ( SupportedPlatformVariant == "simulator" ) && SupportedArchitectures.Contains("arm64"); } else { return ( SupportedPlatformVariant == null ) && SupportedArchitectures.Contains(Architecture); } } return false; } /// /// Creates a XCFrameworkVariantEntry by parsing the content from the XCFramework Info.plist file /// /// XmlNode loaded form Info.plist containing the representation of a <dict> that contains the variant description public static XCFrameworkVariantEntry? Parse(XmlNode DictNode) { // Entries from a in a plist file always contains a node and a value node if(( DictNode.ChildNodes.Count % 2 ) != 0) { Log.TraceVerbose($"Invalid dict in Info.plist file for XCFramework"); return null; } string? LibraryIdentifier = null; string? LibraryPath = null; List? SupportedArchitectures = null; string? SupportedPlatform = null; string? SupportedPlatformVariant = null; for( int i = 0; i < DictNode.ChildNodes.Count; i += 2 ) { XmlNode? Key = DictNode.ChildNodes[i]; XmlNode? Value = DictNode.ChildNodes[i + 1]; if( Value != null && Key != null && Key.Name == "key" ) { switch( Key.InnerText ) { case "LibraryIdentifier": LibraryIdentifier = ParseString(Value); break; case "LibraryPath": LibraryPath = ParseString(Value); break; case "SupportedPlatform": SupportedPlatform = ParseString(Value); break; case "SupportedPlatformVariant": SupportedPlatformVariant = ParseString(Value); break; case "SupportedArchitectures": SupportedArchitectures = ParseListOfStrings(Value); break; default: break; } } } // All fields but SupportedPlatformVariant are allowed to be present in the framework variant descriprion if( LibraryIdentifier == null || LibraryPath == null || SupportedArchitectures == null || SupportedPlatform == null ) { Log.TraceVerbose($"Missing data in Info.plist file for XCFramework"); return null; } return new XCFrameworkVariantEntry( LibraryIdentifier, LibraryPath, SupportedArchitectures, SupportedPlatform, SupportedPlatformVariant ); } /// /// Parses the content from a string value node in a plist file /// /// XmlNode we expect to contain a <string> value static string? ParseString( XmlNode ValueNode ) { if( ValueNode.Name != "string" ) { Log.TraceVerbose($"Unexpected tag \"{ValueNode.Name}\" while expecting \"string\" in Info.plist file for XCFramework"); return null; } return ValueNode.InnerText; } /// /// Parses the content from an array value node in a plist file that has several string entries /// /// XmlNode we expect to contain a <array> value with several <string> entries static List? ParseListOfStrings( XmlNode ValueNode ) { if( ValueNode.Name != "array" ) { Log.TraceVerbose($"Unexpected tag \"{ValueNode.Name}\" while expecting \"array\" in Info.plist file for XCFramework"); return null; } List ListOfStrings = new List(); foreach( XmlNode ChildNode in ValueNode.ChildNodes ) { string? ParsedString = ParseString( ChildNode ); if( ParsedString != null ) { ListOfStrings.Add( ParsedString ); } } return ListOfStrings; } } } }