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 System.Diagnostics ;
using System.Net.NetworkInformation ;
2014-06-04 15:01:25 -04:00
using System.Threading ;
2014-03-14 14:13:41 -04:00
using AutomationTool ;
using UnrealBuildTool ;
2014-11-17 11:31:49 -05:00
using Ionic.Zip ;
2014-03-14 14:13:41 -04:00
public class AndroidPlatform : Platform
{
2014-08-18 13:29:39 -04:00
private const int DeployMaxParallelCommands = 6 ;
2014-06-18 17:43:14 -04:00
2014-03-14 14:13:41 -04:00
public AndroidPlatform ( )
2014-08-18 13:29:39 -04:00
: base ( UnrealTargetPlatform . Android )
2014-03-14 14:13:41 -04:00
{
2014-12-11 17:09:39 -05:00
bool bNeedsAndroidHome = string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "ANDROID_HOME" ) ) ;
if ( Utils . IsRunningOnMono & & bNeedsAndroidHome )
{
// Try reading env variable we need from .bash_profile
string BashProfilePath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) , ".bash_profile" ) ;
2014-12-11 22:44:38 -05:00
if ( File . Exists ( BashProfilePath ) )
2014-12-11 17:09:39 -05:00
{
2014-12-11 22:44:38 -05:00
string [ ] BashProfileContents = File . ReadAllLines ( BashProfilePath ) ;
foreach ( string Line in BashProfileContents )
2014-12-11 17:09:39 -05:00
{
2014-12-11 22:44:38 -05:00
if ( Line . StartsWith ( "export ANDROID_HOME=" ) )
{
string PathVar = Line . Split ( '=' ) [ 1 ] . Replace ( "\"" , "" ) ;
Environment . SetEnvironmentVariable ( "ANDROID_HOME" , PathVar ) ;
}
2014-12-11 17:09:39 -05:00
}
}
}
2014-03-14 14:13:41 -04:00
}
2014-08-26 09:56:29 -04:00
private static string GetSONameWithoutArchitecture ( ProjectParams Params , string DecoratedExeName )
2014-03-14 14:13:41 -04:00
{
2014-08-26 09:56:29 -04:00
return Path . Combine ( Path . GetDirectoryName ( Params . ProjectGameExeFilename ) , DecoratedExeName ) + ".so" ;
2014-03-14 14:13:41 -04:00
}
2014-09-18 17:49:40 -04:00
private static string GetFinalApkName ( ProjectParams Params , string DecoratedExeName , bool bRenameUE4Game , string Architecture , string GPUArchitecture )
2014-03-14 14:13:41 -04:00
{
2014-08-18 13:29:39 -04:00
string ProjectDir = Path . Combine ( Path . GetDirectoryName ( Path . GetFullPath ( Params . RawProjectPath ) ) , "Binaries/Android" ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
if ( Params . Prebuilt )
{
ProjectDir = Path . Combine ( Params . BaseStageDirectory , "Android" ) ;
}
// Apk's go to project location, not necessarily where the .so is (content only packages need to output to their directory)
2014-09-18 17:49:40 -04:00
string ApkName = Path . Combine ( ProjectDir , DecoratedExeName ) + Architecture + GPUArchitecture + ".apk" ;
2014-08-18 13:29:39 -04:00
2014-03-14 14:13:41 -04:00
// if the source binary was UE4Game, handle using it or switching to project name
if ( Path . GetFileNameWithoutExtension ( Params . ProjectGameExeFilename ) = = "UE4Game" )
{
if ( bRenameUE4Game )
{
// replace UE4Game with project name (only replace in the filename part)
ApkName = Path . Combine ( Path . GetDirectoryName ( ApkName ) , Path . GetFileName ( ApkName ) . Replace ( "UE4Game" , Params . ShortProjectName ) ) ;
}
else
{
// if we want to use UE4 directly then use it from the engine directory not project directory
ApkName = ApkName . Replace ( ProjectDir , Path . Combine ( CmdEnv . LocalRoot , "Engine" ) ) ;
}
}
return ApkName ;
}
private static string GetFinalObbName ( string ApkName )
{
// calculate the name for the .obb file
string PackageName = GetPackageInfo ( ApkName , false ) ;
if ( PackageName = = null )
{
2014-09-12 06:23:49 -04:00
ErrorReporter . Error ( "Failed to get package name from " + ApkName , ( int ) ErrorCodes . Error_FailureGettingPackageInfo ) ;
2014-03-14 14:13:41 -04:00
throw new AutomationException ( "Failed to get package name from " + ApkName ) ;
}
string PackageVersion = GetPackageInfo ( ApkName , true ) ;
if ( PackageVersion = = null | | PackageVersion . Length = = 0 )
{
2014-09-12 06:23:49 -04:00
ErrorReporter . Error ( "Failed to get package version from " + ApkName , ( int ) ErrorCodes . Error_FailureGettingPackageInfo ) ;
2014-03-14 14:13:41 -04:00
throw new AutomationException ( "Failed to get package version from " + ApkName ) ;
}
if ( PackageVersion . Length > 0 )
{
int IntVersion = int . Parse ( PackageVersion ) ;
PackageVersion = IntVersion . ToString ( "00000" ) ;
}
string ObbName = string . Format ( "main.{0}.{1}.obb" , PackageVersion , PackageName ) ;
// plop the .obb right next to the executable
ObbName = Path . Combine ( Path . GetDirectoryName ( ApkName ) , ObbName ) ;
return ObbName ;
}
private static string GetDeviceObbName ( string ApkName )
{
string ObbName = GetFinalObbName ( ApkName ) ;
string PackageName = GetPackageInfo ( ApkName , false ) ;
2014-11-10 06:04:27 -05:00
return "obb/" + PackageName + "/" + Path . GetFileName ( ObbName ) ;
2014-03-14 14:13:41 -04:00
}
2014-11-10 06:04:27 -05:00
private static string GetStorageQueryCommand ( )
{
2014-12-11 15:30:24 -05:00
if ( Utils . IsRunningOnMono )
{
return "shell 'echo $EXTERNAL_STORAGE'" ;
}
else
{
return "shell \"echo $EXTERNAL_STORAGE\"" ;
}
2014-11-10 06:04:27 -05:00
}
2014-09-18 17:49:40 -04:00
private static string GetFinalBatchName ( string ApkName , ProjectParams Params , string Architecture , string GPUArchitecture )
2014-03-14 14:13:41 -04:00
{
2014-12-11 15:30:24 -05:00
return Path . Combine ( Path . GetDirectoryName ( ApkName ) , "Install_" + Params . ShortProjectName + "_" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) + Architecture + GPUArchitecture + ( Utils . IsRunningOnMono ? ".sh" : ".bat" ) ) ;
2014-03-14 14:13:41 -04:00
}
public override void Package ( ProjectParams Params , DeploymentContext SC , int WorkingCL )
{
2014-08-26 09:56:29 -04:00
string [ ] Architectures = UnrealBuildTool . AndroidToolChain . GetAllArchitectures ( ) ;
2014-09-18 17:49:40 -04:00
string [ ] GPUArchitectures = UnrealBuildTool . AndroidToolChain . GetAllGPUArchitectures ( ) ;
2014-08-26 09:56:29 -04:00
bool bMakeSeparateApks = UnrealBuildTool . Android . UEDeployAndroid . ShouldMakeSeparateApks ( ) ;
2014-03-14 14:13:41 -04:00
2014-11-17 11:31:49 -05:00
// Make sure this setting is sync'd pre-build
UEBuildConfiguration . bOBBinAPK = Params . OBBinAPK ;
var Deploy = UEBuildDeploy . GetBuildDeploy ( UnrealTargetPlatform . Android ) ;
string BaseApkName = GetFinalApkName ( Params , SC . StageExecutables [ 0 ] , true , "" , "" ) ;
Log ( "BaseApkName = {0}" , BaseApkName ) ;
// Create main OBB with entire contents of staging dir. This
// includes any PAK files, movie files, etc.
string LocalObbName = SC . StageDirectory . TrimEnd ( new char [ ] { '/' , '\\' } ) + ".obb" ;
// Always delete the target OBB file if it exists
if ( File . Exists ( LocalObbName ) )
{
File . Delete ( LocalObbName ) ;
}
// Now create the OBB as a ZIP archive.
Log ( "Creating {0} from {1}" , LocalObbName , SC . StageDirectory ) ;
using ( ZipFile ObbFile = new ZipFile ( LocalObbName ) )
{
ObbFile . CompressionMethod = CompressionMethod . None ;
ObbFile . CompressionLevel = Ionic . Zlib . CompressionLevel . None ;
int ObbFileCount = 0 ;
ObbFile . AddProgress + =
delegate ( object sender , AddProgressEventArgs e )
{
if ( e . EventType = = ZipProgressEventType . Adding_AfterAddEntry )
{
ObbFileCount + = 1 ;
Log ( "[{0}/{1}] Adding {2} to OBB" ,
ObbFileCount , e . EntriesTotal ,
e . CurrentEntry . FileName ) ;
}
} ;
ObbFile . AddDirectory ( SC . StageDirectory + "/" + SC . ShortProjectName , SC . ShortProjectName ) ;
ObbFile . Save ( ) ;
}
2014-08-26 09:56:29 -04:00
foreach ( string Architecture in Architectures )
2014-08-18 13:29:39 -04:00
{
2014-09-18 17:49:40 -04:00
foreach ( string GPUArchitecture in GPUArchitectures )
{
string ApkName = GetFinalApkName ( Params , SC . StageExecutables [ 0 ] , true , bMakeSeparateApks ? Architecture : "" , bMakeSeparateApks ? GPUArchitecture : "" ) ;
string BatchName = GetFinalBatchName ( ApkName , Params , bMakeSeparateApks ? Architecture : "" , bMakeSeparateApks ? GPUArchitecture : "" ) ;
2014-08-26 09:56:29 -04:00
2014-12-10 13:33:56 -05:00
if ( ! Params . Prebuilt )
{
string CookFlavor = SC . FinalCookPlatform . IndexOf ( "_" ) > 0 ? SC . FinalCookPlatform . Substring ( SC . FinalCookPlatform . IndexOf ( "_" ) ) : "" ;
string SOName = GetSONameWithoutArchitecture ( Params , SC . StageExecutables [ 0 ] ) ;
Deploy . PrepForUATPackageOrDeploy ( Params . ShortProjectName , SC . ProjectRoot , SOName , SC . LocalRoot + "/Engine" , Params . Distribution , CookFlavor ) ;
}
// Create APK specific OBB in case we have a detached OBB.
string DeviceObbName = "" ;
string ObbName = "" ;
if ( ! Params . OBBinAPK )
{
DeviceObbName = GetDeviceObbName ( ApkName ) ;
ObbName = GetFinalObbName ( ApkName ) ;
CopyFile ( LocalObbName , ObbName ) ;
}
// Write install batch file(s).
string PackageName = GetPackageInfo ( ApkName , false ) ;
// make a batch file that can be used to install the .apk and .obb files
2014-12-11 15:30:24 -05:00
string [ ] BatchLines ;
if ( Utils . IsRunningOnMono )
{
Log ( "Writing shell script for install with {0}" , Params . OBBinAPK ? "OBB in APK" : "OBB separate" ) ;
BatchLines = new string [ ] {
"#!/bin/sh" ,
"ADB=$ANDROID_HOME/platform-tools/adb" ,
"DEVICE=" ,
"if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi" ,
"$ADB $DEVICE uninstall " + PackageName ,
"$ADB $DEVICE install " + Path . GetFileName ( ApkName ) ,
"if [ $? -eq 0 ]; then" ,
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/" + Params . ShortProjectName + "'" ,
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/UE4CommandLine.txt" + "'" ,
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/obb/" + PackageName + "'" ,
"\tSTORAGE=$(echo \"`$ADB $DEVICE shell 'echo $EXTERNAL_STORAGE'`\" | cat -v | tr -d '^M')" ,
"\t$ADB $DEVICE push " + Path . GetFileName ( ObbName ) + " $STORAGE/" + DeviceObbName ,
"\tif [ $? -eq 0 ]; then" ,
"\t\texit 0" ,
"\tfi" ,
"fi" ,
"echo" ,
"echo \"There was an error installing the game or the obb file. Look above for more info.\"" ,
"echo" ,
"echo \"Things to try:\"" ,
"echo \"Check that the device (and only the device) is listed with \\\"$ADB devices\\\" from a command prompt.\"" ,
"echo \"Make sure all Developer options look normal on the device\"" ,
"echo \"Check that the device has an SD card.\"" ,
"exit 1"
} ;
}
else
{
Log ( "Writing bat for install with {0}" , Params . OBBinAPK ? "OBB in APK" : "OBB separate" ) ;
BatchLines = new string [ ] {
"setlocal" ,
"set ADB=%ANDROID_HOME%\\platform-tools\\adb.exe" ,
"set DEVICE=" ,
"if not \"%1\"==\"\" set DEVICE=-s %1" ,
"for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand ( ) + "') do @set STORAGE=%%A" ,
"%ADB% %DEVICE% uninstall " + PackageName ,
"%ADB% %DEVICE% install " + Path . GetFileName ( ApkName ) ,
"@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error" ,
"%ADB% %DEVICE% shell rm -r %STORAGE%/" + Params . ShortProjectName ,
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt" , // we need to delete the commandline in UE4Game or it will mess up loading
"%ADB% %DEVICE% shell rm -r %STORAGE%/obb/" + PackageName ,
Params . OBBinAPK ? "" : "%ADB% %DEVICE% push " + Path . GetFileName ( ObbName ) + " %STORAGE%/" + DeviceObbName ,
Params . OBBinAPK ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error" ,
"goto:eof" ,
":Error" ,
"@echo." ,
"@echo There was an error installing the game or the obb file. Look above for more info." ,
"@echo." ,
"@echo Things to try:" ,
"@echo Check that the device (and only the device) is listed with \"%ADB$ devices\" from a command prompt." ,
"@echo Make sure all Developer options look normal on the device" ,
"@echo Check that the device has an SD card." ,
"@pause"
} ;
}
2014-12-10 13:33:56 -05:00
File . WriteAllLines ( BatchName , BatchLines ) ;
2014-08-26 09:56:29 -04:00
}
2014-09-18 17:49:40 -04:00
}
2014-08-18 13:29:39 -04:00
PrintRunTime ( ) ;
2014-03-14 14:13:41 -04:00
}
public override void GetFilesToArchive ( ProjectParams Params , DeploymentContext SC )
{
if ( SC . StageTargetConfigurations . Count ! = 1 )
{
2014-09-12 06:23:49 -04:00
string ErrorString = String . Format ( "Android is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations" , SC . StageTargetConfigurations . Count ) ;
ErrorReporter . Error ( ErrorString , ( int ) ErrorCodes . Error_OnlyOneTargetConfigurationSupported ) ;
throw new AutomationException ( ErrorString ) ;
2014-03-14 14:13:41 -04:00
}
2014-08-26 14:02:30 -04:00
string [ ] Architectures = UnrealBuildTool . AndroidToolChain . GetAllArchitectures ( ) ;
2014-09-18 17:49:40 -04:00
string [ ] GPUArchitectures = UnrealBuildTool . AndroidToolChain . GetAllGPUArchitectures ( ) ;
2014-08-26 14:02:30 -04:00
bool bMakeSeparateApks = UnrealBuildTool . Android . UEDeployAndroid . ShouldMakeSeparateApks ( ) ;
2014-03-14 14:13:41 -04:00
2014-09-22 09:46:58 -04:00
bool bAddedOBB = false ;
2014-08-26 14:02:30 -04:00
foreach ( string Architecture in Architectures )
2014-03-14 14:13:41 -04:00
{
2014-09-18 17:49:40 -04:00
foreach ( string GPUArchitecture in GPUArchitectures )
{
string ApkName = GetFinalApkName ( Params , SC . StageExecutables [ 0 ] , true , bMakeSeparateApks ? Architecture : "" , bMakeSeparateApks ? GPUArchitecture : "" ) ;
2014-12-10 13:33:56 -05:00
string ObbName = GetFinalObbName ( ApkName ) ;
2014-09-18 17:49:40 -04:00
string BatchName = GetFinalBatchName ( ApkName , Params , bMakeSeparateApks ? Architecture : "" , bMakeSeparateApks ? GPUArchitecture : "" ) ;
2014-03-14 14:13:41 -04:00
2014-12-10 13:33:56 -05:00
// verify the files exist
if ( ! FileExists ( ApkName ) )
{
string ErrorString = String . Format ( "ARCHIVE FAILED - {0} was not found" , ApkName ) ;
ErrorReporter . Error ( ErrorString , ( int ) ErrorCodes . Error_AppNotFound ) ;
throw new AutomationException ( ErrorString ) ;
}
if ( ! Params . OBBinAPK & & ! FileExists ( ObbName ) )
{
string ErrorString = String . Format ( "ARCHIVE FAILED - {0} was not found" , ObbName ) ;
ErrorReporter . Error ( ErrorString , ( int ) ErrorCodes . Error_ObbNotFound ) ;
throw new AutomationException ( ErrorString ) ;
}
2014-05-13 11:40:41 -04:00
2014-12-10 13:33:56 -05:00
SC . ArchiveFiles ( Path . GetDirectoryName ( ApkName ) , Path . GetFileName ( ApkName ) ) ;
if ( ! Params . OBBinAPK & & ! bAddedOBB )
{
bAddedOBB = true ;
SC . ArchiveFiles ( Path . GetDirectoryName ( ObbName ) , Path . GetFileName ( ObbName ) ) ;
}
2014-08-26 14:02:30 -04:00
2014-12-10 13:33:56 -05:00
SC . ArchiveFiles ( Path . GetDirectoryName ( BatchName ) , Path . GetFileName ( BatchName ) ) ;
}
2014-08-26 14:02:30 -04:00
}
2014-03-14 14:13:41 -04:00
}
2014-12-10 13:33:56 -05:00
private string GetAdbCommandLine ( ProjectParams Params , string Args )
2014-03-14 14:13:41 -04:00
{
string SerialNumber = Params . Device ;
if ( SerialNumber . Contains ( "@" ) )
{
// get the bit after the @
SerialNumber = SerialNumber . Split ( "@" . ToCharArray ( ) ) [ 1 ] ;
}
if ( SerialNumber ! = "" )
{
SerialNumber = " -s " + SerialNumber ;
}
2014-12-10 13:33:56 -05:00
string AdbCommand = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/platform-tools/adb" + ( Utils . IsRunningOnMono ? "" : ".exe" ) + SerialNumber + " " ) ;
return string . Format ( "{0} {1}{2}{3}{1}" , Utils . IsRunningOnMono ? "-c" : "/c" , Utils . IsRunningOnMono ? "'" : "" , AdbCommand , Args ) ;
}
private ProcessResult RunAdbCommand ( ProjectParams Params , string Args , string Input = null , ERunOptions Options = ERunOptions . Default )
{
return Run ( CmdEnv . CmdExe , GetAdbCommandLine ( Params , Args ) , Input , Options ) ;
}
private string RunAndLogAdbCommand ( ProjectParams Params , string Args , out int SuccessCode )
{
return RunAndLog ( CmdEnv , CmdEnv . CmdExe , GetAdbCommandLine ( Params , Args ) , out SuccessCode ) ;
2014-03-14 14:13:41 -04:00
}
2014-08-18 13:29:39 -04:00
public override void GetConnectedDevices ( ProjectParams Params , out List < string > Devices )
{
Devices = new List < string > ( ) ;
2014-12-10 13:33:56 -05:00
ProcessResult Result = RunAdbCommand ( Params , "devices" ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
if ( Result . Output . Length > 0 )
{
string [ ] LogLines = Result . Output . Split ( new char [ ] { '\n' , '\r' } ) ;
bool FoundList = false ;
for ( int i = 0 ; i < LogLines . Length ; + + i )
{
if ( FoundList = = false )
{
if ( LogLines [ i ] . StartsWith ( "List of devices attached" ) )
{
FoundList = true ;
}
continue ;
}
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
string [ ] DeviceLine = LogLines [ i ] . Split ( new char [ ] { '\t' } ) ;
2014-06-20 15:18:17 -04:00
2014-08-18 13:29:39 -04:00
if ( DeviceLine . Length = = 2 )
{
// the second param should be "device"
// if it's not setup correctly it might be "unattached" or "powered off" or something like that
// warning in that case
if ( DeviceLine [ 1 ] = = "device" )
{
Devices . Add ( "@" + DeviceLine [ 0 ] ) ;
}
else
{
CommandUtils . LogWarning ( "Device attached but in bad state {0}:{1}" , DeviceLine [ 0 ] , DeviceLine [ 1 ] ) ;
}
}
}
}
}
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
/ *
private class TimeRegion : System . IDisposable
{
private System . DateTime StartTime { get ; set ; }
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
private string Format { get ; set ; }
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
private System . Collections . Generic . List < object > FormatArgs { get ; set ; }
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
public TimeRegion ( string format , params object [ ] format_args )
{
Format = format ;
FormatArgs = new List < object > ( format_args ) ;
StartTime = DateTime . UtcNow ;
}
public void Dispose ( )
{
double total_time = ( DateTime . UtcNow - StartTime ) . TotalMilliseconds / 1000.0 ;
FormatArgs . Insert ( 0 , total_time ) ;
CommandUtils . Log ( Format , FormatArgs . ToArray ( ) ) ;
}
}
* /
internal class LongestFirst : IComparer < string >
{
public int Compare ( string a , string b )
{
if ( a . Length = = b . Length ) return a . CompareTo ( b ) ;
else return b . Length - a . Length ;
}
}
2014-06-18 17:43:14 -04:00
2014-03-14 14:13:41 -04:00
public override void Deploy ( ProjectParams Params , DeploymentContext SC )
{
2014-09-08 14:02:00 -04:00
string DeviceArchitecture = GetBestDeviceArchitecture ( Params ) ;
2014-12-11 13:44:41 -05:00
string GPUArchitecture = GetBestGPUArchitecture ( Params ) ;
2014-08-26 14:02:30 -04:00
2014-09-18 17:49:40 -04:00
string ApkName = GetFinalApkName ( Params , SC . StageExecutables [ 0 ] , true , DeviceArchitecture , GPUArchitecture ) ;
2014-04-02 18:09:23 -04:00
// make sure APK is up to date (this is fast if so)
2014-08-18 13:29:39 -04:00
var Deploy = UEBuildDeploy . GetBuildDeploy ( UnrealTargetPlatform . Android ) ;
if ( ! Params . Prebuilt )
{
string CookFlavor = SC . FinalCookPlatform . IndexOf ( "_" ) > 0 ? SC . FinalCookPlatform . Substring ( SC . FinalCookPlatform . IndexOf ( "_" ) ) : "" ;
2014-08-26 09:56:29 -04:00
string SOName = GetSONameWithoutArchitecture ( Params , SC . StageExecutables [ 0 ] ) ;
2014-08-18 13:29:39 -04:00
Deploy . PrepForUATPackageOrDeploy ( Params . ShortProjectName , SC . ProjectRoot , SOName , SC . LocalRoot + "/Engine" , Params . Distribution , CookFlavor ) ;
}
2014-04-02 18:09:23 -04:00
// now we can use the apk to get more info
2014-03-14 14:13:41 -04:00
string PackageName = GetPackageInfo ( ApkName , false ) ;
2014-11-17 11:31:49 -05:00
// try uninstalling an old app with the same identifier.
2014-12-10 13:33:56 -05:00
int SuccessCode = 0 ;
string UninstallCommandline = "uninstall " + PackageName ;
RunAndLogAdbCommand ( Params , UninstallCommandline , out SuccessCode ) ;
2014-03-14 14:13:41 -04:00
2014-09-12 06:23:49 -04:00
// install the apk
2014-12-10 13:33:56 -05:00
string InstallCommandline = "install \"" + ApkName + "\"" ;
string InstallOutput = RunAndLogAdbCommand ( Params , InstallCommandline , out SuccessCode ) ;
2014-11-17 11:31:49 -05:00
int FailureIndex = InstallOutput . IndexOf ( "Failure" ) ;
2014-09-12 06:23:49 -04:00
2014-11-17 11:31:49 -05:00
// adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code.
if ( SuccessCode ! = 0 | | FailureIndex ! = - 1 )
{
string ErrorMessage = String . Format ( "Installation of apk '{0}' failed" , ApkName ) ;
if ( FailureIndex ! = - 1 )
{
string FailureString = InstallOutput . Substring ( FailureIndex + 7 ) . Trim ( ) ;
if ( FailureString ! = "" )
{
ErrorMessage + = ": " + FailureString ;
}
}
2014-03-14 14:13:41 -04:00
2014-11-17 11:31:49 -05:00
ErrorReporter . Error ( ErrorMessage , ( int ) ErrorCodes . Error_AppInstallFailed ) ;
throw new AutomationException ( ErrorMessage ) ;
}
2014-03-14 14:13:41 -04:00
// update the ue4commandline.txt
// update and deploy ue4commandline.txt
// always delete the existing commandline text file, so it doesn't reuse an old one
string IntermediateCmdLineFile = CombinePaths ( SC . StageDirectory , "UE4CommandLine.txt" ) ;
Project . WriteStageCommandline ( IntermediateCmdLineFile , Params , SC ) ;
2014-11-10 06:04:27 -05:00
// Setup the OBB name and add the storage path (queried from the device) to it
string DeviceStorageQueryCommand = GetStorageQueryCommand ( ) ;
2014-12-10 13:33:56 -05:00
ProcessResult Result = RunAdbCommand ( Params , DeviceStorageQueryCommand , null , ERunOptions . AppMustExist ) ;
2014-11-10 06:04:27 -05:00
String StorageLocation = Result . Output . Trim ( ) ;
string DeviceObbName = StorageLocation + "/" + GetDeviceObbName ( ApkName ) ;
2014-04-02 18:09:23 -04:00
2014-03-14 14:13:41 -04:00
// copy files to device if we were staging
2014-06-11 18:10:21 -04:00
if ( SC . Stage )
2014-03-14 14:13:41 -04:00
{
// cache some strings
2014-12-10 13:33:56 -05:00
string BaseCommandline = "push" ;
2014-11-10 06:04:27 -05:00
string RemoteDir = StorageLocation + "/" + Params . ShortProjectName ;
string UE4GameRemoteDir = StorageLocation + "/" + Params . ShortProjectName ;
2014-03-14 14:13:41 -04:00
// make sure device is at a clean state
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , "shell rm -r " + RemoteDir ) ;
RunAdbCommand ( Params , "shell rm -r " + UE4GameRemoteDir ) ;
2014-03-14 14:13:41 -04:00
2014-08-18 13:29:39 -04:00
// Copy UFS files..
2014-03-14 14:13:41 -04:00
string [ ] Files = Directory . GetFiles ( SC . StageDirectory , "*" , SearchOption . AllDirectories ) ;
2014-08-18 13:29:39 -04:00
System . Array . Sort ( Files ) ;
2014-03-14 14:13:41 -04:00
2014-08-18 13:29:39 -04:00
// Find all the files we exclude from copying. And include
// the directories we need to individually copy.
HashSet < string > ExcludedFiles = new HashSet < string > ( ) ;
SortedSet < string > IndividualCopyDirectories
= new SortedSet < string > ( ( IComparer < string > ) new LongestFirst ( ) ) ;
foreach ( string Filename in Files )
{
bool Exclude = false ;
// Don't push the apk, we install it
Exclude | = Path . GetExtension ( Filename ) . Equals ( ".apk" , StringComparison . InvariantCultureIgnoreCase ) ;
// For excluded files we add the parent dirs to our
// tracking of stuff to individually copy.
if ( Exclude )
{
ExcludedFiles . Add ( Filename ) ;
// We include all directories up to the stage root in having
// to individually copy the files.
for ( string FileDirectory = Path . GetDirectoryName ( Filename ) ;
! FileDirectory . Equals ( SC . StageDirectory ) ;
FileDirectory = Path . GetDirectoryName ( FileDirectory ) )
{
if ( ! IndividualCopyDirectories . Contains ( FileDirectory ) )
{
IndividualCopyDirectories . Add ( FileDirectory ) ;
}
}
if ( ! IndividualCopyDirectories . Contains ( SC . StageDirectory ) )
{
IndividualCopyDirectories . Add ( SC . StageDirectory ) ;
}
}
}
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
// The directories are sorted above in "deepest" first. We can
// therefore start copying those individual dirs which will
// recreate the tree. As the subtrees will get copied at each
// possible individual level.
HashSet < string > EntriesToDeploy = new HashSet < string > ( ) ;
foreach ( string DirectoryName in IndividualCopyDirectories )
{
string [ ] Entries
= Directory . GetFileSystemEntries ( DirectoryName , "*" , SearchOption . TopDirectoryOnly ) ;
foreach ( string Entry in Entries )
{
// We avoid excluded files and the individual copy dirs
// (the individual copy dirs will get handled as we iterate).
if ( ExcludedFiles . Contains ( Entry ) | | IndividualCopyDirectories . Contains ( Entry ) )
{
continue ;
}
else
{
EntriesToDeploy . Add ( Entry ) ;
}
}
}
if ( EntriesToDeploy . Count = = 0 )
{
EntriesToDeploy . Add ( SC . StageDirectory ) ;
}
2014-06-18 17:43:14 -04:00
2014-08-18 13:29:39 -04:00
// We now have a minimal set of file & dir entries we need
// to deploy. Files we deploy will get individually copied
// and dirs will get the tree copies by default (that's
// what ADB does).
HashSet < ProcessResult > DeployCommands = new HashSet < ProcessResult > ( ) ;
foreach ( string Entry in EntriesToDeploy )
{
2014-03-14 14:13:41 -04:00
string FinalRemoteDir = RemoteDir ;
2014-06-18 17:43:14 -04:00
string RemotePath = Entry . Replace ( SC . StageDirectory , FinalRemoteDir ) . Replace ( "\\" , "/" ) ;
2014-08-18 13:29:39 -04:00
string Commandline = string . Format ( "{0} \"{1}\" \"{2}\"" , BaseCommandline , Entry , RemotePath ) ;
// We run deploy commands in parallel to maximize the connection
// throughput.
DeployCommands . Add (
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , Commandline , null ,
2014-08-18 13:29:39 -04:00
ERunOptions . Default | ERunOptions . NoWaitForExit ) ) ;
// But we limit the parallel commands to avoid overwhelming
// memory resources.
if ( DeployCommands . Count = = DeployMaxParallelCommands )
{
while ( DeployCommands . Count > DeployMaxParallelCommands / 2 )
{
Thread . Sleep ( 10 ) ;
DeployCommands . RemoveWhere (
delegate ( ProcessResult r )
{
return r . HasExited ;
} ) ;
}
}
}
foreach ( ProcessResult deploy_result in DeployCommands )
{
deploy_result . WaitForExit ( ) ;
}
2014-03-14 14:13:41 -04:00
// delete the .obb file, since it will cause nothing we just deployed to be used
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , "shell rm " + DeviceObbName ) ;
2014-08-18 13:29:39 -04:00
}
else if ( SC . Archive )
{
// deploy the obb if there is one
string ObbPath = Path . Combine ( SC . StageDirectory , GetFinalObbName ( ApkName ) ) ;
if ( File . Exists ( ObbPath ) )
{
// cache some strings
2014-12-10 13:33:56 -05:00
string BaseCommandline = "push" ;
2014-06-16 14:26:41 -04:00
2014-08-18 13:29:39 -04:00
string Commandline = string . Format ( "{0} \"{1}\" \"{2}\"" , BaseCommandline , ObbPath , DeviceObbName ) ;
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , Commandline ) ;
2014-08-18 13:29:39 -04:00
}
}
else
{
// cache some strings
2014-12-10 13:33:56 -05:00
string BaseCommandline = "push" ;
2014-11-10 06:04:27 -05:00
string RemoteDir = StorageLocation + "/" + Params . ShortProjectName ;
2014-03-14 14:13:41 -04:00
2014-08-18 13:29:39 -04:00
string FinalRemoteDir = RemoteDir ;
/ *
// handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
if ( ! Params . IsCodeBasedProject )
{
FinalRemoteDir = "/mnt/sdcard/UE4Game" ;
}
* /
2014-03-14 14:13:41 -04:00
2014-08-18 13:29:39 -04:00
string RemoteFilename = IntermediateCmdLineFile . Replace ( SC . StageDirectory , FinalRemoteDir ) . Replace ( "\\" , "/" ) ;
string Commandline = string . Format ( "{0} \"{1}\" \"{2}\"" , BaseCommandline , IntermediateCmdLineFile , RemoteFilename ) ;
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , Commandline ) ;
2014-08-18 13:29:39 -04:00
}
}
2014-03-14 14:13:41 -04:00
/** Internal usage for GetPackageName */
private static string PackageLine = null ;
2014-08-18 13:29:39 -04:00
private static Mutex PackageInfoMutex = new Mutex ( ) ;
2014-03-14 14:13:41 -04:00
/** Run an external exe (and capture the output), given the exe path and the commandline. */
private static string GetPackageInfo ( string ApkName , bool bRetrieveVersionCode )
{
// we expect there to be one, so use the first one
string AaptPath = GetAaptPath ( ) ;
2014-08-18 13:29:39 -04:00
PackageInfoMutex . WaitOne ( ) ;
2014-03-14 14:13:41 -04:00
var ExeInfo = new ProcessStartInfo ( AaptPath , "dump badging \"" + ApkName + "\"" ) ;
ExeInfo . UseShellExecute = false ;
ExeInfo . RedirectStandardOutput = true ;
using ( var GameProcess = Process . Start ( ExeInfo ) )
{
PackageLine = null ;
GameProcess . BeginOutputReadLine ( ) ;
GameProcess . OutputDataReceived + = ParsePackageName ;
GameProcess . WaitForExit ( ) ;
}
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
PackageInfoMutex . ReleaseMutex ( ) ;
2014-06-04 15:01:25 -04:00
2014-03-14 14:13:41 -04:00
string ReturnValue = null ;
if ( PackageLine ! = null )
{
// the line should look like: package: name='com.epicgames.qagame' versionCode='1' versionName='1.0'
string [ ] Tokens = PackageLine . Split ( "'" . ToCharArray ( ) ) ;
int TokenIndex = bRetrieveVersionCode ? 3 : 1 ;
if ( Tokens . Length > = TokenIndex + 1 )
{
ReturnValue = Tokens [ TokenIndex ] ;
}
}
return ReturnValue ;
}
/** Simple function to pipe output asynchronously */
private static void ParsePackageName ( object Sender , DataReceivedEventArgs Event )
{
// DataReceivedEventHandler is fired with a null string when the output stream is closed. We don't want to
// print anything for that event.
if ( ! String . IsNullOrEmpty ( Event . Data ) )
{
if ( PackageLine = = null )
{
string Line = Event . Data ;
if ( Line . StartsWith ( "package:" ) )
{
PackageLine = Line ;
}
}
}
}
private static string GetAaptPath ( )
{
// there is a numbered directory in here, hunt it down
string [ ] Subdirs = Directory . GetDirectories ( Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/build-tools/" ) ) ;
if ( Subdirs . Length = = 0 )
{
2014-09-12 06:23:49 -04:00
ErrorReporter . Error ( "Failed to find %ANDROID_HOME%/build-tools subdirectory" , ( int ) ErrorCodes . Error_AndroidBuildToolsPathNotFound ) ;
2014-03-14 14:13:41 -04:00
throw new AutomationException ( "Failed to find %ANDROID_HOME%/build-tools subdirectory" ) ;
}
// we expect there to be one, so use the first one
2014-12-10 13:33:56 -05:00
return Path . Combine ( Subdirs [ 0 ] , Utils . IsRunningOnMono ? "aapt" : "aapt.exe" ) ;
2014-03-14 14:13:41 -04:00
}
2014-09-08 14:02:00 -04:00
private string GetBestDeviceArchitecture ( ProjectParams Params )
{
bool bMakeSeparateApks = UnrealBuildTool . Android . UEDeployAndroid . ShouldMakeSeparateApks ( ) ;
// if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
if ( ! bMakeSeparateApks )
{
return "" ;
}
string [ ] AppArchitectures = AndroidToolChain . GetAllArchitectures ( ) ;
// ask the device
2014-12-11 13:44:41 -05:00
ProcessResult ABIResult = RunAdbCommand ( Params , " shell getprop ro.product.cpu.abi" , null , ERunOptions . AppMustExist ) ;
2014-09-08 14:02:00 -04:00
// the output is just the architecture
2014-12-11 13:44:41 -05:00
string DeviceArch = UnrealBuildTool . Android . UEDeployAndroid . GetUE4Arch ( ABIResult . Output . Trim ( ) ) ;
2014-09-08 14:02:00 -04:00
// if the architecture wasn't built, look for a backup
if ( Array . IndexOf ( AppArchitectures , DeviceArch ) = = - 1 )
{
// go from 64 to 32-bit
if ( DeviceArch = = "-arm64" )
{
DeviceArch = "-armv7" ;
}
// go from 64 to 32-bit
else if ( DeviceArch = = "-x86_64" )
{
if ( Array . IndexOf ( AppArchitectures , "-x86" ) = = - 1 )
{
DeviceArch = "-x86" ;
}
// if it didn't have 32-bit x86, look for 64-bit arm for emulation
// @todo android 64-bit: x86_64 most likely can't emulate arm64 at this ponit
// else if (Array.IndexOf(AppArchitectures, "-arm64") == -1)
// {
// DeviceArch = "-arm64";
// }
// finally try for 32-bit arm emulation (Houdini)
else
{
DeviceArch = "-armv7" ;
}
}
// use armv7 (with Houdini emulation)
else if ( DeviceArch = = "-x86" )
{
DeviceArch = "-armv7" ;
}
}
// if after the fallbacks, we still don't have it, we can't continue
if ( Array . IndexOf ( AppArchitectures , DeviceArch ) = = - 1 )
{
2014-09-12 06:23:49 -04:00
string ErrorString = String . Format ( "Unable to run because you don't have an apk that is usable on {0}" , Params . Device ) ;
ErrorReporter . Error ( ErrorString , ( int ) ErrorCodes . Error_NoApkSuitableForArchitecture ) ;
throw new AutomationException ( ErrorString ) ;
2014-09-08 14:02:00 -04:00
}
return DeviceArch ;
}
2014-12-11 13:44:41 -05:00
private string GetBestGPUArchitecture ( ProjectParams Params )
{
bool bMakeSeparateApks = UnrealBuildTool . Android . UEDeployAndroid . ShouldMakeSeparateApks ( ) ;
// if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
if ( ! bMakeSeparateApks )
{
return "" ;
}
2014-12-11 15:14:54 -05:00
string [ ] AppGPUArchitectures = AndroidToolChain . GetAllGPUArchitectures ( ) ;
2014-12-11 13:44:41 -05:00
// get the device extensions
2014-12-11 15:14:54 -05:00
ProcessResult ExtensionsResult = RunAdbCommand ( Params , "shell dumpsys SurfaceFlinger" , null , ERunOptions . AppMustExist ) ;
2014-12-11 13:44:41 -05:00
string Extensions = ExtensionsResult . Output . Trim ( ) ;
// look for AEP support
2014-12-11 15:14:54 -05:00
if ( Extensions . Contains ( "GL_ANDROID_extension_pack_es31a" ) & & Extensions . Contains ( "GL_EXT_color_buffer_half_float" ) )
2014-12-11 13:44:41 -05:00
{
2014-12-11 15:14:54 -05:00
if ( AppGPUArchitectures . Contains ( "-es31" ) )
{
return "-es31" ;
}
2014-12-11 13:44:41 -05:00
}
return "-es2" ;
}
2014-09-08 14:02:00 -04:00
2014-08-18 13:29:39 -04:00
public override ProcessResult RunClient ( ERunOptions ClientRunFlags , string ClientApp , string ClientCmdLine , ProjectParams Params )
2014-03-14 14:13:41 -04:00
{
2014-09-08 14:02:00 -04:00
string DeviceArchitecture = GetBestDeviceArchitecture ( Params ) ;
2014-12-11 13:44:41 -05:00
string GPUArchitecture = GetBestGPUArchitecture ( Params ) ; ;
2014-08-26 14:02:30 -04:00
2014-08-26 09:56:29 -04:00
string ApkName = ClientApp + DeviceArchitecture + ".apk" ;
2014-03-14 14:13:41 -04:00
if ( ! File . Exists ( ApkName ) )
{
2014-09-18 17:49:40 -04:00
ApkName = GetFinalApkName ( Params , Path . GetFileNameWithoutExtension ( ClientApp ) , true , DeviceArchitecture , GPUArchitecture ) ;
2014-03-14 14:13:41 -04:00
}
2014-09-08 14:02:00 -04:00
Console . WriteLine ( "Apk='{0}', ClientApp='{1}', ExeName='{2}'" , ApkName , ClientApp , Params . ProjectGameExeFilename ) ;
2014-03-14 14:13:41 -04:00
// run aapt to get the name of the intent
string PackageName = GetPackageInfo ( ApkName , false ) ;
if ( PackageName = = null )
{
2014-09-12 06:23:49 -04:00
ErrorReporter . Error ( "Failed to get package name from " + ClientApp , ( int ) ErrorCodes . Error_FailureGettingPackageInfo ) ;
throw new AutomationException ( "Failed to get package name from " + ClientApp ) ;
2014-03-14 14:13:41 -04:00
}
string CommandLine = "shell am start -n " + PackageName + "/com.epicgames.ue4.GameActivity" ;
2014-08-18 13:29:39 -04:00
if ( Params . Prebuilt )
{
// clear the log
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , "logcat -c" ) ;
2014-08-18 13:29:39 -04:00
}
// Send a command to unlock the device before we try to run it
string UnlockCommandLine = "shell input keyevent 82" ;
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , UnlockCommandLine , null ) ;
2014-06-04 15:01:25 -04:00
2014-03-14 14:13:41 -04:00
// start the app on device!
2014-12-10 13:33:56 -05:00
ProcessResult ClientProcess = RunAdbCommand ( Params , CommandLine , null , ClientRunFlags ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
if ( Params . Prebuilt )
{
// save the output to the staging directory
string LogPath = Path . Combine ( Params . BaseStageDirectory , "Android\\logs" ) ;
string LogFilename = Path . Combine ( LogPath , "devicelog" + Params . Device + ".log" ) ;
string ServerLogFilename = Path . Combine ( CmdEnv . LogFolder , "devicelog" + Params . Device + ".log" ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
Directory . CreateDirectory ( LogPath ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
// check if the game is still running
// time out if it takes to long
DateTime StartTime = DateTime . Now ;
int TimeOutSeconds = Params . RunTimeoutSeconds ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
while ( true )
{
2014-12-10 13:33:56 -05:00
ProcessResult ProcessesResult = RunAdbCommand ( Params , "shell ps" , null , ERunOptions . SpewIsVerbose ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
string RunningProcessList = ProcessesResult . Output ;
if ( ! RunningProcessList . Contains ( PackageName ) )
{
break ;
}
Thread . Sleep ( 10 ) ;
2014-06-04 15:01:25 -04:00
2014-08-18 13:29:39 -04:00
TimeSpan DeltaRunTime = DateTime . Now - StartTime ;
if ( ( DeltaRunTime . TotalSeconds > TimeOutSeconds ) & & ( TimeOutSeconds ! = 0 ) )
{
Log ( "Device: " + Params . Device + " timed out while waiting for run to finish" ) ;
break ;
}
}
2014-06-20 11:57:13 -04:00
2014-08-18 13:29:39 -04:00
// this is just to get the ue4 log to go to the output
2014-12-10 13:33:56 -05:00
RunAdbCommand ( Params , "logcat -d -s UE4 -s Debug" ) ;
2014-06-20 11:57:13 -04:00
2014-08-18 13:29:39 -04:00
// get the log we actually want to save
2014-12-10 13:33:56 -05:00
ProcessResult LogFileProcess = RunAdbCommand ( Params , "logcat -d" , null , ERunOptions . AppMustExist ) ;
2014-08-18 13:29:39 -04:00
File . WriteAllText ( LogFilename , LogFileProcess . Output ) ;
File . WriteAllText ( ServerLogFilename , LogFileProcess . Output ) ;
}
2014-06-04 15:01:25 -04:00
2014-03-14 14:13:41 -04:00
return ClientProcess ;
}
public override void GetFilesToDeployOrStage ( ProjectParams Params , DeploymentContext SC )
{
2014-08-18 13:29:39 -04:00
// if (SC.StageExecutables.Count != 1 && Params.Package)
// {
// throw new AutomationException("Exactly one executable expected when staging Android. Had " + SC.StageExecutables.Count.ToString());
// }
//
// // stage all built executables
// foreach (var Exe in SC.StageExecutables)
// {
// string ApkName = Exe + GetArchitecture(Params) + ".apk";
//
// SC.StageFiles(StagedFileType.NonUFS, Params.ProjectBinariesFolder, ApkName);
// }
2014-03-14 14:13:41 -04:00
}
/// <summary>
/// Gets cook platform name for this platform.
/// </summary>
/// <param name="CookFlavor">Additional parameter used to indicate special sub-target platform.</param>
/// <returns>Cook platform string.</returns>
2014-08-18 13:29:39 -04:00
public override string GetCookPlatform ( bool bDedicatedServer , bool bIsClientOnly , string CookFlavor )
2014-03-14 14:13:41 -04:00
{
2014-08-18 13:29:39 -04:00
if ( CookFlavor . Length > 0 )
2014-03-14 14:13:41 -04:00
{
return "Android_" + CookFlavor ;
}
2014-08-18 13:29:39 -04:00
else
2014-03-14 14:13:41 -04:00
{
return "Android" ;
}
}
public override bool DeployPakInternalLowerCaseFilenames ( )
{
return false ;
}
public override bool DeployLowerCaseFilenames ( bool bUFSFile )
{
return false ;
}
public override string LocalPathToTargetPath ( string LocalPath , string LocalRoot )
{
return LocalPath . Replace ( "\\" , "/" ) . Replace ( LocalRoot , "../../.." ) ;
}
public override bool IsSupported { get { return true ; } }
public override string Remap ( string Dest )
{
return Dest ;
}
2014-09-23 20:31:24 -04:00
public override PakType RequiresPak ( ProjectParams Params )
2014-03-14 14:13:41 -04:00
{
2014-09-26 15:27:39 -04:00
// if packaging is enabled, always create a pak, otherwise use the Params.Pak value
return Params . Package ? PakType . Always : PakType . DontCare ;
2014-03-14 14:13:41 -04:00
}
2014-09-23 20:31:24 -04:00
2014-03-14 14:13:41 -04:00
#region Hooks
public override void PostBuildTarget ( UE4Build Build , string ProjectName , string UProjectPath , string Config )
{
// Run UBT w/ the prep for deployment only option
// This is required as UBT will 'fake' success when building via UAT and run
// the deployment prep step before all the required parts are present.
if ( ProjectName . Length > 0 )
{
string ProjectToBuild = ProjectName ;
if ( ProjectToBuild ! = "UE4Game" & & ! string . IsNullOrEmpty ( UProjectPath ) )
{
ProjectToBuild = UProjectPath ;
}
string UBTCommand = string . Format ( "\"{0}\" Android {1} -prepfordeploy" , ProjectToBuild , Config ) ;
CommandUtils . RunUBT ( UE4Build . CmdEnv , Build . UBTExecutable , UBTCommand ) ;
}
}
#endregion
2014-08-18 13:29:39 -04:00
public override List < string > GetDebugFileExtentions ( )
{
return new List < string > { } ;
}
2014-03-14 14:13:41 -04:00
}