2019-12-26 23:01:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-01-08 11:38:48 -05:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using AutomationTool ;
using UnrealBuildTool ;
using System.Threading ;
using System.Text.RegularExpressions ;
using System.Linq ;
2022-11-12 18:42:11 -05:00
using EpicGames.Core ;
2019-01-08 11:38:48 -05:00
namespace Gauntlet
{
public class IOSBuild : IBuild
{
public UnrealTargetConfiguration Configuration { get ; protected set ; }
public string SourceIPAPath ;
public Dictionary < string , string > FilesToInstall ;
public string PackageName ;
public BuildFlags Flags { get ; protected set ; }
public UnrealTargetPlatform Platform { get { return UnrealTargetPlatform . IOS ; } }
public IOSBuild ( UnrealTargetConfiguration InConfig , string InPackageName , string InIPAPath , Dictionary < string , string > InFilesToInstall , BuildFlags InFlags )
{
Configuration = InConfig ;
PackageName = InPackageName ;
SourceIPAPath = InIPAPath ;
FilesToInstall = InFilesToInstall ;
Flags = InFlags ;
}
public bool CanSupportRole ( UnrealTargetRole RoleType )
{
if ( RoleType . IsClient ( ) )
{
return true ;
}
return false ;
}
2019-09-18 21:49:50 -04:00
internal static IProcessResult ExecuteCommand ( String Command , String Arguments )
2019-01-08 11:38:48 -05:00
{
CommandUtils . ERunOptions RunOptions = CommandUtils . ERunOptions . AppMustExist ;
if ( Log . IsVeryVerbose )
{
RunOptions | = CommandUtils . ERunOptions . AllowSpew ;
}
else
{
RunOptions | = CommandUtils . ERunOptions . NoLoggingOfRunCommand ;
}
Log . Verbose ( "Executing '{0} {1}'" , Command , Arguments ) ;
IProcessResult Result = CommandUtils . Run ( Command , Arguments , Options : RunOptions ) ;
return Result ;
}
2019-09-18 21:49:50 -04:00
// There are issues with IPA Zip64 files being created with Ionic.Zip possibly limited to when running on mono (see IOSPlatform.PackageIPA)
2020-06-23 18:40:00 -04:00
// This manifests as header overflow errors, etc in 7zip, Ionic.Zip, System.IO.Compression, and OSX system unzip
2019-09-18 21:49:50 -04:00
internal static bool ExecuteIPAZipCommand ( String Arguments , out String Output , String ShouldExist = "" )
2019-01-08 11:38:48 -05:00
{
2020-01-30 10:41:00 -05:00
using ( new ScopedSuspendECErrorParsing ( ) )
2019-09-18 21:49:50 -04:00
{
2020-01-30 10:41:00 -05:00
IProcessResult Result = ExecuteCommand ( "unzip" , Arguments ) ;
Output = Result . Output ;
2019-09-18 21:49:50 -04:00
2020-01-30 10:41:00 -05:00
if ( Result . ExitCode ! = 0 )
{
if ( ! String . IsNullOrEmpty ( ShouldExist ) )
{
if ( ! File . Exists ( ShouldExist ) & & ! Directory . Exists ( ShouldExist ) )
{
2022-11-12 18:42:11 -05:00
Log . Error ( KnownLogEvents . Gauntlet_BuildDropEvent , "unzip encountered an error or warning procesing IPA, possibly due to Zip64 issue, {File} missing" , ShouldExist ) ;
2020-01-30 10:41:00 -05:00
return false ;
}
}
Log . Info ( String . Format ( "unzip encountered an issue procesing IPA, possibly due to Zip64. Future steps may fail." ) ) ;
}
2019-09-18 21:49:50 -04:00
}
return true ;
}
2020-06-23 18:40:00 -04:00
// IPA handling using ditto command, which is capable of handling IPA's > 4GB/Zip64
internal static bool ExecuteIPADittoCommand ( String Arguments , out String Output , String ShouldExist = "" )
{
using ( new ScopedSuspendECErrorParsing ( ) )
{
IProcessResult Result = ExecuteCommand ( "ditto" , Arguments ) ;
Output = Result . Output ;
if ( Result . ExitCode ! = 0 )
{
if ( ! String . IsNullOrEmpty ( ShouldExist ) )
{
if ( ! File . Exists ( ShouldExist ) & & ! Directory . Exists ( ShouldExist ) )
{
Log . Error ( String . Format ( "ditto encountered an error or warning procesing IPA, {0} missing" , ShouldExist ) ) ;
return false ;
}
}
Log . Error ( String . Format ( "ditto encountered an issue procesing IPA" ) ) ;
return false ;
}
}
return true ;
}
2019-09-18 21:49:50 -04:00
private static string GetBundleIdentifier ( string SourceIPA )
{
string Output ;
// Get a list of files in the IPA
if ( ! ExecuteIPAZipCommand ( String . Format ( "-Z1 {0}" , SourceIPA ) , out Output ) )
2019-01-08 11:38:48 -05:00
{
Log . Warning ( String . Format ( "Unable to list files for IPA {0}" , SourceIPA ) ) ;
return null ;
}
2019-09-24 13:10:15 -04:00
string [ ] Filenames = Regex . Split ( Output , "\r\n|\r|\n" ) ;
string PList = Filenames . Where ( F = > Regex . IsMatch ( F . ToLower ( ) . Trim ( ) , @"(payload\/)([^\/]+)(\/info\.plist)" ) ) . FirstOrDefault ( ) ;
2019-01-08 11:38:48 -05:00
if ( String . IsNullOrEmpty ( PList ) )
{
Log . Warning ( String . Format ( "Unable to find plist for IPA {0}" , SourceIPA ) ) ;
return null ;
}
// Get the plist info
2019-09-18 21:49:50 -04:00
if ( ! ExecuteIPAZipCommand ( String . Format ( "-p '{0}' '{1}'" , SourceIPA , PList ) , out Output ) )
2019-01-08 11:38:48 -05:00
{
Log . Warning ( String . Format ( "Unable to extract plist data for IPA {0}" , SourceIPA ) ) ;
return null ;
}
2019-09-18 21:49:50 -04:00
string PlistInfo = Output ;
2019-01-08 11:38:48 -05:00
// todo: plist parsing, could be better
string PackageName = null ;
string KeyString = "<key>CFBundleIdentifier</key>" ;
int KeyIndex = PlistInfo . IndexOf ( KeyString ) ;
if ( KeyIndex > 0 )
{
int StartIdx = PlistInfo . IndexOf ( "<string>" , KeyIndex + KeyString . Length ) + "<string>" . Length ;
int EndIdx = PlistInfo . IndexOf ( "</string>" , StartIdx ) ;
if ( StartIdx > 0 & & EndIdx > StartIdx )
{
PackageName = PlistInfo . Substring ( StartIdx , EndIdx - StartIdx ) ;
}
}
if ( String . IsNullOrEmpty ( PackageName ) )
{
Log . Warning ( String . Format ( "Unable to find CFBundleIdentifier in plist info for IPA {0}" , SourceIPA ) ) ;
return null ;
}
Log . Verbose ( "Found bundle id: {0}" , PackageName ) ;
return PackageName ;
}
public static IEnumerable < IOSBuild > CreateFromPath ( string InProjectName , string InPath )
{
string BuildPath = InPath ;
List < IOSBuild > DiscoveredBuilds = new List < IOSBuild > ( ) ;
DirectoryInfo Di = new DirectoryInfo ( BuildPath ) ;
// find all install batchfiles
FileInfo [ ] InstallFiles = Di . GetFiles ( "*.ipa" ) ;
foreach ( FileInfo Fi in InstallFiles )
{
var UnrealConfig = UnrealHelpers . GetConfigurationFromExecutableName ( InProjectName , Fi . Name ) ;
Log . Verbose ( "Pulling package data from {0}" , Fi . FullName ) ;
string AbsPath = Fi . Directory . FullName ;
2019-01-11 12:30:03 -05:00
// IOS builds are always packaged, and can always replace the command line and executable as we cache the unzip'd IPA
BuildFlags Flags = BuildFlags . Packaged | BuildFlags . CanReplaceCommandLine | BuildFlags . CanReplaceExecutable ;
2019-01-08 11:38:48 -05:00
if ( AbsPath . Contains ( "Bulk" ) )
{
Flags | = BuildFlags . Bulk ;
}
2019-07-29 17:39:09 -04:00
else
{
Flags | = BuildFlags . NotBulk ;
}
2019-01-08 11:38:48 -05:00
string SourceIPAPath = Fi . FullName ;
string PackageName = GetBundleIdentifier ( SourceIPAPath ) ;
if ( String . IsNullOrEmpty ( PackageName ) )
{
continue ;
}
Dictionary < string , string > FilesToInstall = new Dictionary < string , string > ( ) ;
IOSBuild NewBuild = new IOSBuild ( UnrealConfig , PackageName , SourceIPAPath , FilesToInstall , Flags ) ;
DiscoveredBuilds . Add ( NewBuild ) ;
Log . Verbose ( "Found {0} {1} build at {2}" , UnrealConfig , ( ( Flags & BuildFlags . Bulk ) = = BuildFlags . Bulk ) ? "(bulk)" : "(not bulk)" , AbsPath ) ;
}
return DiscoveredBuilds ;
}
}
public class IOSBuildSource : IFolderBuildSource
{
public string BuildName { get { return "IOSBuildSource" ; } }
public bool CanSupportPlatform ( UnrealTargetPlatform InPlatform )
{
return InPlatform = = UnrealTargetPlatform . IOS ;
}
public string ProjectName { get ; protected set ; }
public IOSBuildSource ( )
{
}
public List < IBuild > GetBuildsAtPath ( string InProjectName , string InPath , int MaxRecursion = 3 )
{
// We only want iOS builds on Mac host
if ( BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Mac )
{
return new List < IBuild > ( ) ;
}
List < DirectoryInfo > AllDirs = new List < DirectoryInfo > ( ) ;
2019-11-29 21:00:10 -05:00
List < IBuild > Builds = new List < IBuild > ( ) ;
2019-01-08 11:38:48 -05:00
// c:\path\to\build
DirectoryInfo PathDI = new DirectoryInfo ( InPath ) ;
2019-11-29 21:00:10 -05:00
if ( PathDI . Exists )
2019-01-08 11:38:48 -05:00
{
2019-11-29 21:00:10 -05:00
if ( PathDI . Name . IndexOf ( "IOS" , StringComparison . OrdinalIgnoreCase ) > = 0 )
2019-01-08 11:38:48 -05:00
{
2019-11-29 21:00:10 -05:00
AllDirs . Add ( PathDI ) ;
}
2019-01-08 11:38:48 -05:00
2019-11-29 21:00:10 -05:00
// find all directories that begin with IOS
DirectoryInfo [ ] IOSDirs = PathDI . GetDirectories ( "IOS*" , SearchOption . TopDirectoryOnly ) ;
2019-01-08 11:38:48 -05:00
2019-11-29 21:00:10 -05:00
AllDirs . AddRange ( IOSDirs ) ;
2019-01-08 11:38:48 -05:00
2019-11-29 21:00:10 -05:00
List < DirectoryInfo > DirsToRecurse = AllDirs ;
2019-01-08 11:38:48 -05:00
2019-11-29 21:00:10 -05:00
// now get subdirs
while ( MaxRecursion - - > 0 )
2019-01-08 11:38:48 -05:00
{
2019-11-29 21:00:10 -05:00
List < DirectoryInfo > DiscoveredDirs = new List < DirectoryInfo > ( ) ;
DirsToRecurse . ToList ( ) . ForEach ( ( D ) = >
2019-01-08 11:38:48 -05:00
{
2019-11-29 21:00:10 -05:00
DiscoveredDirs . AddRange ( D . GetDirectories ( "*" , SearchOption . TopDirectoryOnly ) ) ;
} ) ;
AllDirs . AddRange ( DiscoveredDirs ) ;
DirsToRecurse = DiscoveredDirs ;
}
//IOSBuildSource BuildSource = null;
string IOSBuildFilter = Globals . Params . ParseValue ( "IOSBuildFilter" , "" ) ;
foreach ( DirectoryInfo Di in AllDirs )
{
IEnumerable < IOSBuild > FoundBuilds = IOSBuild . CreateFromPath ( InProjectName , Di . FullName ) ;
if ( FoundBuilds ! = null )
{
if ( ! string . IsNullOrEmpty ( IOSBuildFilter ) )
{
//IndexOf used because Contains must be case-sensitive
FoundBuilds = FoundBuilds . Where ( B = > B . SourceIPAPath . IndexOf ( IOSBuildFilter , StringComparison . OrdinalIgnoreCase ) > = 0 ) ;
}
Builds . AddRange ( FoundBuilds ) ;
2019-01-08 11:38:48 -05:00
}
}
}
return Builds ;
}
}
}