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
2015-02-24 08:51:34 -05:00
private const string TargetAndroidLocation = "obb/" ;
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-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
2015-02-01 12:33:30 -05:00
ApkName = ApkName . Replace ( ProjectDir , Path . Combine ( CmdEnv . LocalRoot , "Engine/Binaries/Android" ) ) ;
2014-03-14 14:13:41 -04:00
}
}
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 ) ;
2015-02-24 08:51:34 -05:00
PackageVersion = IntVersion . ToString ( "0" ) ;
2014-03-14 14:13:41 -04:00
}
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 )
{
2015-01-20 10:05:42 -05:00
string ObbName = GetFinalObbName ( ApkName ) ;
string PackageName = GetPackageInfo ( ApkName , false ) ;
2015-02-24 08:51:34 -05:00
return TargetAndroidLocation + 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
{
2015-01-12 12:02:43 -05:00
return Path . Combine ( Path . GetDirectoryName ( ApkName ) , "Install_" + Params . ShortProjectName + "_" + Params . ClientConfigsToBuild [ 0 ] . ToString ( ) + Architecture + GPUArchitecture + ( Utils . IsRunningOnMono ? ".command" : ".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 ( ) ;
2015-02-04 20:05:59 -05:00
bool bPackageDataInsideApk = UnrealBuildTool . Android . UEDeployAndroid . PackageDataInsideApk ( false ) ;
2014-11-17 11:31:49 -05:00
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 : "" ) ;
2015-02-01 12:33:30 -05:00
if ( ! SC . IsCodeBasedProject )
{
2015-03-20 16:56:37 -04:00
string UE4SOName = GetFinalApkName ( Params , SC . StageExecutables [ 0 ] , false , bMakeSeparateApks ? Architecture : "" , bMakeSeparateApks ? GPUArchitecture : "" ) ;
UE4SOName = UE4SOName . Replace ( ".apk" , ".so" ) ;
if ( FileExists_NoExceptions ( UE4SOName ) = = false )
2015-02-01 12:33:30 -05:00
{
2015-03-20 16:56:37 -04:00
Log ( "Failed to find game .so " + UE4SOName ) ;
2015-02-01 12:33:30 -05:00
AutomationTool . ErrorReporter . Error ( "Stage Failed." , ( int ) AutomationTool . ErrorCodes . Error_MissingExecutable ) ;
2015-03-20 16:56:37 -04:00
throw new AutomationException ( "Could not find .so {0}. You may need to build the UE4 project with your target configuration and platform." , UE4SOName ) ;
2015-02-01 12:33:30 -05:00
}
}
2014-09-18 17:49:40 -04:00
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 ] ) ;
2015-02-04 20:05:59 -05:00
Deploy . PrepForUATPackageOrDeploy ( Params . ShortProjectName , SC . ProjectRoot , SOName , SC . LocalRoot + "/Engine" , Params . Distribution , CookFlavor , false ) ;
2014-12-10 13:33:56 -05:00
}
2015-01-20 10:05:42 -05:00
// Create APK specific OBB in case we have a detached OBB.
string DeviceObbName = "" ;
string ObbName = "" ;
2015-01-27 13:05:32 -05:00
if ( ! bPackageDataInsideApk )
2015-01-20 10:05:42 -05:00
{
DeviceObbName = GetDeviceObbName ( ApkName ) ;
ObbName = GetFinalObbName ( ApkName ) ;
CopyFile ( LocalObbName , ObbName ) ;
}
2014-12-10 13:33:56 -05:00
// Write install batch file(s).
2015-01-20 10:05:42 -05:00
string PackageName = GetPackageInfo ( ApkName , false ) ;
2014-12-10 13:33:56 -05:00
// 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 )
{
2015-01-27 13:05:32 -05:00
Log ( "Writing shell script for install with {0}" , bPackageDataInsideApk ? "data in APK" : "separate obb" ) ;
2014-12-11 15:30:24 -05:00
BatchLines = new string [ ] {
"#!/bin/sh" ,
2015-01-12 12:02:43 -05:00
"cd \"`dirname \"$0\"`\"" ,
2015-01-20 10:05:42 -05:00
"ADB=" ,
2015-01-22 14:33:53 -05:00
"if [ \"$ANDROID_HOME\" != \"\" ]; then ADB=$ANDROID_HOME/platform-tools/adb; else ADB=" + Environment . GetEnvironmentVariable ( "ANDROID_HOME" ) + "; fi" ,
2014-12-11 15:30:24 -05:00
"DEVICE=" ,
"if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi" ,
2015-01-27 13:05:32 -05:00
"echo" ,
"echo Uninstalling existing application. Failures here can almost always be ignored." ,
2014-12-11 15:30:24 -05:00
"$ADB $DEVICE uninstall " + PackageName ,
2015-01-27 13:05:32 -05:00
"echo" ,
"echo Installing existing application. Failures here indicate a problem with the device \\(connection or storage permissions\\) and are fatal." ,
2014-12-11 15:30:24 -05:00
"$ADB $DEVICE install " + Path . GetFileName ( ApkName ) ,
"if [ $? -eq 0 ]; then" ,
2015-01-27 13:05:32 -05:00
"\techo" ,
2015-04-22 11:14:29 -04:00
"\tNEWVERSION=$(echo \"`$ADB $DEVICE shell 'getprop ro.build.version.release'`\" | awk -F\".\" '{gsub(/-.*/,\"\",$4); if ($1 > 4 || ($1 == 4 && $2 >= 2 )) print 1; else print 0 }')" ,
"\t if [ $NEWVERSION -eq 1 ]; then STORAGE=/mnt/shell/emulated; else STORAGE=$(echo \"`$ADB $DEVICE shell 'echo $EXTERNAL_STORAGE'`\" | cat -v | tr -d '^M'); fi" ,
2015-01-27 13:05:32 -05:00
"\techo Removing old data. Failures here are usually fine - indicating the files were not on the device." ,
2015-04-22 11:14:29 -04:00
"\t$ADB $DEVICE shell rm -r $STORAGE/UE4Game/" + Params . ShortProjectName ,
"\t$ADB $DEVICE shell rm -r $STORAGE/UE4Game/UE4CommandLine.txt" ,
"\t$ADB $DEVICE shell rm -r $STORAGE/" + TargetAndroidLocation + PackageName ,
2015-01-27 13:05:32 -05:00
bPackageDataInsideApk ? "" : "\techo" ,
bPackageDataInsideApk ? "" : "\techo Installing new data. Failures here indicate storage problems \\(missing SD card or bad permissions\\) and are fatal." ,
bPackageDataInsideApk ? "" : "\t$ADB $DEVICE push " + Path . GetFileName ( ObbName ) + " $STORAGE/" + DeviceObbName ,
bPackageDataInsideApk ? "if [ 1 ]; then" : "\tif [ $? -eq 0 ]; then" ,
"\t\techo" ,
2015-01-12 12:02:43 -05:00
"\t\techo Installation successful" ,
2014-12-11 15:30:24 -05:00
"\t\texit 0" ,
"\tfi" ,
"fi" ,
"echo" ,
2015-01-12 12:02:43 -05:00
"echo There was an error installing the game or the obb file. Look above for more info." ,
2014-12-11 15:30:24 -05:00
"echo" ,
2015-01-12 12:02:43 -05:00
"echo Things to try:" ,
2015-01-27 13:05:32 -05:00
"echo Check that the device (and only the device) is listed with \\\"$ADB devices\\\" from a command prompt." ,
2015-01-12 12:02:43 -05:00
"echo Make sure all Developer options look normal on the device" ,
"echo Check that the device has an SD card." ,
2014-12-11 15:30:24 -05:00
"exit 1"
} ;
}
else
{
2015-01-27 13:05:32 -05:00
Log ( "Writing bat for install with {0}" , bPackageDataInsideApk ? "data in APK" : "separate OBB" ) ;
2014-12-11 15:30:24 -05:00
BatchLines = new string [ ] {
"setlocal" ,
2015-01-20 10:05:42 -05:00
"set ANDROIDHOME=%ANDROID_HOME%" ,
"if \"%ANDROIDHOME%\"==\"\" set ANDROIDHOME=" + Environment . GetEnvironmentVariable ( "ANDROID_HOME" ) ,
"set ADB=%ANDROIDHOME%\\platform-tools\\adb.exe" ,
2014-12-11 15:30:24 -05:00
"set DEVICE=" ,
2015-01-20 10:05:42 -05:00
"if not \"%1\"==\"\" set DEVICE=-s %1" ,
"for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand ( ) + "') do @set STORAGE=%%A" ,
2015-04-22 11:14:29 -04:00
"for /f \"tokens=1-2 delims=.\" %%M in ('%ADB% %DEVICE% shell getprop ro.build.version.release') do (" ,
" if \"%%M\"==\"\" (goto completed)" ,
" if \"%%N\"==\"\" (goto completed)" ,
" if %%M equ 4 (" ,
" if %%N geq 2 (set STORAGE=/mnt/shell/emulated)" ,
" ) else (" ,
" if %%M gtr 4 (set STORAGE=/mnt/shell/emulated)" ,
" );" ,
" @echo." ,
")" ,
":completed" ,
2015-01-27 13:05:32 -05:00
"@echo." ,
"@echo Uninstalling existing application. Failures here can almost always be ignored." ,
2014-12-11 15:30:24 -05:00
"%ADB% %DEVICE% uninstall " + PackageName ,
2015-01-27 13:05:32 -05:00
"@echo." ,
2015-01-29 15:44:55 -05:00
"@echo Installing existing application. Failures here indicate a problem with the device (connection or storage permissions) and are fatal." ,
2014-12-11 15:30:24 -05:00
"%ADB% %DEVICE% install " + Path . GetFileName ( ApkName ) ,
"@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error" ,
2015-02-24 08:51:34 -05:00
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/" + Params . ShortProjectName ,
2014-12-11 15:30:24 -05:00
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt" , // we need to delete the commandline in UE4Game or it will mess up loading
2015-02-24 08:51:34 -05:00
"%ADB% %DEVICE% shell rm -r %STORAGE%/" + TargetAndroidLocation + PackageName ,
2015-01-27 13:05:32 -05:00
bPackageDataInsideApk ? "" : "@echo." ,
2015-01-29 15:44:55 -05:00
bPackageDataInsideApk ? "" : "@echo Installing new data. Failures here indicate storage problems (missing SD card or bad permissions) and are fatal." ,
2015-01-27 13:05:32 -05:00
bPackageDataInsideApk ? "" : "%ADB% %DEVICE% push " + Path . GetFileName ( ObbName ) + " %STORAGE%/" + DeviceObbName ,
bPackageDataInsideApk ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error" ,
"@echo." ,
"@echo Installation successful" ,
2014-12-11 15:30:24 -05:00
"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 ) ;
2015-01-12 12:02:43 -05:00
if ( Utils . IsRunningOnMono )
{
CommandUtils . FixUnixFilePermissions ( BatchName ) ;
}
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 ( ) ;
2015-02-04 20:05:59 -05:00
bool bPackageDataInsideApk = UnrealBuildTool . Android . UEDeployAndroid . PackageDataInsideApk ( false ) ;
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 ) ;
}
2015-01-27 13:05:32 -05:00
if ( ! bPackageDataInsideApk & & ! FileExists ( ObbName ) )
2014-12-10 13:33:56 -05:00
{
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 ) ) ;
2015-01-27 13:05:32 -05:00
if ( ! bPackageDataInsideApk & & ! bAddedOBB )
2014-12-10 13:33:56 -05:00
{
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 ! = "" )
{
2015-01-14 16:02:37 -05:00
SerialNumber = "-s " + SerialNumber ;
2014-03-14 14:13:41 -04:00
}
2014-12-10 13:33:56 -05:00
2015-01-14 16:02:37 -05:00
return string . Format ( "{0} {1}" , SerialNumber , Args ) ;
2014-12-10 13:33:56 -05:00
}
private ProcessResult RunAdbCommand ( ProjectParams Params , string Args , string Input = null , ERunOptions Options = ERunOptions . Default )
{
2015-01-14 16:02:37 -05:00
string AdbCommand = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/platform-tools/adb" + ( Utils . IsRunningOnMono ? "" : ".exe" ) ) ;
return Run ( AdbCommand , GetAdbCommandLine ( Params , Args ) , Input , Options ) ;
2014-12-10 13:33:56 -05:00
}
private string RunAndLogAdbCommand ( ProjectParams Params , string Args , out int SuccessCode )
{
2015-01-14 16:02:37 -05:00
string AdbCommand = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/platform-tools/adb" + ( Utils . IsRunningOnMono ? "" : ".exe" ) ) ;
return RunAndLog ( CmdEnv , AdbCommand , 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 ( ) ) ;
}
}
* /
2015-03-27 18:37:53 -04:00
public override bool RetrieveDeployedManifests ( ProjectParams Params , DeploymentContext SC , out List < string > UFSManifests , out List < string > NonUFSManifests )
{
UFSManifests = null ;
NonUFSManifests = null ;
// Query the storage path from the device
string DeviceStorageQueryCommand = GetStorageQueryCommand ( ) ;
ProcessResult StorageResult = RunAdbCommand ( Params , DeviceStorageQueryCommand , null , ERunOptions . AppMustExist ) ;
String StorageLocation = StorageResult . Output . Trim ( ) ;
string RemoteDir = StorageLocation + "/UE4Game/" + Params . ShortProjectName ;
// Note: appends the device name to make the filename unique; these files will be deleted later during delta manifest generation
// Try retrieving the UFS files manifest files from the device
string UFSManifestFileName = CombinePaths ( SC . StageDirectory , DeploymentContext . UFSDeployedManifestFileName + "_" + Params . Device ) ;
ProcessResult UFSResult = RunAdbCommand ( Params , " pull " + RemoteDir + "/" + DeploymentContext . UFSDeployedManifestFileName + " \"" + UFSManifestFileName + "\"" , null , ERunOptions . AppMustExist ) ;
if ( ! UFSResult . Output . Contains ( "bytes" ) )
{
return false ;
}
// Try retrieving the non UFS files manifest files from the device
string NonUFSManifestFileName = CombinePaths ( SC . StageDirectory , DeploymentContext . NonUFSDeployedManifestFileName + "_" + Params . Device ) ;
ProcessResult NonUFSResult = RunAdbCommand ( Params , " pull " + RemoteDir + "/" + DeploymentContext . NonUFSDeployedManifestFileName + " \"" + NonUFSManifestFileName + "\"" , null , ERunOptions . AppMustExist ) ;
if ( ! NonUFSResult . Output . Contains ( "bytes" ) )
{
// Did not retrieve both so delete one we did retrieve
File . Delete ( UFSManifestFileName ) ;
return false ;
}
// Return the manifest files
UFSManifests = new List < string > ( ) ;
UFSManifests . Add ( UFSManifestFileName ) ;
NonUFSManifests = new List < string > ( ) ;
NonUFSManifests . Add ( NonUFSManifestFileName ) ;
return true ;
}
2014-08-18 13:29:39 -04:00
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 ] ) ;
2015-02-04 20:05:59 -05:00
Deploy . PrepForUATPackageOrDeploy ( Params . ShortProjectName , SC . ProjectRoot , SOName , SC . LocalRoot + "/Engine" , Params . Distribution , CookFlavor , true ) ;
2014-08-18 13:29:39 -04:00
}
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 ) ;
2015-03-27 18:37:53 -04:00
// Setup the OBB name and add the storage path (queried from the device) to it
string DeviceStorageQueryCommand = GetStorageQueryCommand ( ) ;
ProcessResult Result = RunAdbCommand ( Params , DeviceStorageQueryCommand , null , ERunOptions . AppMustExist ) ;
String StorageLocation = Result . Output . Trim ( ) ;
string DeviceObbName = StorageLocation + "/" + GetDeviceObbName ( ApkName ) ;
string RemoteDir = StorageLocation + "/UE4Game/" + Params . ShortProjectName ;
2014-03-14 14:13:41 -04:00
2015-03-27 18:37:53 -04:00
// determine if APK out of date
string APKLastUpdateTime = new FileInfo ( ApkName ) . LastWriteTime . ToString ( ) ;
bool bNeedAPKInstall = true ;
if ( Params . IterativeDeploy )
2014-11-17 11:31:49 -05:00
{
2015-03-27 18:37:53 -04:00
// Check for apk installed with this package name on the device
ProcessResult InstalledResult = RunAdbCommand ( Params , "shell pm list packages " + PackageName , null , ERunOptions . AppMustExist ) ;
if ( InstalledResult . Output . Contains ( PackageName ) )
2014-11-17 11:31:49 -05:00
{
2015-03-27 18:37:53 -04:00
// See if apk is up to date on device
InstalledResult = RunAdbCommand ( Params , "shell cat " + RemoteDir + "/APKFileStamp.txt" , null , ERunOptions . AppMustExist ) ;
if ( InstalledResult . Output . StartsWith ( "APK: " ) )
2014-11-17 11:31:49 -05:00
{
2015-03-27 18:37:53 -04:00
if ( InstalledResult . Output . Substring ( 5 ) . Trim ( ) = = APKLastUpdateTime )
bNeedAPKInstall = false ;
2015-04-02 16:30:40 -04:00
// Stop the previously running copy (uninstall/install did this before)
InstalledResult = RunAdbCommand ( Params , "shell am force-stop " + PackageName , null , ERunOptions . AppMustExist ) ;
if ( InstalledResult . Output . Contains ( "Error" ) )
{
// force-stop not supported (Android < 3.0) so check if package is actually running
// Note: cannot use grep here since it may not be installed on device
InstalledResult = RunAdbCommand ( Params , "shell ps" , null , ERunOptions . AppMustExist ) ;
if ( InstalledResult . Output . Contains ( PackageName ) )
{
// it is actually running so use the slow way to kill it (uninstall and reinstall)
bNeedAPKInstall = true ;
}
}
2014-11-17 11:31:49 -05:00
}
}
}
2015-03-27 18:37:53 -04:00
// install new APK if needed
if ( bNeedAPKInstall )
{
// try uninstalling an old app with the same identifier.
int SuccessCode = 0 ;
string UninstallCommandline = "uninstall " + PackageName ;
RunAndLogAdbCommand ( Params , UninstallCommandline , out SuccessCode ) ;
// install the apk
string InstallCommandline = "install \"" + ApkName + "\"" ;
string InstallOutput = RunAndLogAdbCommand ( Params , InstallCommandline , out SuccessCode ) ;
int FailureIndex = InstallOutput . IndexOf ( "Failure" ) ;
// 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 ;
}
}
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 ) ;
// 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-03-14 14:13:41 -04:00
2015-03-27 18:37:53 -04:00
HashSet < string > EntriesToDeploy = new HashSet < string > ( ) ;
2014-03-14 14:13:41 -04:00
2015-03-27 18:37:53 -04:00
if ( Params . IterativeDeploy )
2014-08-18 13:29:39 -04:00
{
2015-03-30 12:43:30 -04:00
// always send UE4CommandLine.txt (it was written above after delta checks applied)
EntriesToDeploy . Add ( IntermediateCmdLineFile ) ;
2015-03-27 18:37:53 -04:00
// Add non UFS files if any to deploy
String NonUFSManifestPath = SC . GetNonUFSDeploymentDeltaPath ( ) ;
if ( File . Exists ( NonUFSManifestPath ) )
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
string NonUFSFiles = File . ReadAllText ( NonUFSManifestPath ) ;
foreach ( string Filename in NonUFSFiles . Split ( '\n' ) )
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
if ( ! string . IsNullOrEmpty ( Filename ) & & ! string . IsNullOrWhiteSpace ( Filename ) )
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
EntriesToDeploy . Add ( CombinePaths ( SC . StageDirectory , Filename . Trim ( ) ) ) ;
2014-08-18 13:29:39 -04:00
}
}
}
2014-06-18 17:43:14 -04:00
2015-03-27 18:37:53 -04:00
// Add UFS files if any to deploy
String UFSManifestPath = SC . GetUFSDeploymentDeltaPath ( ) ;
if ( File . Exists ( UFSManifestPath ) )
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
string UFSFiles = File . ReadAllText ( UFSManifestPath ) ;
foreach ( string Filename in UFSFiles . Split ( '\n' ) )
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
if ( ! string . IsNullOrEmpty ( Filename ) & & ! string . IsNullOrWhiteSpace ( Filename ) )
{
EntriesToDeploy . Add ( CombinePaths ( SC . StageDirectory , Filename . Trim ( ) ) ) ;
}
2014-08-18 13:29:39 -04:00
}
}
2015-03-27 18:37:53 -04:00
// For now, if too many files may be better to just push them all
if ( EntriesToDeploy . Count > 500 )
{
// make sure device is at a clean state
RunAdbCommand ( Params , "shell rm -r " + RemoteDir ) ;
EntriesToDeploy . Clear ( ) ;
EntriesToDeploy . TrimExcess ( ) ;
EntriesToDeploy . Add ( SC . StageDirectory ) ;
}
2014-08-18 13:29:39 -04:00
}
2015-03-27 18:37:53 -04:00
else
2014-08-18 13:29:39 -04:00
{
2015-03-27 18:37:53 -04:00
// make sure device is at a clean state
RunAdbCommand ( Params , "shell rm -r " + RemoteDir ) ;
// Copy UFS files..
string [ ] Files = Directory . GetFiles ( SC . StageDirectory , "*" , SearchOption . AllDirectories ) ;
System . Array . Sort ( Files ) ;
// 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 ) ;
}
}
}
// 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.
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-08-18 13:29:39 -04:00
}
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 )
{
2015-04-02 16:30:40 -04:00
Thread . Sleep ( 1 ) ;
2014-08-18 13:29:39 -04:00
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-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
}
2015-03-27 18:37:53 -04:00
// write new timestamp for APK (do it here since RemoteDir will now exist)
if ( bNeedAPKInstall )
{
int SuccessCode = 0 ;
RunAndLogAdbCommand ( Params , "shell \"echo 'APK: " + APKLastUpdateTime + "' > " + RemoteDir + "/APKFileStamp.txt\"" , out SuccessCode ) ;
}
}
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
2015-01-20 10:05:42 -05:00
string path = Environment . ExpandEnvironmentVariables ( "%ANDROID_HOME%/build-tools/" ) ;
string [ ] Subdirs = Directory . GetDirectories ( path ) ;
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 ) ;
2015-01-20 10:05:42 -05:00
throw new AutomationException ( "Failed to find %ANDROID_HOME%/build-tools subdirectory" ) ;
}
2014-03-14 14:13:41 -04:00
// 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
2015-03-13 14:05:13 -04:00
else if ( DeviceArch = = "-x64" )
2014-09-08 14:02:00 -04:00
{
2015-03-13 14:05:13 -04:00
if ( Array . IndexOf ( AppArchitectures , "-x86" ) ! = - 1 )
2014-09-08 14:02:00 -04:00
{
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" ;
}
2015-03-13 16:21:52 -04:00
else
{
// future-proof by dropping back to armv7 for unknown
DeviceArch = "-armv7" ;
}
2014-09-08 14:02:00 -04:00
}
// if after the fallbacks, we still don't have it, we can't continue
if ( Array . IndexOf ( AppArchitectures , DeviceArch ) = = - 1 )
{
2015-03-13 14:05:13 -04:00
string ErrorString = String . Format ( "Unable to run because you don't have an apk that is usable on {0}. Looked for {1}" , Params . Device , DeviceArch ) ;
2014-09-12 06:23:49 -04:00
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 ( ) ;
2015-04-08 16:23:03 -04:00
// look for AEP support (on device and in project)
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
}
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
}
2014-03-14 14:13:41 -04:00
// start the app on device!
2015-01-05 08:49:14 -05:00
string CommandLine = "shell am start -n " + PackageName + "/com.epicgames.ue4.GameActivity" ;
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
}