2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
using AutomationTool ;
using UnrealBuildTool ;
2014-07-16 04:09:43 -04:00
using System.Text.RegularExpressions ;
2014-03-14 14:13:41 -04:00
using Ionic.Zip ;
using Ionic.Zlib ;
static class IOSEnvVarNames
{
// Should we code sign when staging? (defaults to 1 if not present)
static public readonly string CodeSignWhenStaging = "uebp_CodeSignWhenStaging" ;
}
public class IOSPlatform : Platform
{
2014-12-11 16:20:07 -05:00
bool bCreatedIPA = false ;
2014-03-14 14:13:41 -04:00
public IOSPlatform ( )
: base ( UnrealTargetPlatform . IOS )
{
}
2014-07-16 04:09:43 -04:00
// Run the integrated IPP code
// @todo ipp: How to log the output in a nice UAT way?
protected static int RunIPP ( string IPPArguments )
{
List < string > Args = new List < string > ( ) ;
StringBuilder Token = null ;
int Index = 0 ;
bool bInQuote = false ;
while ( Index < IPPArguments . Length )
{
if ( IPPArguments [ Index ] = = '\"' )
{
bInQuote = ! bInQuote ;
}
else if ( IPPArguments [ Index ] = = ' ' & & ! bInQuote )
{
if ( Token ! = null )
{
Args . Add ( Token . ToString ( ) ) ;
Token = null ;
}
}
else
{
if ( Token = = null )
{
Token = new StringBuilder ( ) ;
}
Token . Append ( IPPArguments [ Index ] ) ;
}
Index + + ;
}
if ( Token ! = null )
{
Args . Add ( Token . ToString ( ) ) ;
}
return RunIPP ( Args . ToArray ( ) ) ;
}
protected static int RunIPP ( string [ ] Args )
{
2014-08-22 18:06:00 -04:00
return 5 ; //@TODO: Reintegrate IPP to IOS.Automation iPhonePackager.Program.RealMain(Args);
2014-07-16 04:09:43 -04:00
}
public override int RunCommand ( string Command )
{
// run generic IPP commands through the interface
if ( Command . StartsWith ( "IPP:" ) )
{
return RunIPP ( Command . Substring ( 4 ) ) ;
}
// non-zero error code
return 4 ;
}
2014-10-02 17:14:09 -04:00
protected string MakeIPAFileName ( UnrealTargetConfiguration TargetConfiguration , ProjectParams Params )
2014-03-14 14:13:41 -04:00
{
2014-10-02 17:14:09 -04:00
string ProjectIPA = Path . Combine ( Path . GetDirectoryName ( Params . RawProjectPath ) , "Binaries" , "IOS" , ( Params . Distribution ? "Distro_" : "" ) + Params . ShortProjectName ) ;
2014-03-14 14:13:41 -04:00
if ( TargetConfiguration ! = UnrealTargetConfiguration . Development )
{
ProjectIPA + = "-" + PlatformType . ToString ( ) + "-" + TargetConfiguration . ToString ( ) ;
}
ProjectIPA + = ".ipa" ;
return ProjectIPA ;
}
// Determine if we should code sign
protected bool GetCodeSignDesirability ( ProjectParams Params )
{
//@TODO: Would like to make this true, as it's the common case for everyone else
2014-04-28 11:19:36 -04:00
bool bDefaultNeedsSign = true ;
2014-03-14 14:13:41 -04:00
bool bNeedsSign = false ;
string EnvVar = InternalUtils . GetEnvironmentVariable ( IOSEnvVarNames . CodeSignWhenStaging , bDefaultNeedsSign ? "1" : "0" , /*bQuiet=*/ false ) ;
if ( ! bool . TryParse ( EnvVar , out bNeedsSign ) )
{
int BoolAsInt ;
if ( int . TryParse ( EnvVar , out BoolAsInt ) )
{
bNeedsSign = BoolAsInt ! = 0 ;
}
else
{
bNeedsSign = bDefaultNeedsSign ;
}
}
if ( ! String . IsNullOrEmpty ( Params . BundleName ) )
{
// Have to sign when a bundle name is specified
bNeedsSign = true ;
}
return bNeedsSign ;
}
public override void Package ( ProjectParams Params , DeploymentContext SC , int WorkingCL )
{
Log ( "Package {0}" , Params . RawProjectPath ) ;
2015-02-05 12:47:11 -05:00
// ensure the ue4game binary exists, if applicable
2015-02-09 17:55:04 -05:00
string FullExePath = CombinePaths ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , SC . StageExecutables [ 0 ] + ( UnrealBuildTool . BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Mac ? ".stub" : "" ) ) ;
2015-02-05 12:47:11 -05:00
if ( ! SC . IsCodeBasedProject & & ! FileExists_NoExceptions ( FullExePath ) )
{
Log ( "Failed to find game binary " + FullExePath ) ;
2015-06-30 11:40:05 -04:00
throw new AutomationException ( ErrorCodes . Error_MissingExecutable , "Stage Failed. Could not find binary {0}. You may need to build the UE4 project with your target configuration and platform." , FullExePath ) ;
2015-02-05 12:47:11 -05:00
}
2014-03-14 14:13:41 -04:00
//@TODO: We should be able to use this code on both platforms, when the following issues are sorted:
// - Raw executable is unsigned & unstripped (need to investigate adding stripping to IPP)
// - IPP needs to be able to codesign a raw directory
// - IPP needs to be able to take a .app directory instead of a Payload directory when doing RepackageFromStage (which would probably be renamed)
// - Some discrepancy in the loading screen pngs that are getting packaged, which needs to be investigated
// - Code here probably needs to be updated to write 0 byte files as 1 byte (difference with IPP, was required at one point when using Ionic.Zip to prevent issues on device, maybe not needed anymore?)
2014-08-06 07:05:15 -04:00
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
2014-03-14 14:13:41 -04:00
{
// copy in all of the artwork and plist
2014-05-21 07:25:13 -04:00
var DeployHandler = UEBuildDeploy . GetBuildDeploy ( UnrealTargetPlatform . IOS ) ;
2015-02-01 12:33:30 -05:00
2014-03-14 14:13:41 -04:00
DeployHandler . PrepForUATPackageOrDeploy ( Params . ShortProjectName ,
Path . GetDirectoryName ( Params . RawProjectPath ) ,
CombinePaths ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , SC . StageExecutables [ 0 ] ) ,
CombinePaths ( SC . LocalRoot , "Engine" ) ,
2015-02-04 20:05:59 -05:00
Params . Distribution ,
"" ,
false ) ;
2014-03-14 14:13:41 -04:00
// figure out where to pop in the staged files
string AppDirectory = string . Format ( "{0}/Payload/{1}.app" ,
Path . GetDirectoryName ( Params . ProjectGameExeFilename ) ,
Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) ) ;
// delete the old cookeddata
InternalUtils . SafeDeleteDirectory ( AppDirectory + "/cookeddata" , true ) ;
InternalUtils . SafeDeleteFile ( AppDirectory + "/ue4commandline.txt" , true ) ;
2014-12-11 16:20:07 -05:00
if ( ! Params . IterativeDeploy )
2014-03-14 14:13:41 -04:00
{
2014-12-11 16:20:07 -05:00
// copy the Staged files to the AppDirectory
string [ ] StagedFiles = Directory . GetFiles ( SC . StageDirectory , "*" , SearchOption . AllDirectories ) ;
foreach ( string Filename in StagedFiles )
{
string DestFilename = Filename . Replace ( SC . StageDirectory , AppDirectory ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( DestFilename ) ) ;
InternalUtils . SafeCopyFile ( Filename , DestFilename , true ) ;
}
}
else
{
// copy just the root stage directory files
string [ ] StagedFiles = Directory . GetFiles ( SC . StageDirectory , "*" , SearchOption . TopDirectoryOnly ) ;
foreach ( string Filename in StagedFiles )
{
string DestFilename = Filename . Replace ( SC . StageDirectory , AppDirectory ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( DestFilename ) ) ;
InternalUtils . SafeCopyFile ( Filename , DestFilename , true ) ;
}
2014-03-14 14:13:41 -04:00
}
}
2014-12-11 16:20:07 -05:00
if ( SC . StageTargetConfigurations . Count ! = 1 )
{
throw new AutomationException ( "iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations" , SC . StageTargetConfigurations . Count ) ;
}
bCreatedIPA = false ;
bool bNeedsIPA = false ;
if ( Params . IterativeDeploy )
{
2014-12-18 17:37:28 -05:00
String NonUFSManifestPath = SC . GetNonUFSDeploymentDeltaPath ( ) ;
2014-12-11 16:20:07 -05:00
// check to determine if we need to update the IPA
2014-12-18 17:37:28 -05:00
if ( File . Exists ( NonUFSManifestPath ) )
2014-12-11 16:20:07 -05:00
{
2014-12-18 17:37:28 -05:00
string NonUFSFiles = File . ReadAllText ( NonUFSManifestPath ) ;
2014-12-11 16:20:07 -05:00
string [ ] Lines = NonUFSFiles . Split ( '\n' ) ;
bNeedsIPA = Lines . Length > 0 & & ! string . IsNullOrWhiteSpace ( Lines [ 0 ] ) ;
}
}
2014-08-06 07:05:15 -04:00
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Mac )
2014-03-14 14:13:41 -04:00
{
var TargetConfiguration = SC . StageTargetConfigurations [ 0 ] ;
2014-10-02 17:14:09 -04:00
var ProjectIPA = MakeIPAFileName ( TargetConfiguration , Params ) ;
2015-02-01 12:33:30 -05:00
var ProjectStub = Path . GetFullPath ( Params . ProjectGameExeFilename ) ;
2014-03-14 14:13:41 -04:00
// package a .ipa from the now staged directory
var IPPExe = CombinePaths ( CmdEnv . LocalRoot , "Engine/Binaries/DotNET/IOS/IPhonePackager.exe" ) ;
Log ( "ProjectName={0}" , Params . ShortProjectName ) ;
Log ( "ProjectStub={0}" , ProjectStub ) ;
Log ( "ProjectIPA={0}" , ProjectIPA ) ;
Log ( "IPPExe={0}" , IPPExe ) ;
bool cookonthefly = Params . CookOnTheFly | | Params . SkipCookOnTheFly ;
2014-12-11 16:20:07 -05:00
// if we are incremental check to see if we need to even update the IPA
if ( ! Params . IterativeDeploy | | ! File . Exists ( ProjectIPA ) | | bNeedsIPA )
2014-07-16 04:09:43 -04:00
{
2014-12-11 16:20:07 -05:00
// delete the .ipa to make sure it was made
DeleteFile ( ProjectIPA ) ;
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
bCreatedIPA = true ;
if ( RemoteToolChain . bUseRPCUtil )
2014-07-16 04:09:43 -04:00
{
2014-12-11 16:20:07 -05:00
string IPPArguments = "RepackageFromStage \"" + ( Params . IsCodeBasedProject ? Params . RawProjectPath : "Engine" ) + "\"" ;
IPPArguments + = " -config " + TargetConfiguration . ToString ( ) ;
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
if ( TargetConfiguration = = UnrealTargetConfiguration . Shipping )
2014-08-12 11:13:15 -04:00
{
2014-12-11 16:20:07 -05:00
IPPArguments + = " -compress=best" ;
2014-08-12 11:13:15 -04:00
}
2014-12-11 16:20:07 -05:00
// Determine if we should sign
bool bNeedToSign = GetCodeSignDesirability ( Params ) ;
if ( ! String . IsNullOrEmpty ( Params . BundleName ) )
{
// Have to sign when a bundle name is specified
bNeedToSign = true ;
IPPArguments + = " -bundlename " + Params . BundleName ;
}
if ( bNeedToSign )
{
IPPArguments + = " -sign" ;
if ( Params . Distribution )
{
IPPArguments + = " -distribution" ;
}
}
IPPArguments + = ( cookonthefly ? " -cookonthefly" : "" ) ;
IPPArguments + = " -stagedir \"" + CombinePaths ( Params . BaseStageDirectory , "IOS" ) + "\"" ;
IPPArguments + = " -project \"" + Params . RawProjectPath + "\"" ;
if ( Params . IterativeDeploy )
{
IPPArguments + = " -iterate" ;
}
RunAndLog ( CmdEnv , IPPExe , IPPArguments ) ;
2014-07-16 04:09:43 -04:00
}
2014-12-11 16:20:07 -05:00
else
2014-07-16 04:09:43 -04:00
{
2014-12-11 16:20:07 -05:00
List < string > IPPArguments = new List < string > ( ) ;
IPPArguments . Add ( "RepackageFromStage" ) ;
IPPArguments . Add ( Params . IsCodeBasedProject ? Params . RawProjectPath : "Engine" ) ;
IPPArguments . Add ( "-config" ) ;
IPPArguments . Add ( TargetConfiguration . ToString ( ) ) ;
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
if ( TargetConfiguration = = UnrealTargetConfiguration . Shipping )
{
IPPArguments . Add ( "-compress=best" ) ;
}
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
// Determine if we should sign
bool bNeedToSign = GetCodeSignDesirability ( Params ) ;
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
if ( ! String . IsNullOrEmpty ( Params . BundleName ) )
{
// Have to sign when a bundle name is specified
bNeedToSign = true ;
IPPArguments . Add ( "-bundlename" ) ;
IPPArguments . Add ( Params . BundleName ) ;
}
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
if ( bNeedToSign )
{
IPPArguments . Add ( "-sign" ) ;
}
2014-07-16 04:09:43 -04:00
2014-12-11 16:20:07 -05:00
if ( cookonthefly )
{
IPPArguments . Add ( " -cookonthefly" ) ;
}
IPPArguments . Add ( " -stagedir" ) ;
IPPArguments . Add ( CombinePaths ( Params . BaseStageDirectory , "IOS" ) ) ;
IPPArguments . Add ( " -project" ) ;
IPPArguments . Add ( Params . RawProjectPath ) ;
if ( Params . IterativeDeploy )
{
IPPArguments . Add ( " -iterate" ) ;
}
if ( RunIPP ( IPPArguments . ToArray ( ) ) ! = 0 )
{
throw new AutomationException ( "IPP Failed" ) ;
}
2014-07-16 04:09:43 -04:00
}
}
2014-03-14 14:13:41 -04:00
// verify the .ipa exists
if ( ! FileExists ( ProjectIPA ) )
{
2015-06-30 11:40:05 -04:00
throw new AutomationException ( ErrorCodes . Error_FailedToCreateIPA , "PACKAGE FAILED - {0} was not created" , ProjectIPA ) ;
2014-03-14 14:13:41 -04:00
}
if ( WorkingCL > 0 )
{
// Open files for add or edit
var ExtraFilesToCheckin = new List < string >
{
ProjectIPA
} ;
// check in the .ipa along with everything else
UE4Build . AddBuildProductsToChangelist ( WorkingCL , ExtraFilesToCheckin ) ;
}
//@TODO: This automatically deploys after packaging, useful for testing on PC when iterating on IPP
//Deploy(Params, SC);
}
else
{
2014-12-11 16:20:07 -05:00
// create the ipa
string IPAName = CombinePaths ( Path . GetDirectoryName ( Params . RawProjectPath ) , "Binaries" , "IOS" , ( Params . Distribution ? "Distro_" : "" ) + Params . ShortProjectName + ( SC . StageTargetConfigurations [ 0 ] ! = UnrealTargetConfiguration . Development ? ( "-IOS-" + SC . StageTargetConfigurations [ 0 ] . ToString ( ) ) : "" ) + ".ipa" ) ;
2014-03-14 14:13:41 -04:00
2014-12-11 16:20:07 -05:00
if ( ! Params . IterativeDeploy | | ! File . Exists ( IPAName ) | | bNeedsIPA )
{
bCreatedIPA = true ;
// code sign the app
CodeSign ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , Params . IsCodeBasedProject ? Params . ShortProjectName : Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) , Params . RawProjectPath , SC . StageTargetConfigurations [ 0 ] , SC . LocalRoot , Params . ShortProjectName , Path . GetDirectoryName ( Params . RawProjectPath ) , SC . IsCodeBasedProject , Params . Distribution ) ;
// now generate the ipa
PackageIPA ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , Params . IsCodeBasedProject ? Params . ShortProjectName : Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) , Params . ShortProjectName , Path . GetDirectoryName ( Params . RawProjectPath ) , SC . StageTargetConfigurations [ 0 ] , Params . Distribution ) ;
}
2014-03-14 14:13:41 -04:00
}
PrintRunTime ( ) ;
}
2014-04-25 17:06:07 -04:00
private string EnsureXcodeProjectExists ( string RawProjectPath , string LocalRoot , string ShortProjectName , string ProjectRoot , bool IsCodeBasedProject , out bool bWasGenerated )
2014-03-14 14:13:41 -04:00
{
// first check for ue4.xcodeproj
bWasGenerated = false ;
2014-04-25 13:10:14 -04:00
string XcodeProj = RawProjectPath . Replace ( ".uproject" , "_IOS.xcodeproj" ) ;
2014-03-14 14:13:41 -04:00
Console . WriteLine ( "Project: " + XcodeProj ) ;
2014-10-24 15:31:48 -04:00
// if (!Directory.Exists (XcodeProj))
2014-03-14 14:13:41 -04:00
{
2014-04-25 13:10:14 -04:00
// project.xcodeproj doesn't exist, so generate temp project
string Arguments = "-project=\"" + RawProjectPath + "\"" ;
Arguments + = " -platforms=IOS -game -nointellisense -iosdeployonly -ignorejunk" ;
string Script = CombinePaths ( CmdEnv . LocalRoot , "Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" ) ;
if ( GlobalCommandLine . Rocket )
{
Script = CombinePaths ( CmdEnv . LocalRoot , "Engine/Build/BatchFiles/Mac/RocketGenerateProjectFiles.sh" ) ;
}
string CWD = Directory . GetCurrentDirectory ( ) ;
Directory . SetCurrentDirectory ( Path . GetDirectoryName ( Script ) ) ;
Run ( Script , Arguments , null , ERunOptions . Default ) ;
bWasGenerated = true ;
Directory . SetCurrentDirectory ( CWD ) ;
2014-03-14 14:13:41 -04:00
if ( ! Directory . Exists ( XcodeProj ) )
{
2014-04-25 13:10:14 -04:00
// something very bad happened
throw new AutomationException ( "iOS couldn't find the appropriate Xcode Project" ) ;
2014-03-14 14:13:41 -04:00
}
}
return XcodeProj ;
}
2014-04-25 17:06:07 -04:00
private void CodeSign ( string BaseDirectory , string GameName , string RawProjectPath , UnrealTargetConfiguration TargetConfig , string LocalRoot , string ProjectName , string ProjectDirectory , bool IsCode , bool Distribution = false )
2014-03-14 14:13:41 -04:00
{
// check for the proper xcodeproject
bool bWasGenerated = false ;
2014-04-25 17:06:07 -04:00
string XcodeProj = EnsureXcodeProjectExists ( RawProjectPath , LocalRoot , ProjectName , ProjectDirectory , IsCode , out bWasGenerated ) ;
2015-01-22 00:20:01 -05:00
2014-03-14 14:13:41 -04:00
string Arguments = "UBT_NO_POST_DEPLOY=true" ;
Arguments + = " /usr/bin/xcrun xcodebuild build -project \"" + XcodeProj + "\"" ;
Arguments + = " -scheme '" ;
Arguments + = GameName ;
2014-04-23 17:32:01 -04:00
Arguments + = " - iOS'" ;
2014-03-14 14:13:41 -04:00
Arguments + = " -configuration " + TargetConfig . ToString ( ) ;
2014-11-05 13:44:01 -05:00
Arguments + = " -sdk iphoneos" ;
2014-03-14 14:13:41 -04:00
Arguments + = " CODE_SIGN_IDENTITY=" + ( Distribution ? "\"iPhone Distribution\"" : "\"iPhone Developer\"" ) ;
2014-05-13 14:01:21 -04:00
ProcessResult Result = Run ( "/usr/bin/env" , Arguments , null , ERunOptions . Default ) ;
2014-03-14 14:13:41 -04:00
if ( bWasGenerated )
{
InternalUtils . SafeDeleteDirectory ( XcodeProj , true ) ;
}
2014-05-13 14:01:21 -04:00
if ( Result . ExitCode ! = 0 )
{
2015-06-30 11:40:05 -04:00
throw new AutomationException ( ErrorCodes . Error_FailedToCodeSign , "CodeSign Failed" ) ;
2014-05-13 14:01:21 -04:00
}
2014-03-14 14:13:41 -04:00
}
private void PackageIPA ( string BaseDirectory , string GameName , string ProjectName , string ProjectDirectory , UnrealTargetConfiguration TargetConfig , bool Distribution = false )
{
// create the ipa
2014-08-13 09:17:54 -04:00
string IPAName = CombinePaths ( ProjectDirectory , "Binaries" , "IOS" , ( Distribution ? "Distro_" : "" ) + ProjectName + ( TargetConfig ! = UnrealTargetConfiguration . Development ? ( "-IOS-" + TargetConfig . ToString ( ) ) : "" ) + ".ipa" ) ;
2014-03-14 14:13:41 -04:00
// delete the old one
if ( File . Exists ( IPAName ) )
{
File . Delete ( IPAName ) ;
}
// make the subdirectory if needed
string DestSubdir = Path . GetDirectoryName ( IPAName ) ;
if ( ! Directory . Exists ( DestSubdir ) )
{
Directory . CreateDirectory ( DestSubdir ) ;
}
// set up the directories
string ZipWorkingDir = String . Format ( "Payload/{0}.app/" , GameName ) ;
string ZipSourceDir = string . Format ( "{0}/Payload/{1}.app" , BaseDirectory , GameName ) ;
// create the file
using ( ZipFile Zip = new ZipFile ( ) )
{
2014-04-23 20:10:59 -04:00
// Set encoding to support unicode filenames
Zip . AlternateEncodingUsage = ZipOption . Always ;
Zip . AlternateEncoding = Encoding . UTF8 ;
2014-03-14 14:13:41 -04:00
// set the compression level
if ( Distribution )
{
Zip . CompressionLevel = CompressionLevel . BestCompression ;
}
// add the entire directory
Zip . AddDirectory ( ZipSourceDir , ZipWorkingDir ) ;
// Update permissions to be UNIX-style
// Modify the file attributes of any added file to unix format
foreach ( ZipEntry E in Zip . Entries )
{
const byte FileAttributePlatform_NTFS = 0x0A ;
const byte FileAttributePlatform_UNIX = 0x03 ;
const byte FileAttributePlatform_FAT = 0x00 ;
const int UNIX_FILETYPE_NORMAL_FILE = 0x8000 ;
//const int UNIX_FILETYPE_SOCKET = 0xC000;
//const int UNIX_FILETYPE_SYMLINK = 0xA000;
//const int UNIX_FILETYPE_BLOCKSPECIAL = 0x6000;
const int UNIX_FILETYPE_DIRECTORY = 0x4000 ;
//const int UNIX_FILETYPE_CHARSPECIAL = 0x2000;
//const int UNIX_FILETYPE_FIFO = 0x1000;
const int UNIX_EXEC = 1 ;
const int UNIX_WRITE = 2 ;
const int UNIX_READ = 4 ;
int MyPermissions = UNIX_READ | UNIX_WRITE ;
int OtherPermissions = UNIX_READ ;
int PlatformEncodedBy = ( E . VersionMadeBy > > 8 ) & 0xFF ;
int LowerBits = 0 ;
// Try to preserve read-only if it was set
bool bIsDirectory = E . IsDirectory ;
// Check to see if this
bool bIsExecutable = false ;
if ( Path . GetFileNameWithoutExtension ( E . FileName ) . Equals ( GameName , StringComparison . InvariantCultureIgnoreCase ) )
{
bIsExecutable = true ;
}
if ( bIsExecutable )
{
// The executable will be encrypted in the final distribution IPA and will compress very poorly, so keeping it
// uncompressed gives a better indicator of IPA size for our distro builds
E . CompressionLevel = CompressionLevel . None ;
}
if ( ( PlatformEncodedBy = = FileAttributePlatform_NTFS ) | | ( PlatformEncodedBy = = FileAttributePlatform_FAT ) )
{
FileAttributes OldAttributes = E . Attributes ;
//LowerBits = ((int)E.Attributes) & 0xFFFF;
if ( ( OldAttributes & FileAttributes . Directory ) ! = 0 )
{
bIsDirectory = true ;
}
// Permissions
if ( ( OldAttributes & FileAttributes . ReadOnly ) ! = 0 )
{
MyPermissions & = ~ UNIX_WRITE ;
OtherPermissions & = ~ UNIX_WRITE ;
}
}
if ( bIsDirectory | | bIsExecutable )
{
MyPermissions | = UNIX_EXEC ;
OtherPermissions | = UNIX_EXEC ;
}
// Re-jigger the external file attributes to UNIX style if they're not already that way
if ( PlatformEncodedBy ! = FileAttributePlatform_UNIX )
{
int NewAttributes = bIsDirectory ? UNIX_FILETYPE_DIRECTORY : UNIX_FILETYPE_NORMAL_FILE ;
NewAttributes | = ( MyPermissions < < 6 ) ;
NewAttributes | = ( OtherPermissions < < 3 ) ;
NewAttributes | = ( OtherPermissions < < 0 ) ;
// Now modify the properties
E . AdjustExternalFileAttributes ( FileAttributePlatform_UNIX , ( NewAttributes < < 16 ) | LowerBits ) ;
}
}
// Save it out
Zip . Save ( IPAName ) ;
}
}
public override void GetFilesToDeployOrStage ( ProjectParams Params , DeploymentContext SC )
{
2014-12-11 16:20:07 -05:00
// if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
2014-03-14 14:13:41 -04:00
{
// copy the icons/launch screens from the engine
{
string SourcePath = CombinePaths ( SC . LocalRoot , "Engine" , "Build" , "IOS" , "Resources" , "Graphics" ) ;
SC . StageFiles ( StagedFileType . NonUFS , SourcePath , "*.png" , false , null , "" , true , false ) ;
}
2014-04-30 14:09:06 -04:00
// copy any additional framework assets that will be needed at runtime
{
Support for third party iOS framework bundled assets
* Work in progress, works with RPC utility, local mac support incoming
* Changed AdditionalPublicFrameworks from storing just string, to storing the frame work name, zip name, and bundled asset name that needs to be copied
* We also now store the module that added this framework, so we can derive the module project path, etc when we need it for when we create intermediate directories
#Codereview Josh.Adams, Peter.Sauerbrei, Michael.Noland, Gil.Gribb, Robert.Manuszewski
[CL 2068603 by John Pollard in Main branch]
2014-05-09 16:38:26 -04:00
string SourcePath = CombinePaths ( ( SC . IsCodeBasedProject ? SC . ProjectRoot : SC . LocalRoot + "\\Engine" ) , "Intermediate" , "IOS" , "FrameworkAssets" ) ;
2014-04-30 21:10:24 -04:00
if ( Directory . Exists ( SourcePath ) )
{
SC . StageFiles ( StagedFileType . NonUFS , SourcePath , "*.*" , true , null , "" , true , false ) ;
}
2014-04-30 14:09:06 -04:00
}
2014-03-14 14:13:41 -04:00
// copy the icons/launch screens from the game (may stomp the engine copies)
{
string SourcePath = CombinePaths ( SC . ProjectRoot , "Build" , "IOS" , "Resources" , "Graphics" ) ;
SC . StageFiles ( StagedFileType . NonUFS , SourcePath , "*.png" , false , null , "" , true , false ) ;
}
// copy the plist (only if code signing, as it's protected by the code sign blob in the executable and can't be modified independently)
if ( GetCodeSignDesirability ( Params ) )
{
2015-01-29 17:04:04 -05:00
string SourcePath = CombinePaths ( ( SC . IsCodeBasedProject ? SC . ProjectRoot : SC . LocalRoot + "/Engine" ) , "Intermediate" , "IOS" ) ;
2014-03-14 14:13:41 -04:00
string TargetPListFile = Path . Combine ( SourcePath , ( SC . IsCodeBasedProject ? SC . ShortProjectName : "UE4Game" ) + "-Info.plist" ) ;
2015-01-26 15:42:02 -05:00
// if (!File.Exists(TargetPListFile))
2015-01-22 00:20:01 -05:00
{
// ensure the plist, entitlements, and provision files are properly copied
2015-01-29 17:04:04 -05:00
UnrealBuildTool . IOS . UEDeployIOS . GeneratePList ( ( SC . IsCodeBasedProject ? SC . ProjectRoot : SC . LocalRoot + "/Engine" ) , ! SC . IsCodeBasedProject , ( SC . IsCodeBasedProject ? SC . ShortProjectName : "UE4Game" ) , SC . ShortProjectName , SC . LocalRoot + "/Engine" , ( SC . IsCodeBasedProject ? SC . ProjectRoot : SC . LocalRoot + "/Engine" ) + "/Binaries/IOS/Payload/" + ( SC . IsCodeBasedProject ? SC . ShortProjectName : "UE4Game" ) + ".app" ) ;
2015-01-22 00:20:01 -05:00
}
2014-03-14 14:13:41 -04:00
SC . StageFiles ( StagedFileType . NonUFS , SourcePath , Path . GetFileName ( TargetPListFile ) , false , null , "" , false , false , "Info.plist" ) ;
}
}
2014-06-18 16:38:05 -04:00
// copy the movies from the project
{
SC . StageFiles ( StagedFileType . NonUFS , CombinePaths ( SC . ProjectRoot , "Build/IOS/Resources/Movies" ) , "*" , false , null , "" , true , false ) ;
2014-06-26 17:46:56 -04:00
SC . StageFiles ( StagedFileType . NonUFS , CombinePaths ( SC . ProjectRoot , "Content/Movies" ) , "*" , true , null , "" , true , false ) ;
2014-06-18 16:38:05 -04:00
}
2014-03-14 14:13:41 -04:00
}
public override void GetFilesToArchive ( ProjectParams Params , DeploymentContext SC )
{
if ( SC . StageTargetConfigurations . Count ! = 1 )
{
throw new AutomationException ( "iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations" , SC . StageTargetConfigurations . Count ) ;
}
var TargetConfiguration = SC . StageTargetConfigurations [ 0 ] ;
2014-10-02 17:14:09 -04:00
var ProjectIPA = MakeIPAFileName ( TargetConfiguration , Params ) ;
2014-03-14 14:13:41 -04:00
// verify the .ipa exists
if ( ! FileExists ( ProjectIPA ) )
{
throw new AutomationException ( "ARCHIVE FAILED - {0} was not found" , ProjectIPA ) ;
}
SC . ArchiveFiles ( Path . GetDirectoryName ( ProjectIPA ) , Path . GetFileName ( ProjectIPA ) ) ;
}
2014-12-11 16:20:07 -05:00
public override bool RetrieveDeployedManifests ( ProjectParams Params , DeploymentContext SC , out List < string > UFSManifests , out List < string > NonUFSManifests )
{
bool Result = true ;
UFSManifests = new List < string > ( ) ;
NonUFSManifests = new List < string > ( ) ;
var DeployServer = CombinePaths ( CmdEnv . LocalRoot , "Engine/Binaries/DotNET/IOS/DeploymentServer.exe" ) ;
try
{
var TargetConfiguration = SC . StageTargetConfigurations [ 0 ] ;
string BundleIdentifier = "" ;
if ( File . Exists ( Params . BaseStageDirectory + "/IOS/Info.plist" ) )
{
string Contents = File . ReadAllText ( SC . StageDirectory + "/Info.plist" ) ;
int Pos = Contents . IndexOf ( "CFBundleIdentifier" ) ;
Pos = Contents . IndexOf ( "<string>" , Pos ) + 8 ;
int EndPos = Contents . IndexOf ( "</string>" , Pos ) ;
BundleIdentifier = Contents . Substring ( Pos , EndPos - Pos ) ;
}
2014-12-18 17:37:28 -05:00
RunAndLog ( CmdEnv , DeployServer , "Backup -file \"" + CombinePaths ( Params . BaseStageDirectory , "IOS" , DeploymentContext . UFSDeployedManifestFileName ) + "\" -file \"" + CombinePaths ( Params . BaseStageDirectory , "IOS" , DeploymentContext . NonUFSDeployedManifestFileName ) + "\"" + ( String . IsNullOrEmpty ( Params . Device ) ? "" : " -device " + Params . Device . Substring ( 4 ) ) + " -bundle " + BundleIdentifier ) ;
2014-12-11 16:20:07 -05:00
string [ ] ManifestFiles = Directory . GetFiles ( CombinePaths ( Params . BaseStageDirectory , "IOS" ) , "*_Manifest_UFS*.txt" ) ;
UFSManifests . AddRange ( ManifestFiles ) ;
ManifestFiles = Directory . GetFiles ( CombinePaths ( Params . BaseStageDirectory , "IOS" ) , "*_Manifest_NonUFS*.txt" ) ;
NonUFSManifests . AddRange ( ManifestFiles ) ;
}
catch ( System . Exception )
{
// delete any files that did get copied
string [ ] Manifests = Directory . GetFiles ( CombinePaths ( Params . BaseStageDirectory , "IOS" ) , "*_Manifest_*.txt" ) ;
foreach ( string Manifest in Manifests )
{
File . Delete ( Manifest ) ;
}
Result = false ;
}
return Result ;
}
2014-03-14 14:13:41 -04:00
public override void Deploy ( ProjectParams Params , DeploymentContext SC )
{
2014-12-11 16:20:07 -05:00
if ( SC . StageTargetConfigurations . Count ! = 1 )
2014-03-14 14:13:41 -04:00
{
2014-12-11 16:20:07 -05:00
throw new AutomationException ( "iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations" , SC . StageTargetConfigurations . Count ) ;
2014-03-14 14:13:41 -04:00
}
2014-12-11 16:20:07 -05:00
if ( Params . Distribution )
{
throw new AutomationException ( "iOS cannot deploy a package made for distribution." ) ;
}
var TargetConfiguration = SC . StageTargetConfigurations [ 0 ] ;
var ProjectIPA = MakeIPAFileName ( TargetConfiguration , Params ) ;
var StagedIPA = SC . StageDirectory + "\\" + Path . GetFileName ( ProjectIPA ) ;
// verify the .ipa exists
if ( ! FileExists ( StagedIPA ) )
{
StagedIPA = ProjectIPA ;
if ( ! FileExists ( StagedIPA ) )
{
throw new AutomationException ( "DEPLOY FAILED - {0} was not found" , ProjectIPA ) ;
}
}
// if iterative deploy, determine the file delta
string BundleIdentifier = "" ;
bool bNeedsIPA = true ;
if ( Params . IterativeDeploy )
{
if ( File . Exists ( Params . BaseStageDirectory + "/IOS/Info.plist" ) )
{
string Contents = File . ReadAllText ( SC . StageDirectory + "/Info.plist" ) ;
int Pos = Contents . IndexOf ( "CFBundleIdentifier" ) ;
Pos = Contents . IndexOf ( "<string>" , Pos ) + 8 ;
int EndPos = Contents . IndexOf ( "</string>" , Pos ) ;
BundleIdentifier = Contents . Substring ( Pos , EndPos - Pos ) ;
}
// check to determine if we need to update the IPA
2014-12-18 17:37:28 -05:00
String NonUFSManifestPath = SC . GetNonUFSDeploymentDeltaPath ( ) ;
if ( File . Exists ( NonUFSManifestPath ) )
2014-12-11 16:20:07 -05:00
{
2014-12-18 17:37:28 -05:00
string NonUFSFiles = File . ReadAllText ( NonUFSManifestPath ) ;
2014-12-11 16:20:07 -05:00
string [ ] Lines = NonUFSFiles . Split ( '\n' ) ;
bNeedsIPA = Lines . Length > 0 & & ! string . IsNullOrWhiteSpace ( Lines [ 0 ] ) ;
}
}
// Add a commandline for this deploy, if the config allows it.
string AdditionalCommandline = ( Params . FileServer | | Params . CookOnTheFly ) ? "" : ( " -additionalcommandline " + "\"" + Params . RunCommandline + "\"" ) ;
// deploy the .ipa
var DeployServer = CombinePaths ( CmdEnv . LocalRoot , "Engine/Binaries/DotNET/IOS/DeploymentServer.exe" ) ;
// check for it in the stage directory
string CurrentDir = Directory . GetCurrentDirectory ( ) ;
Directory . SetCurrentDirectory ( CombinePaths ( CmdEnv . LocalRoot , "Engine/Binaries/DotNET/IOS/" ) ) ;
if ( ! Params . IterativeDeploy | | bCreatedIPA | | bNeedsIPA )
{
RunAndLog ( CmdEnv , DeployServer , "Install -ipa \"" + Path . GetFullPath ( StagedIPA ) + "\"" + ( String . IsNullOrEmpty ( Params . Device ) ? "" : " -device " + Params . Device . Substring ( 4 ) ) + AdditionalCommandline ) ;
}
if ( Params . IterativeDeploy )
{
// push over the changed files
2014-12-18 17:37:28 -05:00
RunAndLog ( CmdEnv , DeployServer , "Deploy -manifest \"" + CombinePaths ( Params . BaseStageDirectory , "IOS" , DeploymentContext . UFSDeployDeltaFileName ) + "\"" + ( String . IsNullOrEmpty ( Params . Device ) ? "" : " -device " + Params . Device . Substring ( 4 ) ) + AdditionalCommandline + " -bundle " + BundleIdentifier ) ;
2014-12-11 16:20:07 -05:00
}
Directory . SetCurrentDirectory ( CurrentDir ) ;
2014-03-14 14:13:41 -04:00
PrintRunTime ( ) ;
}
public override string GetCookPlatform ( bool bDedicatedServer , bool bIsClientOnly , string CookFlavor )
{
return "IOS" ;
}
public override bool DeployPakInternalLowerCaseFilenames ( )
{
return false ;
}
public override bool DeployLowerCaseFilenames ( bool bUFSFile )
{
// we shouldn't modify the case on files like Info.plist or the icons
return bUFSFile ;
}
public override string LocalPathToTargetPath ( string LocalPath , string LocalRoot )
{
return LocalPath . Replace ( "\\" , "/" ) . Replace ( LocalRoot , "../../.." ) ;
}
public override bool IsSupported { get { return true ; } }
2014-08-06 07:05:15 -04:00
public override bool LaunchViaUFE { get { return UnrealBuildTool . BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Mac ; } }
2014-03-14 14:13:41 -04:00
2014-12-18 17:37:28 -05:00
public override bool UseAbsLog
{
get
{
return ! LaunchViaUFE ;
}
}
2014-03-14 14:13:41 -04:00
public override string Remap ( string Dest )
{
return "cookeddata/" + Dest ;
}
public override List < string > GetDebugFileExtentions ( )
{
return new List < string > { ".dsym" } ;
}
public override ProcessResult RunClient ( ERunOptions ClientRunFlags , string ClientApp , string ClientCmdLine , ProjectParams Params )
{
2014-08-06 07:05:15 -04:00
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
2014-03-14 14:13:41 -04:00
{
2015-01-09 17:44:23 -05:00
/ * string AppDirectory = string . Format ( "{0}/Payload/{1}.app" ,
2014-03-14 14:13:41 -04:00
Path . GetDirectoryName ( Params . ProjectGameExeFilename ) ,
Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) ) ;
string GameName = Path . GetFileNameWithoutExtension ( ClientApp ) ;
2014-04-02 18:09:23 -04:00
if ( GameName . Contains ( "-IOS-" ) )
{
GameName = GameName . Substring ( 0 , GameName . IndexOf ( "-IOS-" ) ) ;
}
2014-03-14 14:13:41 -04:00
string GameApp = AppDirectory + "/" + GameName ;
2015-01-09 17:44:23 -05:00
bWasGenerated = false ;
XcodeProj = EnsureXcodeProjectExists ( Params . RawProjectPath , CmdEnv . LocalRoot , Params . ShortProjectName , GetDirectoryName ( Params . RawProjectPath ) , Params . IsCodeBasedProject , out bWasGenerated ) ;
2014-03-14 14:13:41 -04:00
string Arguments = "UBT_NO_POST_DEPLOY=true /usr/bin/xcrun xcodebuild test -project \"" + XcodeProj + "\"" ;
Arguments + = " -scheme '" ;
Arguments + = GameName ;
2014-04-23 17:32:01 -04:00
Arguments + = " - iOS'" ;
2014-03-14 14:13:41 -04:00
Arguments + = " -configuration " + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) ;
Arguments + = " -destination 'platform=iOS,id=" + Params . Device . Substring ( 4 ) + "'" ;
Arguments + = " TEST_HOST=\"" ;
Arguments + = GameApp ;
Arguments + = "\" BUNDLE_LOADER=\"" ;
2015-01-09 17:44:23 -05:00
Arguments + = GameApp + "\"" ; * /
string BundleIdentifier = "" ;
if ( File . Exists ( Params . BaseStageDirectory + "/IOS/Info.plist" ) )
{
string Contents = File . ReadAllText ( Params . BaseStageDirectory + "/IOS/Info.plist" ) ;
int Pos = Contents . IndexOf ( "CFBundleIdentifier" ) ;
Pos = Contents . IndexOf ( "<string>" , Pos ) + 8 ;
int EndPos = Contents . IndexOf ( "</string>" , Pos ) ;
BundleIdentifier = Contents . Substring ( Pos , EndPos - Pos ) ;
}
string Arguments = "/usr/bin/instruments" ;
Arguments + = " -w '" + Params . Device . Substring ( 4 ) + "'" ;
Arguments + = " -t 'Activity Monitor'" ;
Arguments + = " -D \"" + Params . BaseStageDirectory + "/IOS/launch.trace\"" ;
Arguments + = " '" + BundleIdentifier + "'" ;
2014-03-14 14:13:41 -04:00
ProcessResult ClientProcess = Run ( "/usr/bin/env" , Arguments , null , ClientRunFlags | ERunOptions . NoWaitForExit ) ;
return ClientProcess ;
}
else
{
2014-12-17 11:17:53 -05:00
ProcessResult Result = new ProcessResult ( "DummyApp" , null , false , null ) ;
Result . ExitCode = 0 ;
return Result ;
2014-03-14 14:13:41 -04:00
}
}
2015-01-09 17:44:23 -05:00
public override void PostRunClient ( ProcessResult Result , ProjectParams Params )
{
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
{
Console . WriteLine ( "Deleting " + Params . BaseStageDirectory + "/IOS/launch.trace" ) ;
2015-01-19 11:44:55 -05:00
if ( Directory . Exists ( Params . BaseStageDirectory + "/IOS/launch.trace" ) )
{
Directory . Delete ( Params . BaseStageDirectory + "/IOS/launch.trace" , true ) ;
}
2015-05-11 19:34:55 -04:00
switch ( Result . ExitCode )
{
case 253 :
2015-06-30 11:40:05 -04:00
throw new AutomationException ( ErrorCodes . Error_DeviceNotSetupForDevelopment , "Launch Failure" ) ;
2015-05-11 19:34:55 -04:00
case 255 :
2015-06-30 11:40:05 -04:00
throw new AutomationException ( ErrorCodes . Error_DeviceOSNewerThanSDK , "Launch Failure" ) ;
2015-05-11 19:34:55 -04:00
}
2015-01-09 17:44:23 -05:00
}
}
2014-06-26 17:46:56 -04:00
public override bool StageMovies
{
get { return false ; }
}
2014-09-18 18:55:01 -04:00
public override bool RequiresPackageToDeploy
{
get { return true ; }
}
2014-12-11 16:20:07 -05:00
public override List < string > GetFilesForCRCCheck ( )
{
List < string > FileList = base . GetFilesForCRCCheck ( ) ;
FileList . Add ( "Info.plist" ) ;
return FileList ;
}
2014-03-14 14:13:41 -04:00
#region Hooks
public override void PreBuildAgenda ( UE4Build Build , UE4Build . BuildAgenda Agenda )
{
2014-08-06 07:05:15 -04:00
if ( UnrealBuildTool . BuildHostPlatform . Current . Platform ! = UnrealTargetPlatform . Mac )
2014-03-14 14:13:41 -04:00
{
2014-12-11 16:20:07 -05:00
Agenda . DotNetProjects . Add ( @"Engine\Source\Programs\IOS\MobileDeviceInterface\MobileDeviceInterface.csproj" ) ;
Agenda . DotNetProjects . Add ( @"Engine\Source\Programs\IOS\iPhonePackager\iPhonePackager.csproj" ) ;
Agenda . DotNetProjects . Add ( @"Engine\Source\Programs\IOS\DeploymentServer\DeploymentServer.csproj" ) ;
2014-05-08 15:24:09 -04:00
}
2014-03-14 14:13:41 -04:00
}
#endregion
}